[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github:\n- ofek\ncustom:\n- https://ofek.dev/donate/\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-hatch_bug_report.yml",
    "content": "---\nname: Hatch Bug report\ndescription: Problems and issues with code in Hatch\nbody:\n- type: markdown\n  attributes:\n    # yamllint disable rule:line-length\n    value: \"\n      <img src='https://raw.githubusercontent.com/pypa/hatch/HEAD/docs/assets/images/logo.svg' align='left' width='80' height='80'>\n      Thank you for finding the time to report the problem, we ask that you use \n      the command `hatch self report` to report bugs instead of using this form.\n      \n      <br clear='left'/>\"\n    # yamllint enable rule:line-length\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-feature_request.yml",
    "content": "---\nname: Hatch feature request\ndescription: Suggest an idea for this project\nlabels: [\"kind:feature\"]\nbody:\n- type: markdown\n  attributes:\n    # yamllint disable rule:line-length\n    value: |\n      <img src='https://raw.githubusercontent.com/pypa/hatch/HEAD/docs/assets/images/logo.svg' align='left' width='80' height='80'>\n      Thank you for finding the time to propose new feature!\n\n      We really appreciate the community efforts to improve Hatch.\n\n      Please keep feature requests to smaller incremental changes that do not make changes to the assumptions\n      about hatch. \n      \n      If unsure - open a [discussion](https://github.com/pypa/hatch/discussions) first to gather\n      an initial feedback on your idea.\n\n      <br clear='left'/>\n\n    # yamllint enable rule:line-length\n- type: textarea\n  attributes:\n    label: Description\n    description: A short description of your feature\n- type: textarea\n  attributes:\n    label: Use case/motivation\n    description: What would you like to happen?\n    placeholder: >\n      Rather than telling us how you might implement this feature, try to take a\n      step back and describe what you are trying to achieve.\n- type: textarea\n  attributes:\n    label: Related issues\n    description: Is there currently another issue associated with this?\n- type: checkboxes\n  attributes:\n    label: Are you willing to submit a PR?\n    description: >\n      If want to submit a PR you do not need to open feature request, <b>just create the PR!</b>.\n      Especially if you already have a good understanding of how to implement the feature.\n      Hatch is a PyPA managed project but we love to bring new contributors in.\n      Find us on the PyPA Discord Server under #hatch\n      It's optional though - if you have good idea for small feature,\n      others might implement it if they pick an interest in it, so feel free to leave that\n      checkbox unchecked.\n    options:\n    - label: Yes I am willing to submit a PR!\n- type: checkboxes\n  attributes:\n    label: Code of Conduct\n    description: The Code of Conduct helps create a safe space for everyone. We require\n      that everyone agrees to it.\n    options:\n    - label: >\n        I agree to follow the Python Software Foundation's\n        [Code of Conduct](https://policies.python.org/python.org/code-of-conduct/)\n      required: true\n- type: markdown\n  attributes:\n    value: Thanks for completing our form!\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: github-actions\n  directory: /\n  schedule:\n    interval: monthly\n"
  },
  {
    "path": ".github/workflows/auto-merge.yml",
    "content": "name: auto-merge\n\non:\n  pull_request_target:\n    types:\n    - opened\n    - reopened\n    - synchronize\n    branches:\n    - master\n\njobs:\n  dependabot:\n    runs-on: ubuntu-latest\n    if: ${{ github.actor == 'dependabot[bot]' }}\n\n    steps:\n    - name: Wait for tests to succeed\n      uses: lewagon/wait-on-check-action@v1.3.4\n      with:\n        ref: ${{ github.ref }}\n        check-name: check\n        wait-interval: 10\n        repo-token: ${{ secrets.GITHUB_TOKEN }}\n\n    - name: Enable auto-merge for Dependabot PRs\n      run: gh pr merge --auto --squash ${{ github.event.pull_request.html_url }}\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/build-distributions.yml",
    "content": "name: build distributions\n\non:\n  workflow_call:\n    inputs:\n      version:\n        required: false\n        type: string\n\ndefaults:\n  run:\n    shell: bash\n\nenv:\n  DIST_URL: \"https://github.com/indygreg/python-build-standalone/releases/download\"\n  DIST_VERSION: \"20240415\"\n  DIST_PYTHON_VERSION: \"3.12.3\"\n  PYTHONDONTWRITEBYTECODE: \"1\"\n  PIP_ONLY_BINARY: \":all:\"\n  # Some pip environment variables are weird, this means do not compile\n  PIP_NO_COMPILE: \"0\"\n\njobs:\n  ensure-installable:\n    name: Ensure Hatch is installable\n    runs-on: ubuntu-22.04\n\n    steps:\n    - name: Set up Python ${{ env.DIST_PYTHON_VERSION }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ env.DIST_PYTHON_VERSION }}\n\n    - name: Install UV\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install Hatch\n      if: inputs.version\n      # Try to install the specific version of Hatch that was just released until successful\n      run: |-\n        for i in {1..20}; do\n          uv pip install --system hatch==${{ inputs.version }} && break || sleep 5\n        done\n\n  linux:\n    name: Distribution ${{ matrix.job.target }}\n    needs: ensure-installable\n    runs-on: ubuntu-22.04\n    strategy:\n      fail-fast: false\n      matrix:\n        job:\n        - target: x86_64-unknown-linux-gnu\n          image: quay.io/pypa/manylinux2014_x86_64\n          target-override: x86_64_v3-unknown-linux-gnu\n        - target: aarch64-unknown-linux-gnu\n          image: quay.io/pypa/manylinux_2_28_aarch64\n          emulation: arm64\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: ${{ inputs.version && 1 || 0 }}\n\n    - name: Set up QEMU\n      if: matrix.job.emulation\n      uses: docker/setup-qemu-action@v3\n\n    - name: Set up Docker container\n      run: >-\n        docker run --rm -d\n        --name builder\n        --workdir /home\n        --env PYTHONDONTWRITEBYTECODE\n        --env PIP_ONLY_BINARY\n        --env PIP_NO_COMPILE\n        --volume ${{ github.workspace }}:/home/hatch\n        ${{ matrix.job.image }}\n        sleep infinity\n\n    - name: Download distribution\n      run: >-\n        docker exec builder\n        curl -LO\n        ${{ env.DIST_URL }}/${{ env.DIST_VERSION }}/cpython-${{ env.DIST_PYTHON_VERSION }}+${{ env.DIST_VERSION }}-${{ matrix.job.target-override || matrix.job.target }}-install_only.tar.gz\n\n    - name: Unpack distribution\n      run: >-\n        docker exec builder\n        tar xzf cpython-${{ env.DIST_PYTHON_VERSION }}+${{ env.DIST_VERSION }}-${{ matrix.job.target-override || matrix.job.target }}-install_only.tar.gz\n\n    - name: Install Hatch\n      run: >-\n        docker exec builder\n        /home/python/bin/python -m pip install\n        ${{ inputs.version && format('hatch=={0}', inputs.version) || '/home/hatch' }}\n\n    - name: Make scripts portable\n      run: >-\n        docker exec builder\n        /home/python/bin/python /home/hatch/release/unix/make_scripts_portable.py\n\n    - name: Strip debug symbols\n      run: >-\n        docker exec builder\n        sh -c \"find /home/python -name '*.so' | xargs strip -S\"\n\n    - name: Archive distribution\n      run: >-\n        docker exec builder\n        tar czf hatch-dist-${{ matrix.job.target }}.tar.gz python\n\n    - name: Move to host\n      run: docker cp builder:/home/hatch-dist-${{ matrix.job.target }}.tar.gz .\n\n    - name: Check original size\n      run: >-\n        docker exec builder\n        ls -lh cpython-${{ env.DIST_PYTHON_VERSION }}+${{ env.DIST_VERSION }}-${{ matrix.job.target-override || matrix.job.target }}-install_only.tar.gz\n\n    - name: Check final size\n      run: ls -lh hatch-dist-${{ matrix.job.target }}.tar.gz\n\n    - name: Upload archive\n      uses: actions/upload-artifact@v4\n      with:\n        name: distribution-${{ matrix.job.target }}\n        path: hatch-dist-${{ matrix.job.target }}.tar.gz\n\n  windows-macos:\n    name: Distribution ${{ matrix.job.target }}\n    needs: ensure-installable\n    runs-on: ${{ matrix.job.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        job:\n        - target: x86_64-pc-windows-msvc\n          os: windows-2022\n        - target: aarch64-apple-darwin\n          os: macos-14\n        - target: x86_64-apple-darwin\n          os: macos-14\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: ${{ inputs.version && 1 || 0 }}\n\n    - name: Download distribution\n      run: curl -LO ${{ env.DIST_URL }}/${{ env.DIST_VERSION }}/cpython-${{ env.DIST_PYTHON_VERSION }}+${{ env.DIST_VERSION }}-${{ matrix.job.target }}-install_only.tar.gz\n\n    - name: Unpack distribution\n      run: tar xzf cpython-${{ env.DIST_PYTHON_VERSION }}+${{ env.DIST_VERSION }}-${{ matrix.job.target }}-install_only.tar.gz\n\n    - name: Install Hatch\n      run: >-\n        ${{ startsWith(matrix.job.os, 'windows-') && '.\\\\python\\\\python.exe' || './python/bin/python' }}\n        -m pip install\n        ${{ inputs.version && format('hatch=={0}', inputs.version) || '.' }}\n\n    - name: Make scripts portable\n      run: >-\n        ${{ startsWith(matrix.job.os, 'windows-') && '.\\\\python\\\\python.exe' || './python/bin/python' }}\n        release/${{ startsWith(matrix.job.os, 'windows-') && 'windows' || 'unix' }}/make_scripts_portable.py\n\n    - name: Strip debug symbols\n      if: startsWith(matrix.job.os, 'macos-')\n      run: find python -name '*.so' | xargs strip -S\n\n    - name: Remove debug symbols\n      if: startsWith(matrix.job.os, 'windows-')\n      run: Get-ChildItem -Path python -Filter \"*.pdb\" -Recurse | Remove-Item\n      shell: pwsh\n\n    - name: Archive distribution\n      run: tar czf hatch-dist-${{ matrix.job.target }}.tar.gz python\n\n    - name: Check original size\n      run: ls -lh cpython-${{ env.DIST_PYTHON_VERSION }}+${{ env.DIST_VERSION }}-${{ matrix.job.target }}-install_only.tar.gz\n\n    - name: Check final size\n      run: ls -lh hatch-dist-${{ matrix.job.target }}.tar.gz\n\n    - name: Upload archive\n      uses: actions/upload-artifact@v4\n      with:\n        name: distribution-${{ matrix.job.target }}\n        path: hatch-dist-${{ matrix.job.target }}.tar.gz\n"
  },
  {
    "path": ".github/workflows/build-hatch.yml",
    "content": "name: build hatch\n\non:\n  push:\n    tags:\n    - hatch-v*\n    branches:\n    - master\n  pull_request:\n    branches:\n    - master\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\ndefaults:\n  run:\n    shell: bash\n\nenv:\n  APP_NAME: hatch\n  PYTHON_VERSION: \"3.12\"\n  PYOXIDIZER_VERSION: \"0.24.0\"\n  DIST_URL: \"https://github.com/pypa/hatch/releases/download\"\n\njobs:\n  python-artifacts:\n    name: Build wheel and source distribution\n    runs-on: ubuntu-latest\n\n    outputs:\n      old-version: ${{ steps.version.outputs.old-version }}\n      version: ${{ steps.version.outputs.version }}\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n\n    - name: Set up Python ${{ env.PYTHON_VERSION }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ env.PYTHON_VERSION }}\n\n    - name: Install UV\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install tools\n      run: |-\n        uv pip install --system build\n        uv pip install --system .\n        hatch env create\n\n    # Windows installers don't accept non-integer versions so we ubiquitously\n    # perform the following transformation: X.Y.Z.devN -> X.Y.Z.N\n    - name: Set project version\n      id: version\n      run: |-\n        old_version=\"$(hatch version)\"\n        version=\"${old_version/dev/}\"\n\n        echo \"old-version=$old_version\" >> $GITHUB_OUTPUT\n        echo \"version=$version\" >> $GITHUB_OUTPUT\n        echo \"$version\"\n\n    - name: Build\n      run: python -m build\n\n    - name: Upload artifacts\n      uses: actions/upload-artifact@v4\n      with:\n        name: python-artifacts\n        path: dist/*\n        if-no-files-found: error\n\n  publish-pypi:\n    name: Publish to PyPI\n    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')\n    needs: python-artifacts\n    runs-on: ubuntu-latest\n\n    permissions:\n      id-token: write\n\n    steps:\n    - name: Download Python artifacts\n      uses: actions/download-artifact@v4\n      with:\n        name: python-artifacts\n        path: dist\n\n    - name: Push Python artifacts to PyPI\n      uses: pypa/gh-action-pypi-publish@v1.12.3\n      with:\n        skip-existing: true\n\n  binaries:\n    name: Binary ${{ matrix.job.target }} (${{ matrix.job.os }})\n    needs:\n    - python-artifacts\n    runs-on: ${{ matrix.job.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        job:\n        # Linux\n        - target: aarch64-unknown-linux-gnu\n          os: ubuntu-22.04\n          use-dist: true\n          cross: true\n        - target: x86_64-unknown-linux-gnu\n          os: ubuntu-22.04\n          use-dist: true\n          cross: true\n        - target: x86_64-unknown-linux-musl\n          os: ubuntu-22.04\n          cross: true\n        - target: powerpc64le-unknown-linux-gnu\n          os: ubuntu-22.04\n          cross: true\n        # Windows\n        - target: x86_64-pc-windows-msvc\n          os: windows-2022\n          use-dist: true\n        - target: i686-pc-windows-msvc\n          os: windows-2022\n        # macOS\n        - target: aarch64-apple-darwin\n          os: macos-14\n          use-dist: true\n        - target: x86_64-apple-darwin\n          os: macos-14\n          use-dist: true\n\n    env:\n      CARGO: cargo\n      CARGO_BUILD_TARGET: ${{ matrix.job.target }}\n      PYAPP_REPO: pyapp\n      PYAPP_VERSION: \"0.22.0\"\n      PYAPP_UV_ENABLED: \"true\"\n      PYAPP_PASS_LOCATION: \"true\"\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n\n    - name: Fetch PyApp\n      run: >-\n        mkdir $PYAPP_REPO && curl -L\n        https://github.com/ofek/pyapp/releases/download/v$PYAPP_VERSION/source.tar.gz\n        |\n        tar --strip-components=1 -xzf - -C $PYAPP_REPO\n\n    - name: Set up Python ${{ env.PYTHON_VERSION }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ env.PYTHON_VERSION }}\n\n    - name: Install UV\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install Hatch\n      run: |-\n        uv pip install --system -e .\n        hatch env create\n\n    - name: Install Rust toolchain\n      uses: dtolnay/rust-toolchain@stable\n      with:\n        targets: ${{ matrix.job.target }}\n\n    - name: Set up cross compiling\n      if: matrix.job.cross\n      uses: taiki-e/install-action@v2\n      with:\n        tool: cross\n\n    - name: Configure cross compiling\n      if: matrix.job.cross\n      run: echo \"CARGO=cross\" >> $GITHUB_ENV\n\n    - name: Configure target\n      run: |-\n        config_file=\"$PYAPP_REPO/.cargo/config_${{ matrix.job.target }}.toml\"\n        if [[ -f \"$config_file\" ]]; then\n          mv \"$config_file\" \"$PYAPP_REPO/.cargo/config.toml\"\n        fi\n\n    - name: Download Python artifacts\n      if: ${{ !startsWith(github.event.ref, 'refs/tags') }}\n      uses: actions/download-artifact@v4\n      with:\n        name: python-artifacts\n        path: dist\n\n    - name: Configure embedded project\n      if: ${{ !startsWith(github.event.ref, 'refs/tags') }}\n      run: |-\n        cd dist\n        wheel=\"$(echo *.whl)\"\n        mv \"$wheel\" \"../$PYAPP_REPO\"\n        echo \"PYAPP_PROJECT_PATH=$wheel\" >> $GITHUB_ENV\n\n    - name: Configure release with distribution\n      if: startsWith(github.event.ref, 'refs/tags') && matrix.job.use-dist\n      run: |-\n        echo \"PYAPP_SKIP_INSTALL=true\" >> $GITHUB_ENV\n        echo \"PYAPP_FULL_ISOLATION=true\" >> $GITHUB_ENV\n        echo \"PYAPP_DISTRIBUTION_SOURCE=${{ env.DIST_URL }}/hatch-v${{ needs.python-artifacts.outputs.version }}/hatch-dist-${{ matrix.job.target }}.tar.gz\" >> $GITHUB_ENV\n        echo \"PYAPP_DISTRIBUTION_PATH_PREFIX=python\" >> $GITHUB_ENV\n        echo \"PYAPP_ALLOW_UPDATES=true\" >> $GITHUB_ENV\n\n        # Disable in the case of self updates\n        echo \"PYAPP_UV_ENABLED=false\" >> $GITHUB_ENV\n\n    - name: Build binary\n      run: hatch build --target binary\n\n    - name: Correct binary version\n      run: |-\n        old_version=\"${{ needs.python-artifacts.outputs.old-version }}\"\n        version=\"${{ needs.python-artifacts.outputs.version }}\"\n\n        if [[ \"$version\" != \"$old_version\" ]]; then\n          cd dist/binary\n          old_binary=\"$(ls)\"\n          binary=\"${old_binary/$old_version/$version}\"\n          mv \"$old_binary\" \"$binary\"\n        fi\n\n    - name: Archive binary\n      run: |-\n        mkdir packaging\n        cd dist/binary\n\n        old_binary=\"$(ls)\"\n\n        if [[ \"$old_binary\" =~ -pc-windows- ]]; then\n          new_binary=\"${{ env.APP_NAME }}.exe\"\n          mv \"$old_binary\" \"$new_binary\"\n          7z a \"../../packaging/${{ env.APP_NAME }}-${{ matrix.job.target }}.zip\" \"$new_binary\"\n        else\n          new_binary=\"${{ env.APP_NAME }}\"\n          mv \"$old_binary\" \"$new_binary\"\n          chmod +x \"$new_binary\"\n          tar -czf \"../../packaging/${{ env.APP_NAME }}-${{ matrix.job.target }}.tar.gz\" \"$new_binary\"\n        fi\n\n    - name: Upload staged archive\n      if: runner.os != 'Linux'\n      uses: actions/upload-artifact@v4\n      with:\n        name: staged-${{ runner.os }}-${{ matrix.job.target }}\n        path: packaging/*\n        if-no-files-found: error\n\n    - name: Upload archive\n      if: runner.os == 'Linux'\n      uses: actions/upload-artifact@v4\n      with:\n        name: standalone-${{ matrix.job.target }}\n        path: packaging/*\n        if-no-files-found: error\n\n  windows-packaging:\n    name: Build Windows installers\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository\n    needs:\n    - binaries\n    - python-artifacts\n    runs-on: windows-2022\n\n    env:\n      VERSION: ${{ needs.python-artifacts.outputs.version }}\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Set up Python ${{ env.PYTHON_VERSION }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ env.PYTHON_VERSION }}\n\n    - name: Install UV\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install PyOxidizer ${{ env.PYOXIDIZER_VERSION }}\n      run: uv pip install --system pyoxidizer==${{ env.PYOXIDIZER_VERSION }}\n\n    - name: Download staged binaries\n      uses: actions/download-artifact@v4\n      with:\n        pattern: staged-${{ runner.os }}-*\n        path: archives\n        merge-multiple: true\n\n    - name: Extract staged binaries\n      run: |-\n        mkdir bin\n\n        cd archives\n        for f in *; do\n          binary_id=${f:0:-4}\n          7z e \"$f\" -o../bin\n          mv \"../bin/${{ env.APP_NAME }}.exe\" \"../bin/$binary_id.exe\"\n        done\n\n    # bin/<APP_NAME>-<TARGET>.exe -> targets/<TARGET>/<APP_NAME>.exe\n    - name: Prepare binaries\n      run: |-\n        mkdir targets\n        for f in bin/*; do\n          if [[ \"$f\" =~ ${{ env.APP_NAME }}-(.+).exe$ ]]; then\n            target=\"${BASH_REMATCH[1]}\"\n            mkdir \"targets/$target\"\n            mv \"$f\" \"targets/$target/${{ env.APP_NAME }}.exe\"\n          fi\n        done\n\n    - name: Build installers\n      run: >-\n        pyoxidizer build windows_installers\n        --release\n        --var version ${{ env.VERSION }}\n\n    - name: Prepare installers\n      run: |-\n        mkdir installers\n        mv build/*/release/*/*.{exe,msi} installers\n\n        cd installers\n        universal_installer=\"$(ls *.exe)\"\n        mv \"$universal_installer\" \"${{ env.APP_NAME }}-universal.exe\"\n\n    - name: Upload binaries\n      uses: actions/upload-artifact@v4\n      with:\n        name: standalone-${{ runner.os }}\n        path: archives/*\n        if-no-files-found: error\n\n    - name: Upload installers\n      uses: actions/upload-artifact@v4\n      with:\n        name: installers-${{ runner.os }}\n        path: installers/*\n        if-no-files-found: error\n\n  macos-packaging:\n    name: Build macOS installer and sign/notarize artifacts\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository\n    needs:\n    - binaries\n    - python-artifacts\n    runs-on: macos-14\n\n    env:\n      VERSION: ${{ needs.python-artifacts.outputs.version }}\n      NOTARY_WAIT_TIME: \"3600\"  # 1 hour\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Set up Python ${{ env.PYTHON_VERSION }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ env.PYTHON_VERSION }}\n\n    - name: Install UV\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install PyOxidizer ${{ env.PYOXIDIZER_VERSION }}\n      run: uv pip install --system pyoxidizer==${{ env.PYOXIDIZER_VERSION }}\n\n    - name: Install rcodesign\n      env:\n        ARCHIVE_NAME: \"apple-codesign-0.27.0-x86_64-apple-darwin\"\n      run: >-\n        curl -L\n        \"https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.27.0/$ARCHIVE_NAME.tar.gz\"\n        |\n        tar --strip-components=1 -xzf - -C /usr/local/bin \"$ARCHIVE_NAME/rcodesign\"\n\n    - name: Download staged binaries\n      uses: actions/download-artifact@v4\n      with:\n        pattern: staged-${{ runner.os }}-*\n        path: archives\n        merge-multiple: true\n\n    - name: Extract staged binaries\n      run: |-\n        mkdir bin\n\n        cd archives\n        for f in *; do\n          binary_id=${f:0:${#f}-7}\n          tar -xzf \"$f\" -C ../bin\n          mv \"../bin/${{ env.APP_NAME }}\" \"../bin/$binary_id\"\n        done\n\n    - name: Write credentials\n      env:\n        APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE: \"${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE }}\"\n        APPLE_DEVELOPER_ID_APPLICATION_PRIVATE_KEY: \"${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_PRIVATE_KEY }}\"\n        APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE: \"${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE }}\"\n        APPLE_DEVELOPER_ID_INSTALLER_PRIVATE_KEY: \"${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_PRIVATE_KEY }}\"\n        APPLE_APP_STORE_CONNECT_API_DATA: \"${{ secrets.APPLE_APP_STORE_CONNECT_API_DATA }}\"\n      run: |-\n        echo \"$APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE\" > /tmp/certificate-application.pem\n        echo \"$APPLE_DEVELOPER_ID_APPLICATION_PRIVATE_KEY\" > /tmp/private-key-application.pem\n        echo \"$APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE\" > /tmp/certificate-installer.pem\n        echo \"$APPLE_DEVELOPER_ID_INSTALLER_PRIVATE_KEY\" > /tmp/private-key-installer.pem\n        echo \"$APPLE_APP_STORE_CONNECT_API_DATA\" > /tmp/app-store-connect.json\n\n    # https://developer.apple.com/documentation/security/hardened_runtime\n    - name: Sign binaries\n      run: |-\n        for f in bin/*; do\n          rcodesign sign -vv \\\n          --pem-source /tmp/certificate-application.pem \\\n          --pem-source /tmp/private-key-application.pem \\\n          --code-signature-flags runtime \\\n          \"$f\"\n        done\n\n    # https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution\n    - name: Notarize binaries\n      run: |-\n        mkdir notarize-bin\n\n        cd bin\n        for f in *; do\n          zip \"../notarize-bin/$f.zip\" \"$f\"\n        done\n\n        cd ../notarize-bin\n        for f in *; do\n          rcodesign notary-submit -vv \\\n          --max-wait-seconds ${{ env.NOTARY_WAIT_TIME }} \\\n          --api-key-path /tmp/app-store-connect.json \\\n          \"$f\"\n        done\n\n    - name: Archive binaries\n      run: |-\n        rm archives/*\n\n        cd bin\n        for f in *; do\n          mv \"$f\" \"${{ env.APP_NAME }}\"\n          tar -czf \"../archives/$f.tar.gz\" \"${{ env.APP_NAME }}\"\n          mv \"${{ env.APP_NAME }}\" \"$f\"\n        done\n\n    # bin/<APP_NAME>-<TARGET> -> targets/<TARGET>/<APP_NAME>\n    - name: Prepare binaries\n      run: |-\n        mkdir targets\n        for f in bin/*; do\n          if [[ \"$f\" =~ ${{ env.APP_NAME }}-(.+)$ ]]; then\n            target=\"${BASH_REMATCH[1]}\"\n            mkdir \"targets/$target\"\n            mv \"$f\" \"targets/$target/${{ env.APP_NAME }}\"\n          fi\n        done\n\n    - name: Build universal binary\n      run: >-\n        pyoxidizer build macos_universal_binary\n        --release\n        --var version ${{ env.VERSION }}\n\n    - name: Prepare universal binary\n      id: binary\n      run: |-\n        binary=$(echo build/*/release/*/${{ env.APP_NAME }})\n        chmod +x \"$binary\"\n        echo \"path=$binary\" >> \"$GITHUB_OUTPUT\"\n\n    - name: Build PKG\n      run: >-\n        python release/macos/build_pkg.py\n        --binary ${{ steps.binary.outputs.path }}\n        --version ${{ env.VERSION }}\n        staged\n\n    - name: Stage PKG\n      id: pkg\n      run: |-\n        mkdir signed\n        pkg_file=\"$(ls staged)\"\n        echo \"path=$pkg_file\" >> \"$GITHUB_OUTPUT\"\n\n    - name: Sign PKG\n      run: >-\n        rcodesign sign -vv\n        --pem-source /tmp/certificate-installer.pem\n        --pem-source /tmp/private-key-installer.pem\n        \"staged/${{ steps.pkg.outputs.path }}\"\n        \"signed/${{ steps.pkg.outputs.path }}\"\n\n    - name: Notarize PKG\n      run: >-\n        rcodesign notary-submit -vv\n        --max-wait-seconds ${{ env.NOTARY_WAIT_TIME }}\n        --api-key-path /tmp/app-store-connect.json\n        --staple\n        \"signed/${{ steps.pkg.outputs.path }}\"\n\n    - name: Upload binaries\n      uses: actions/upload-artifact@v4\n      with:\n        name: standalone-${{ runner.os }}\n        path: archives/*\n        if-no-files-found: error\n\n    - name: Upload installer\n      uses: actions/upload-artifact@v4\n      with:\n        name: installers-${{ runner.os }}\n        path: signed/${{ steps.pkg.outputs.path }}\n        if-no-files-found: error\n\n  distributions-dev:\n    name: Build development distributions\n    if: ${{ !startsWith(github.event.ref, 'refs/tags') }}\n    uses: ./.github/workflows/build-distributions.yml\n    # This actually does not need the binary jobs but we want to prioritize\n    # resources for the test jobs therefore this forces these later on\n    needs: binaries\n\n  distributions-release:\n    name: Build release distributions\n    needs:\n    - python-artifacts\n    - publish-pypi\n    if: startsWith(github.event.ref, 'refs/tags')\n    uses: ./.github/workflows/build-distributions.yml\n    with:\n      version: ${{ needs.python-artifacts.outputs.version }}\n\n  publish-release:\n    name: Publish distributions\n    if: startsWith(github.event.ref, 'refs/tags')\n    needs:\n    - binaries\n    - windows-packaging\n    - macos-packaging\n    - distributions-release\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: write\n      id-token: write\n\n    steps:\n    - name: Download distributions\n      uses: actions/download-artifact@v4\n      with:\n        pattern: distribution-*\n        path: distributions\n        merge-multiple: true\n\n    - name: Download binaries\n      uses: actions/download-artifact@v4\n      with:\n        pattern: standalone-*\n        path: archives\n        merge-multiple: true\n\n    - name: Download installers\n      uses: actions/download-artifact@v4\n      with:\n        pattern: installers-*\n        path: installers\n        merge-multiple: true\n\n    - name: Add assets to draft release\n      uses: softprops/action-gh-release@v2\n      with:\n        files: |-\n          archives/*\n          distributions/*\n          installers/*\n"
  },
  {
    "path": ".github/workflows/build-hatchling.yml",
    "content": "name: build hatchling\n\non:\n  push:\n    tags:\n    - hatchling-v*\n\nenv:\n  PYTHON_VERSION: \"3.12\"\n\njobs:\n  build:\n    name: Build wheels and source distribution\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ env.PYTHON_VERSION }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ env.PYTHON_VERSION }}\n\n    - name: Install UV\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install build dependencies\n      run: uv pip install --system --upgrade build\n\n    - name: Build source distribution\n      run: python -m build backend\n\n    - uses: actions/upload-artifact@v4\n      with:\n        name: artifacts\n        path: backend/dist\n        if-no-files-found: error\n\n  publish:\n    name: Publish release\n    needs:\n    - build\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: write\n      id-token: write\n\n    steps:\n    - uses: actions/download-artifact@v4\n      with:\n        name: artifacts\n        path: dist\n\n    - name: Push build artifacts to PyPI\n      uses: pypa/gh-action-pypi-publish@v1.12.3\n      with:\n        skip-existing: true\n\n    - name: Add assets to draft release\n      uses: softprops/action-gh-release@v2\n      with:\n        files: dist/*\n"
  },
  {
    "path": ".github/workflows/cli.yml",
    "content": "name: CLI experience\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n    branches:\n    - master\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  STABLE_PYTHON_VERSION: '3.12'\n  HYPERFINE_VERSION: '1.18.0'\n\njobs:\n  response-time:\n    name: CLI responsiveness with latest Python\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ env.STABLE_PYTHON_VERSION }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ env.STABLE_PYTHON_VERSION }}\n\n    - name: Install UV\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install hyperfine\n      uses: taiki-e/install-action@v2\n      with:\n        tool: hyperfine@${{ env.HYPERFINE_VERSION }}\n\n    - name: Install other tools\n      run: uv pip install --system --upgrade flit poetry pipenv\n\n    - name: Install ourself\n      run: |\n        uv pip install --system .\n\n    - name: Benchmark\n      run: |\n        hyperfine -m 100 --warmup 10 -i pipenv\n        hyperfine -m 100 --warmup 10 poetry\n        hyperfine -m 100 --warmup 10 -i flit\n        hyperfine -m 100 --warmup 10 hatch\n"
  },
  {
    "path": ".github/workflows/docs-dev.yml",
    "content": "name: dev docs\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n    branches:\n    - master\n\nconcurrency:\n  group: docs-deploy\n\nenv:\n  FORCE_COLOR: \"1\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n      with:\n        # Fetch all history for applying timestamps to every page\n        fetch-depth: 0\n\n    - name: Set up Python\n      uses: actions/setup-python@v5\n      with:\n        python-version: '3.11'\n\n    - name: Validate history\n      run: python scripts/validate_history.py\n\n    - name: Install UV\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install ourself\n      run: |\n        uv pip install --system -e .\n        hatch env create\n\n    - name: Configure Git for GitHub Actions bot\n      run: |\n        git config --local user.name 'github-actions[bot]'\n        git config --local user.email 'github-actions[bot]@users.noreply.github.com'\n\n    - name: Build documentation\n      run: hatch -v run docs:build-check\n      env:\n        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        GH_TOKEN_MKDOCS_MATERIAL_INSIDERS: ${{ secrets.GH_TOKEN_MKDOCS_MATERIAL_INSIDERS }}\n\n    - name: Commit documentation\n      run: hatch -v run docs:ci-build dev\n      env:\n        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        GH_TOKEN_MKDOCS_MATERIAL_INSIDERS: ${{ secrets.GH_TOKEN_MKDOCS_MATERIAL_INSIDERS }}\n\n    - name: Create archive\n      run: git archive -o site.zip gh-pages\n\n    - uses: actions/upload-artifact@v4\n      with:\n        name: documentation\n        path: site.zip\n\n  publish:\n    runs-on: ubuntu-latest\n\n    if: github.event_name == 'push' && github.ref == 'refs/heads/master'\n    needs:\n    - build\n\n    steps:\n    - uses: actions/download-artifact@v4\n      with:\n        name: documentation\n\n    - name: Unpack archive\n      run: python -m zipfile -e site.zip site\n\n    - uses: peaceiris/actions-gh-pages@v4\n      with:\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n        publish_dir: site\n        commit_message: ${{ github.event.head_commit.message }}\n        # Write .nojekyll at the root, see:\n        # https://help.github.com/en/github/working-with-github-pages/about-github-pages#static-site-generators\n        enable_jekyll: false\n        # Only deploy if there were changes\n        allow_empty_commit: false\n"
  },
  {
    "path": ".github/workflows/docs-release.yml",
    "content": "name: release docs\n\non:\n  push:\n    tags:\n    - hatch-v*\n  workflow_dispatch:\n\nconcurrency:\n  group: docs-deploy\n\nenv:\n  FORCE_COLOR: \"1\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n      with:\n        # Fetch all history for applying timestamps to every page\n        fetch-depth: 0\n\n    - name: Set up Python\n      uses: actions/setup-python@v5\n      with:\n        python-version: '3.11'\n\n    - name: Validate history\n      run: python scripts/validate_history.py\n\n    - name: Install UV\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install ourself\n      run: |\n        uv pip install --system -e .\n        hatch env create     \n\n    - name: Display full version\n      run: hatch version\n\n    - name: Set the version of docs to publish\n      run: python scripts/set_release_version.py\n\n    - name: Configure Git for GitHub Actions bot\n      run: |\n        git config --local user.name 'github-actions[bot]'\n        git config --local user.email 'github-actions[bot]@users.noreply.github.com'\n\n    - name: Build documentation\n      run: hatch run docs:build-check\n      env:\n        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        GH_TOKEN_MKDOCS_MATERIAL_INSIDERS: ${{ secrets.GH_TOKEN_MKDOCS_MATERIAL_INSIDERS }}\n\n    - name: Commit documentation\n      run: hatch run docs:ci-build $HATCH_DOCS_VERSION latest\n      env:\n        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        GH_TOKEN_MKDOCS_MATERIAL_INSIDERS: ${{ secrets.GH_TOKEN_MKDOCS_MATERIAL_INSIDERS }}\n\n    - name: Create archive\n      run: git archive -o site.zip gh-pages\n\n    - uses: actions/upload-artifact@v4\n      with:\n        name: documentation\n        path: site.zip\n\n  publish:\n    runs-on: ubuntu-latest\n    needs:\n    - build\n\n    steps:\n    - uses: actions/download-artifact@v4\n      with:\n        name: documentation\n\n    - name: Unpack archive\n      run: python -m zipfile -e site.zip site\n\n    - uses: peaceiris/actions-gh-pages@v4\n      with:\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n        publish_dir: site\n        commit_message: ${{ github.event.head_commit.message }}\n        # Write .nojekyll at the root, see:\n        # https://help.github.com/en/github/working-with-github-pages/about-github-pages#static-site-generators\n        enable_jekyll: false\n        # Only deploy if there were changes\n        allow_empty_commit: false\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n    branches:\n    - master\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  PYTHONUNBUFFERED: \"1\"\n  FORCE_COLOR: \"1\"\n\njobs:\n  run:\n    name: Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\", \"3.14t\"]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v6\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install uv\n      uses: astral-sh/setup-uv@v3\n\n    - name: Install ourself\n      run: |\n        uv pip install --system -e .\n        hatch env create\n\n    - name: Run static analysis\n      run: hatch fmt --check\n\n    - name: Check types\n      run: hatch run types:check\n\n    - name: Run tests\n      run: hatch test --python ${{ matrix.python-version }} --cover-quiet --randomize --parallel --retries 5 --retry-delay 3\n\n    - name: Disambiguate coverage filename\n      run: mv .coverage \".coverage.${{ matrix.os }}.${{ matrix.python-version }}\"\n\n    - name: Upload coverage data\n      uses: actions/upload-artifact@v4\n      with:\n        include-hidden-files: true\n        name: coverage-${{ matrix.os }}-${{ matrix.python-version }}\n        path: .coverage*\n\n  coverage:\n    name: Report coverage\n    runs-on: ubuntu-latest\n    needs:\n    - run\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install Hatch\n      uses: pypa/hatch@install\n\n    - name: Trigger build for auto-generated files\n      run: hatch build --hooks-only\n\n    - name: Download coverage data\n      uses: actions/download-artifact@v4\n      with:\n        pattern: coverage-*\n        merge-multiple: true\n\n    - name: Combine coverage data\n      run: hatch run coverage:combine\n\n    - name: Export coverage reports\n      run: |\n        hatch run coverage:report-xml\n        hatch run coverage:report-uncovered-html\n\n    - name: Upload uncovered HTML report\n      uses: actions/upload-artifact@v4\n      with:\n        name: uncovered-html-report\n        path: htmlcov\n\n    - name: Generate coverage summary\n      run: hatch run coverage:generate-summary\n\n    - name: Write coverage summary report\n      if: github.event_name == 'pull_request'\n      run: hatch run coverage:write-summary-report\n\n    - name: Update coverage pull request comment\n      if: github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork\n      uses: marocchino/sticky-pull-request-comment@v2\n      with:\n        path: coverage-report.md\n\n  downstream:\n    name: Downstream builds with Python ${{ matrix.python-version }}\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install tools\n      run: pip install --upgrade -r backend/tests/downstream/requirements.txt\n\n    - name: Build downstream projects\n      run: python backend/tests/downstream/integrate.py\n\n  # https://github.com/marketplace/actions/alls-green#why\n  check: # This job does nothing and is only used for the branch protection\n    if: always()\n\n    needs:\n    - coverage\n    - downstream\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Decide whether the needed jobs succeeded or failed\n      uses: re-actors/alls-green@release/v1\n      with:\n        jobs: ${{ toJSON(needs) }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Global directories\n__pycache__/\n\n# Global files\n*.py[cod]\n*.dll\n*.so\n*.log\n*.swp\n\n# Root directories\n/.benchmarks/\n/.cache/\n/.env/\n/.idea/\n/.mypy_cache/\n/.pytest_cache/\n/.ruff_cache/\n/.vscode/\n/backend/dist/\n/dist/\n/site/\n\n# Root files\n/.coverage*\n\n# Auto-generated during builds\n/src/hatch/_version.py\n"
  },
  {
    "path": ".linkcheckerrc",
    "content": "# https://linkchecker.github.io/linkchecker/man/linkcheckerrc.html\n[filtering]\nignore=\n  https://docs.astral.sh/ruff/rules/.+\n  https://github.com/pypa/hatch/releases/tag/hatch-v.+\n\n[AnchorCheck]\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2017-present Ofek Lev <oss@ofek.dev>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Hatch\n\n<div align=\"center\">\n\n<img src=\"https://raw.githubusercontent.com/pypa/hatch/master/docs/assets/images/logo.svg\" alt=\"Hatch logo\" width=\"500\" role=\"img\">\n\n| | |\n| --- | --- |\n| CI/CD | [![CI - Test](https://github.com/pypa/hatch/actions/workflows/test.yml/badge.svg)](https://github.com/pypa/hatch/actions/workflows/test.yml) [![CD - Build Hatch](https://github.com/pypa/hatch/actions/workflows/build-hatch.yml/badge.svg)](https://github.com/pypa/hatch/actions/workflows/build-hatch.yml) [![CD - Build Hatchling](https://github.com/pypa/hatch/actions/workflows/build-hatchling.yml/badge.svg)](https://github.com/pypa/hatch/actions/workflows/build-hatchling.yml) |\n| Docs | [![Docs - Release](https://github.com/pypa/hatch/actions/workflows/docs-release.yml/badge.svg)](https://github.com/pypa/hatch/actions/workflows/docs-release.yml) [![Docs - Dev](https://github.com/pypa/hatch/actions/workflows/docs-dev.yml/badge.svg)](https://github.com/pypa/hatch/actions/workflows/docs-dev.yml) |\n| Package | [![PyPI - Version](https://img.shields.io/pypi/v/hatch.svg?logo=pypi&label=PyPI&logoColor=gold)](https://pypi.org/project/hatch/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/hatch.svg?logo=python&label=Python&logoColor=gold)](https://pypi.org/project/hatch/) [![PyPI - Installs](https://img.shields.io/pypi/dm/hatchling.svg?color=blue&label=Installs&logo=pypi&logoColor=gold)](https://pypi.org/project/hatch/) [![Release - Downloads](https://img.shields.io/github/downloads/pypa/hatch/total?label=Downloads)](https://github.com/pypa/hatch/releases) |\n| Meta | [![Hatch project](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pypa/hatch/master/docs/assets/badge/v0.json)](https://github.com/pypa/hatch) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![types - Mypy](https://img.shields.io/badge/types-Mypy-blue.svg)](https://github.com/python/mypy) [![License - MIT](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://spdx.org/licenses/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/ofek?logo=GitHub%20Sponsors&style=social)](https://github.com/sponsors/ofek) |\n\n</div>\n\n-----\n\nHatch is a modern, extensible Python project manager.\n\n## Features\n\n- Standardized [build system](https://hatch.pypa.io/latest/config/build/#build-system) with reproducible builds by default\n- Robust [environment management](https://hatch.pypa.io/latest/environment/) with support for custom scripts and UV\n- Configurable [Python distribution management](https://hatch.pypa.io/latest/tutorials/python/manage/)\n- [Test execution](https://hatch.pypa.io/latest/tutorials/testing/overview/) with known best practices\n- [Static analysis](https://hatch.pypa.io/latest/config/static-analysis/) with sane defaults\n- Built-in Python [script runner](https://hatch.pypa.io/latest/how-to/run/python-scripts/)\n- Easy [publishing](https://hatch.pypa.io/latest/publish/) to PyPI or other indices\n- [Version](https://hatch.pypa.io/latest/version/) management\n- Best practice [project generation](https://hatch.pypa.io/latest/config/project-templates/)\n- Responsive [CLI](https://hatch.pypa.io/latest/cli/about/), ~2-3x [faster](https://github.com/pypa/hatch/actions/workflows/cli.yml) than equivalent tools\n\nSee the [Why Hatch?](https://hatch.pypa.io/latest/why/) page for more information.\n\n## Documentation\n\nThe [documentation](https://hatch.pypa.io/) is made with [Material for MkDocs](https://github.com/squidfunk/mkdocs-material) and is hosted by [GitHub Pages](https://docs.github.com/en/pages).\n\n## License\n\nHatch is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n"
  },
  {
    "path": "backend/LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2021-present Ofek Lev <oss@ofek.dev>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "backend/README.md",
    "content": "# Hatchling\n\n<div align=\"center\">\n\n<img src=\"https://raw.githubusercontent.com/pypa/hatch/master/docs/assets/images/logo.svg\" alt=\"Hatch logo\" width=\"500\" role=\"img\">\n\n| | |\n| --- | --- |\n| Package | [![PyPI - Version](https://img.shields.io/pypi/v/hatchling.svg?logo=pypi&label=PyPI&logoColor=gold)](https://pypi.org/project/hatchling/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/hatchling.svg?color=blue&label=Downloads&logo=pypi&logoColor=gold)](https://pypi.org/project/hatchling/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/hatchling.svg?logo=python&label=Python&logoColor=gold)](https://pypi.org/project/hatchling/) |\n| Meta | [![Hatch project](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pypa/hatch/master/docs/assets/badge/v0.json)](https://github.com/pypa/hatch) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![code style - Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![types - Mypy](https://img.shields.io/badge/types-Mypy-blue.svg)](https://github.com/python/mypy) [![License - MIT](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://spdx.org/licenses/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/ofek?logo=GitHub%20Sponsors&style=social)](https://github.com/sponsors/ofek) |\n\n</div>\n\n-----\n\nThis is the extensible, standards compliant build backend used by [Hatch](https://github.com/pypa/hatch).\n\n## Usage\n\nThe following snippet must be present in your project's `pyproject.toml` file in order to use Hatchling as your build backend:\n\n```toml\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n```\n\nThen a build frontend like [pip](https://github.com/pypa/pip), [build](https://github.com/pypa/build), or Hatch itself can build or install your project automatically:\n\n```console\n# install using pip\npip install /path/to/project\n\n# build\npython -m build /path/to/project\n\n# build with Hatch\nhatch build /path/to/project\n```\n\n## Documentation\n\n- [Project metadata](https://hatch.pypa.io/latest/config/metadata/)\n- [Dependencies](https://hatch.pypa.io/latest/config/dependency/)\n- [Packaging](https://hatch.pypa.io/latest/config/build/)\n"
  },
  {
    "path": "backend/pyproject.toml",
    "content": "[build-system]\nrequires = []\nbuild-backend = 'hatchling.ouroboros'\nbackend-path = ['src']\n\n[project]\nname = \"hatchling\"\ndynamic = [\"version\"]\ndescription = \"Modern, extensible Python build backend\"\nreadme = \"README.md\"\nlicense = \"MIT\"\nlicense-files = [\"LICENSE.txt\"]\nrequires-python = \">=3.10\"\nkeywords = [\n  \"build\",\n  \"hatch\",\n  \"packaging\",\n]\nauthors = [\n  { name = \"Ofek Lev\", email = \"oss@ofek.dev\" },\n]\nclassifiers = [\n  \"Development Status :: 5 - Production/Stable\",\n  \"Intended Audience :: Developers\",\n  \"Natural Language :: English\",\n  \"Operating System :: OS Independent\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: 3.13\",\n  \"Programming Language :: Python :: 3.14\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n  \"Topic :: Software Development :: Build Tools\",\n  \"Topic :: Software Development :: Libraries :: Python Modules\",\n]\ndependencies = [\n  \"packaging>=24.2\",\n  \"pathspec>=0.10.1\",\n  \"pluggy>=1.0.0\",\n  \"tomli>=1.2.2; python_version < '3.11'\",\n  \"trove-classifiers\",\n]\n\n[project.urls]\nHomepage = \"https://hatch.pypa.io/latest/\"\nSponsor = \"https://github.com/sponsors/ofek\"\nHistory = \"https://hatch.pypa.io/dev/history/hatchling/\"\nTracker = \"https://github.com/pypa/hatch/issues\"\nSource = \"https://github.com/pypa/hatch/tree/master/backend\"\n\n[project.scripts]\nhatchling = \"hatchling.cli:hatchling\"\n\n[tool.hatch.version]\npath = \"src/hatchling/__about__.py\"\n"
  },
  {
    "path": "backend/src/hatchling/__about__.py",
    "content": "__version__ = \"1.29.0\"\n"
  },
  {
    "path": "backend/src/hatchling/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/__main__.py",
    "content": "import sys\n\nif __name__ == \"__main__\":\n    from hatchling.cli import hatchling\n\n    sys.exit(hatchling())\n"
  },
  {
    "path": "backend/src/hatchling/bridge/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/bridge/app.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\nfrom typing import Any\n\n\nclass Application:\n    \"\"\"\n    The way output is displayed can be [configured](../config/hatch.md#terminal) by users.\n\n    !!! important\n        Never import this directly; Hatch judiciously decides if a type of plugin requires\n        the capabilities herein and will grant access via an attribute.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.__verbosity = int(os.environ.get(\"HATCH_VERBOSE\", \"0\")) - int(os.environ.get(\"HATCH_QUIET\", \"0\"))\n\n    @property\n    def verbosity(self) -> int:\n        \"\"\"\n        The verbosity level of the application, with 0 as the default.\n        \"\"\"\n        return self.__verbosity\n\n    @staticmethod\n    def display(message: str = \"\", **kwargs: Any) -> None:  # noqa: ARG004\n        # Do not document\n        _display(message, always=True)\n\n    def display_info(self, message: str = \"\", **kwargs: Any) -> None:  # noqa: ARG002\n        \"\"\"\n        Meant to be used for messages conveying basic information.\n        \"\"\"\n        if self.__verbosity >= 0:\n            _display(message)\n\n    def display_waiting(self, message: str = \"\", **kwargs: Any) -> None:  # noqa: ARG002\n        \"\"\"\n        Meant to be used for messages shown before potentially time consuming operations.\n        \"\"\"\n        if self.__verbosity >= 0:\n            _display(message)\n\n    def display_success(self, message: str = \"\", **kwargs: Any) -> None:  # noqa: ARG002\n        \"\"\"\n        Meant to be used for messages indicating some positive outcome.\n        \"\"\"\n        if self.__verbosity >= 0:\n            _display(message)\n\n    def display_warning(self, message: str = \"\", **kwargs: Any) -> None:  # noqa: ARG002\n        \"\"\"\n        Meant to be used for messages conveying important information.\n        \"\"\"\n        if self.__verbosity >= -1:\n            _display(message)\n\n    def display_error(self, message: str = \"\", **kwargs: Any) -> None:  # noqa: ARG002\n        \"\"\"\n        Meant to be used for messages indicating some unrecoverable error.\n        \"\"\"\n        if self.__verbosity >= -2:  # noqa: PLR2004\n            _display(message)\n\n    def display_debug(self, message: str = \"\", level: int = 1, **kwargs: Any) -> None:  # noqa: ARG002\n        \"\"\"\n        Meant to be used for messages that are not useful for most user experiences.\n        The `level` option must be between 1 and 3 (inclusive).\n        \"\"\"\n        if not 1 <= level <= 3:  # noqa: PLR2004\n            error_message = \"Debug output can only have verbosity levels between 1 and 3 (inclusive)\"\n            raise ValueError(error_message)\n\n        if self.__verbosity >= level:\n            _display(message)\n\n    def display_mini_header(self, message: str = \"\", **kwargs: Any) -> None:  # noqa: ARG002\n        if self.__verbosity >= 0:\n            _display(f\"[{message}]\")\n\n    def abort(self, message: str = \"\", code: int = 1, **kwargs: Any) -> None:  # noqa: ARG002\n        \"\"\"\n        Terminate the program with the given return code.\n        \"\"\"\n        if message and self.__verbosity >= -2:  # noqa: PLR2004\n            _display(message)\n\n        sys.exit(code)\n\n    def get_safe_application(self) -> SafeApplication:\n        return SafeApplication(self)\n\n\nclass SafeApplication:\n    def __init__(self, app: Application) -> None:\n        self.abort = app.abort\n        self.verbosity = app.verbosity\n        self.display = app.display\n        self.display_info = app.display_info\n        self.display_error = app.display_error\n        self.display_success = app.display_success\n        self.display_waiting = app.display_waiting\n        self.display_warning = app.display_warning\n        self.display_debug = app.display_debug\n        self.display_mini_header = app.display_mini_header\n\n\ndef _display(message: str, *, always: bool = False) -> None:\n    print(message, file=None if always else sys.stderr)\n"
  },
  {
    "path": "backend/src/hatchling/build.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom typing import Any\n\n__all__ = [\n    \"build_editable\",\n    \"build_sdist\",\n    \"build_wheel\",\n    \"get_requires_for_build_editable\",\n    \"get_requires_for_build_sdist\",\n    \"get_requires_for_build_wheel\",\n]\n__all__ += [\"__all__\"]\n\n\ndef get_requires_for_build_sdist(config_settings: dict[str, Any] | None = None) -> list[str]:  # noqa: ARG001\n    \"\"\"\n    https://peps.python.org/pep-0517/#get-requires-for-build-sdist\n    \"\"\"\n    from hatchling.builders.sdist import SdistBuilder\n\n    builder = SdistBuilder(os.getcwd())\n    return builder.config.dependencies\n\n\ndef build_sdist(sdist_directory: str, config_settings: dict[str, Any] | None = None) -> str:  # noqa: ARG001\n    \"\"\"\n    https://peps.python.org/pep-0517/#build-sdist\n    \"\"\"\n    from hatchling.builders.sdist import SdistBuilder\n\n    builder = SdistBuilder(os.getcwd())\n    return os.path.basename(next(builder.build(directory=sdist_directory, versions=[\"standard\"])))\n\n\ndef get_requires_for_build_wheel(config_settings: dict[str, Any] | None = None) -> list[str]:  # noqa: ARG001\n    \"\"\"\n    https://peps.python.org/pep-0517/#get-requires-for-build-wheel\n    \"\"\"\n    from hatchling.builders.wheel import WheelBuilder\n\n    builder = WheelBuilder(os.getcwd())\n    return builder.config.dependencies\n\n\ndef build_wheel(\n    wheel_directory: str,\n    config_settings: dict[str, Any] | None = None,  # noqa: ARG001\n    metadata_directory: str | None = None,  # noqa: ARG001\n) -> str:\n    \"\"\"\n    https://peps.python.org/pep-0517/#build-wheel\n    \"\"\"\n    from hatchling.builders.wheel import WheelBuilder\n\n    builder = WheelBuilder(os.getcwd())\n    return os.path.basename(next(builder.build(directory=wheel_directory, versions=[\"standard\"])))\n\n\ndef get_requires_for_build_editable(config_settings: dict[str, Any] | None = None) -> list[str]:  # noqa: ARG001\n    \"\"\"\n    https://peps.python.org/pep-0660/#get-requires-for-build-editable\n    \"\"\"\n    from hatchling.builders.constants import EDITABLES_REQUIREMENT\n    from hatchling.builders.wheel import WheelBuilder\n\n    builder = WheelBuilder(os.getcwd())\n    return [*builder.config.dependencies, EDITABLES_REQUIREMENT]\n\n\ndef build_editable(\n    wheel_directory: str,\n    config_settings: dict[str, Any] | None = None,  # noqa: ARG001\n    metadata_directory: str | None = None,  # noqa: ARG001\n) -> str:\n    \"\"\"\n    https://peps.python.org/pep-0660/#build-editable\n    \"\"\"\n    from hatchling.builders.wheel import WheelBuilder\n\n    builder = WheelBuilder(os.getcwd())\n    return os.path.basename(next(builder.build(directory=wheel_directory, versions=[\"editable\"])))\n\n\n# Any builder that has build-time hooks like Hatchling and setuptools cannot technically keep PEP 517's identical\n# metadata promise e.g. C extensions would require different tags in the `WHEEL` file. Therefore, we consider the\n# methods as mostly being for non-frontend tools like tox and dependency updaters. So Hatchling only writes the\n# `METADATA` file to the metadata directory and continues to ignore that directory itself.\n#\n# An issue we encounter by supporting this metadata-only access is that for installations with pip the required\n# dependencies of the project are read at this stage. This means that build hooks that add to the `dependencies`\n# build data or modify the built wheel have no effect on what dependencies are or are not installed.\n#\n# There are legitimate use cases in which this is required, so we only define these when no pip build is detected.\n# See: https://github.com/pypa/pip/blob/22.2.2/src/pip/_internal/operations/build/build_tracker.py#L41-L51\n# Example use case: https://github.com/pypa/hatch/issues/532\nif \"PIP_BUILD_TRACKER\" not in os.environ:\n    __all__ += [\"prepare_metadata_for_build_editable\", \"prepare_metadata_for_build_wheel\"]\n\n    def prepare_metadata_for_build_wheel(\n        metadata_directory: str,\n        config_settings: dict[str, Any] | None = None,  # noqa: ARG001\n    ) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0517/#prepare-metadata-for-build-wheel\n        \"\"\"\n        from hatchling.builders.wheel import WheelBuilder\n\n        builder = WheelBuilder(os.getcwd())\n\n        directory = os.path.join(metadata_directory, f\"{builder.artifact_project_id}.dist-info\")\n        if not os.path.isdir(directory):\n            os.mkdir(directory)\n\n        with open(os.path.join(directory, \"METADATA\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(builder.config.core_metadata_constructor(builder.metadata))\n\n        return os.path.basename(directory)\n\n    def prepare_metadata_for_build_editable(\n        metadata_directory: str,\n        config_settings: dict[str, Any] | None = None,  # noqa: ARG001\n    ) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0660/#prepare-metadata-for-build-editable\n        \"\"\"\n        from hatchling.builders.constants import EDITABLES_REQUIREMENT\n        from hatchling.builders.wheel import WheelBuilder\n\n        builder = WheelBuilder(os.getcwd())\n\n        directory = os.path.join(metadata_directory, f\"{builder.artifact_project_id}.dist-info\")\n        if not os.path.isdir(directory):\n            os.mkdir(directory)\n\n        extra_dependencies = []\n        if not builder.config.dev_mode_dirs and builder.config.dev_mode_exact:\n            extra_dependencies.append(EDITABLES_REQUIREMENT)\n\n        with open(os.path.join(directory, \"METADATA\"), \"w\", encoding=\"utf-8\") as f:\n            f.write(builder.config.core_metadata_constructor(builder.metadata, extra_dependencies=extra_dependencies))\n\n        return os.path.basename(directory)\n"
  },
  {
    "path": "backend/src/hatchling/builders/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/builders/app.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\nfrom hatchling.builders.binary import BinaryBuilder\n\n\nclass AppBuilder(BinaryBuilder):\n    PLUGIN_NAME = \"app\"\n\n    def build_bootstrap(\n        self,\n        directory: str,\n        **build_data: Any,\n    ) -> str:\n        self.app.display_warning(\n            \"The `app` build target is deprecated and will be removed in a future release. \"\n            \"Use the `binary` build target instead.\"\n        )\n        return super().build_bootstrap(directory, **build_data)\n"
  },
  {
    "path": "backend/src/hatchling/builders/binary.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\nfrom typing import TYPE_CHECKING, Any\n\nfrom hatchling.builders.config import BuilderConfig\nfrom hatchling.builders.plugin.interface import BuilderInterface\n\nif TYPE_CHECKING:\n    from collections.abc import Callable\n\n\nclass BinaryBuilderConfig(BuilderConfig):\n    SUPPORTED_VERSIONS = (\"3.12\", \"3.11\", \"3.10\", \"3.9\", \"3.8\", \"3.7\")\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, **kwargs)\n\n        self.__scripts: list[str] | None = None\n        self.__python_version: str | None = None\n        self.__pyapp_version: str | None = None\n\n    @property\n    def scripts(self) -> list[str]:\n        if self.__scripts is None:\n            known_scripts = self.builder.metadata.core.scripts\n            scripts = self.target_config.get(\"scripts\", [])\n\n            if not isinstance(scripts, list):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.scripts` must be an array\"\n                raise TypeError(message)\n\n            for i, script in enumerate(scripts, 1):\n                if not isinstance(script, str):\n                    message = (\n                        f\"Script #{i} of field `tool.hatch.build.targets.{self.plugin_name}.scripts` must be a string\"\n                    )\n                    raise TypeError(message)\n\n                if script not in known_scripts:\n                    message = f\"Unknown script in field `tool.hatch.build.targets.{self.plugin_name}.scripts`: {script}\"\n                    raise ValueError(message)\n\n            self.__scripts = sorted(set(scripts)) if scripts else list(known_scripts)\n\n        return self.__scripts\n\n    @property\n    def python_version(self) -> str:\n        if self.__python_version is None:\n            python_version = self.target_config.get(\"python-version\", \"\")\n\n            if not isinstance(python_version, str):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.python-version` must be a string\"\n                raise TypeError(message)\n\n            if not python_version and \"PYAPP_DISTRIBUTION_SOURCE\" not in os.environ:\n                for supported_version in self.SUPPORTED_VERSIONS:\n                    if self.builder.metadata.core.python_constraint.contains(supported_version):\n                        python_version = supported_version\n                        break\n                else:\n                    message = \"Field `project.requires-python` is incompatible with the known distributions\"\n                    raise ValueError(message)\n\n            self.__python_version = python_version\n\n        return self.__python_version\n\n    @property\n    def pyapp_version(self) -> str:\n        if self.__pyapp_version is None:\n            pyapp_version = self.target_config.get(\"pyapp-version\", \"\")\n\n            if not isinstance(pyapp_version, str):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.pyapp-version` must be a string\"\n                raise TypeError(message)\n\n            self.__pyapp_version = pyapp_version\n\n        return self.__pyapp_version\n\n\nclass BinaryBuilder(BuilderInterface):\n    \"\"\"\n    Build binaries\n    \"\"\"\n\n    PLUGIN_NAME = \"binary\"\n\n    def get_version_api(self) -> dict[str, Callable]:\n        return {\"bootstrap\": self.build_bootstrap}\n\n    def get_default_versions(self) -> list[str]:  # noqa: PLR6301\n        return [\"bootstrap\"]\n\n    def clean(\n        self,\n        directory: str,\n        versions: list[str],  # noqa: ARG002\n    ) -> None:\n        import shutil\n\n        app_dir = os.path.join(directory, self.PLUGIN_NAME)\n        if os.path.isdir(app_dir):\n            shutil.rmtree(app_dir)\n\n    def build_bootstrap(\n        self,\n        directory: str,\n        **build_data: Any,  # noqa: ARG002\n    ) -> str:\n        import shutil\n        import tempfile\n\n        cargo_path = os.environ.get(\"CARGO\", \"\")\n        if not cargo_path:\n            if not shutil.which(\"cargo\"):\n                message = \"Executable `cargo` could not be found on PATH\"\n                raise OSError(message)\n\n            cargo_path = \"cargo\"\n\n        app_dir = os.path.join(directory, self.PLUGIN_NAME)\n        if not os.path.isdir(app_dir):\n            os.makedirs(app_dir)\n\n        on_windows = sys.platform == \"win32\"\n        base_env = dict(os.environ)\n        base_env[\"PYAPP_PROJECT_NAME\"] = self.metadata.name\n        base_env[\"PYAPP_PROJECT_VERSION\"] = self.metadata.version\n\n        if self.config.python_version:\n            base_env[\"PYAPP_PYTHON_VERSION\"] = self.config.python_version\n\n        # https://doc.rust-lang.org/cargo/reference/config.html#buildtarget\n        build_target = os.environ.get(\"CARGO_BUILD_TARGET\", \"\")\n\n        # This will determine whether we install from crates.io or build locally and is currently required for\n        # cross compilation: https://github.com/cross-rs/cross/issues/1215\n        repo_path = os.environ.get(\"PYAPP_REPO\", \"\")\n\n        with tempfile.TemporaryDirectory() as temp_dir:\n            exe_name = \"pyapp.exe\" if on_windows else \"pyapp\"\n            if repo_path:\n                context_dir = repo_path\n                target_dir = os.path.join(temp_dir, \"build\")\n                if build_target:\n                    temp_exe_path = os.path.join(target_dir, build_target, \"release\", exe_name)\n                else:\n                    temp_exe_path = os.path.join(target_dir, \"release\", exe_name)\n                install_command = [cargo_path, \"build\", \"--release\", \"--target-dir\", target_dir]\n            else:\n                context_dir = temp_dir\n                temp_exe_path = os.path.join(temp_dir, \"bin\", exe_name)\n                install_command = [cargo_path, \"install\", \"pyapp\", \"--force\", \"--root\", temp_dir]\n                if self.config.pyapp_version:\n                    install_command.extend([\"--version\", self.config.pyapp_version])\n\n            if self.config.scripts:\n                for script in self.config.scripts:\n                    env = dict(base_env)\n                    env[\"PYAPP_EXEC_SPEC\"] = self.metadata.core.scripts[script]\n\n                    self.cargo_build(install_command, cwd=context_dir, env=env)\n\n                    exe_stem = (\n                        f\"{script}-{self.metadata.version}-{build_target}\"\n                        if build_target\n                        else f\"{script}-{self.metadata.version}\"\n                    )\n                    exe_path = os.path.join(app_dir, f\"{exe_stem}.exe\" if on_windows else exe_stem)\n                    shutil.move(temp_exe_path, exe_path)\n            else:\n                self.cargo_build(install_command, cwd=context_dir, env=base_env)\n\n                exe_stem = (\n                    f\"{self.metadata.name}-{self.metadata.version}-{build_target}\"\n                    if build_target\n                    else f\"{self.metadata.name}-{self.metadata.version}\"\n                )\n                exe_path = os.path.join(app_dir, f\"{exe_stem}.exe\" if on_windows else exe_stem)\n                shutil.move(temp_exe_path, exe_path)\n\n        return app_dir\n\n    def cargo_build(self, *args: Any, **kwargs: Any) -> None:\n        import subprocess\n\n        if self.app.verbosity < 0:\n            kwargs[\"stdout\"] = subprocess.PIPE\n            kwargs[\"stderr\"] = subprocess.STDOUT\n\n        process = subprocess.run(*args, **kwargs)  # noqa: PLW1510\n        if process.returncode:\n            message = f\"Compilation failed (code {process.returncode})\"\n            raise OSError(message)\n\n    @classmethod\n    def get_config_class(cls) -> type[BinaryBuilderConfig]:\n        return BinaryBuilderConfig\n"
  },
  {
    "path": "backend/src/hatchling/builders/config.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom contextlib import contextmanager\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, Any, TypeVar\n\nimport pathspec\n\nfrom hatchling.builders.constants import DEFAULT_BUILD_DIRECTORY, EXCLUDED_DIRECTORIES, BuildEnvVars\nfrom hatchling.builders.utils import normalize_inclusion_map, normalize_relative_directory, normalize_relative_path\nfrom hatchling.metadata.utils import normalize_project_name\nfrom hatchling.utils.fs import locate_file\n\nif TYPE_CHECKING:\n    from collections.abc import Generator\n\n    from hatchling.builders.plugin.interface import BuilderInterface\n\n\nclass BuilderConfig:\n    def __init__(\n        self,\n        builder: BuilderInterface,\n        root: str,\n        plugin_name: str,\n        build_config: dict[str, Any],\n        target_config: dict[str, Any],\n    ) -> None:\n        self.__builder = builder\n        self.__root = root\n        self.__plugin_name = plugin_name\n        self.__build_config = build_config\n        self.__target_config = target_config\n\n        # This is used when the only file selection is based on forced inclusion or build-time artifacts. This\n        # instructs to `exclude` every encountered path without doing pattern matching that matches everything.\n        self.__exclude_all: bool = False\n\n        # Modified at build time\n        self.build_artifact_spec: pathspec.GitIgnoreSpec | None = None\n        self.build_force_include: dict[str, str] = {}\n        self.build_reserved_paths: set[str] = set()\n\n    @property\n    def builder(self) -> BuilderInterface:\n        return self.__builder\n\n    @property\n    def root(self) -> str:\n        return self.__root\n\n    @property\n    def plugin_name(self) -> str:\n        return self.__plugin_name\n\n    @property\n    def build_config(self) -> dict[str, Any]:\n        return self.__build_config\n\n    @property\n    def target_config(self) -> dict[str, Any]:\n        return self.__target_config\n\n    def include_path(self, relative_path: str, *, explicit: bool = False, is_package: bool = True) -> bool:\n        return (\n            self.path_is_build_artifact(relative_path)\n            or self.path_is_artifact(relative_path)\n            or (\n                not (self.only_packages and not is_package)\n                and not self.path_is_excluded(relative_path)\n                and (explicit or self.path_is_included(relative_path))\n            )\n        )\n\n    def path_is_included(self, relative_path: str) -> bool:\n        if self.include_spec is None:\n            return True\n\n        return self.include_spec.match_file(relative_path)\n\n    def path_is_excluded(self, relative_path: str) -> bool:\n        if self.__exclude_all:\n            return True\n\n        if self.exclude_spec is None:\n            return False\n\n        return self.exclude_spec.match_file(relative_path)\n\n    def path_is_artifact(self, relative_path: str) -> bool:\n        if self.artifact_spec is None:\n            return False\n\n        return self.artifact_spec.match_file(relative_path)\n\n    def path_is_build_artifact(self, relative_path: str) -> bool:\n        if self.build_artifact_spec is None:\n            return False\n\n        return self.build_artifact_spec.match_file(relative_path)\n\n    def path_is_reserved(self, relative_path: str) -> bool:\n        return relative_path in self.build_reserved_paths\n\n    def directory_is_excluded(self, name: str, relative_path: str) -> bool:\n        if name in EXCLUDED_DIRECTORIES:\n            return True\n\n        relative_directory = os.path.join(relative_path, name)\n        return (\n            self.path_is_reserved(relative_directory)\n            # The trailing slash is necessary so e.g. `bar/` matches `foo/bar`\n            or (self.skip_excluded_dirs and self.path_is_excluded(f\"{relative_directory}/\"))\n        )\n\n    @cached_property\n    def include_spec(self) -> pathspec.GitIgnoreSpec | None:\n        if \"include\" in self.target_config:\n            include_config = self.target_config\n            include_location = f\"tool.hatch.build.targets.{self.plugin_name}.include\"\n        else:\n            include_config = self.build_config\n            include_location = \"tool.hatch.build.include\"\n\n        all_include_patterns = []\n\n        include_patterns = include_config.get(\"include\", self.default_include())\n        if not isinstance(include_patterns, list):\n            message = f\"Field `{include_location}` must be an array of strings\"\n            raise TypeError(message)\n\n        for i, include_pattern in enumerate(include_patterns, 1):\n            if not isinstance(include_pattern, str):\n                message = f\"Pattern #{i} in field `{include_location}` must be a string\"\n                raise TypeError(message)\n\n            if not include_pattern:\n                message = f\"Pattern #{i} in field `{include_location}` cannot be an empty string\"\n                raise ValueError(message)\n\n            all_include_patterns.append(include_pattern)\n\n        # Matching only at the root requires a forward slash, back slashes do not work. As such,\n        # normalize to forward slashes for consistency.\n        all_include_patterns.extend(f\"/{relative_path.replace(os.sep, '/')}/\" for relative_path in self.packages)\n\n        if all_include_patterns:\n            return pathspec.GitIgnoreSpec.from_lines(all_include_patterns)\n        return None\n\n    @cached_property\n    def exclude_spec(self) -> pathspec.GitIgnoreSpec | None:\n        if \"exclude\" in self.target_config:\n            exclude_config = self.target_config\n            exclude_location = f\"tool.hatch.build.targets.{self.plugin_name}.exclude\"\n        else:\n            exclude_config = self.build_config\n            exclude_location = \"tool.hatch.build.exclude\"\n\n        all_exclude_patterns = self.default_global_exclude()\n\n        if not self.ignore_vcs:\n            all_exclude_patterns.extend(self.load_vcs_exclusion_patterns())\n\n        exclude_patterns = exclude_config.get(\"exclude\", self.default_exclude())\n        if not isinstance(exclude_patterns, list):\n            message = f\"Field `{exclude_location}` must be an array of strings\"\n            raise TypeError(message)\n\n        for i, exclude_pattern in enumerate(exclude_patterns, 1):\n            if not isinstance(exclude_pattern, str):\n                message = f\"Pattern #{i} in field `{exclude_location}` must be a string\"\n                raise TypeError(message)\n\n            if not exclude_pattern:\n                message = f\"Pattern #{i} in field `{exclude_location}` cannot be an empty string\"\n                raise ValueError(message)\n\n            all_exclude_patterns.append(exclude_pattern)\n\n        if all_exclude_patterns:\n            return pathspec.GitIgnoreSpec.from_lines(all_exclude_patterns)\n        return None\n\n    @property\n    def artifact_spec(self) -> pathspec.GitIgnoreSpec | None:\n        if \"artifacts\" in self.target_config:\n            artifact_config = self.target_config\n            artifact_location = f\"tool.hatch.build.targets.{self.plugin_name}.artifacts\"\n        else:\n            artifact_config = self.build_config\n            artifact_location = \"tool.hatch.build.artifacts\"\n\n        all_artifact_patterns = []\n\n        artifact_patterns = artifact_config.get(\"artifacts\", [])\n        if not isinstance(artifact_patterns, list):\n            message = f\"Field `{artifact_location}` must be an array of strings\"\n            raise TypeError(message)\n\n        for i, artifact_pattern in enumerate(artifact_patterns, 1):\n            if not isinstance(artifact_pattern, str):\n                message = f\"Pattern #{i} in field `{artifact_location}` must be a string\"\n                raise TypeError(message)\n\n            if not artifact_pattern:\n                message = f\"Pattern #{i} in field `{artifact_location}` cannot be an empty string\"\n                raise ValueError(message)\n\n            all_artifact_patterns.append(artifact_pattern)\n\n        if all_artifact_patterns:\n            return pathspec.GitIgnoreSpec.from_lines(all_artifact_patterns)\n        return None\n\n    @cached_property\n    def hook_config(self) -> dict[str, Any]:\n        hook_config: dict[str, dict[str, Any]] = {}\n\n        global_hook_config = self.build_config.get(\"hooks\", {})\n        if not isinstance(global_hook_config, dict):\n            message = \"Field `tool.hatch.build.hooks` must be a table\"\n            raise TypeError(message)\n\n        for hook_name, config in global_hook_config.items():\n            if not isinstance(config, dict):\n                message = f\"Field `tool.hatch.build.hooks.{hook_name}` must be a table\"\n                raise TypeError(message)\n\n            hook_config.setdefault(hook_name, config)\n\n        target_hook_config = self.target_config.get(\"hooks\", {})\n        if not isinstance(target_hook_config, dict):\n            message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.hooks` must be a table\"\n            raise TypeError(message)\n\n        for hook_name, config in target_hook_config.items():\n            if not isinstance(config, dict):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.hooks.{hook_name}` must be a table\"\n                raise TypeError(message)\n\n            hook_config[hook_name] = config\n\n        if not env_var_enabled(BuildEnvVars.NO_HOOKS):\n            all_hooks_enabled = env_var_enabled(BuildEnvVars.HOOKS_ENABLE)\n            final_hook_config = {\n                hook_name: config\n                for hook_name, config in hook_config.items()\n                if (\n                    all_hooks_enabled\n                    or config.get(\"enable-by-default\", True)\n                    or env_var_enabled(f\"{BuildEnvVars.HOOK_ENABLE_PREFIX}{hook_name.upper()}\")\n                )\n            }\n        else:\n            final_hook_config = {}\n\n        return final_hook_config\n\n    @cached_property\n    def directory(self) -> str:\n        if \"directory\" in self.target_config:\n            directory = self.target_config[\"directory\"]\n            if not isinstance(directory, str):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.directory` must be a string\"\n                raise TypeError(message)\n        else:\n            directory = self.build_config.get(\"directory\", DEFAULT_BUILD_DIRECTORY)\n            if not isinstance(directory, str):\n                message = \"Field `tool.hatch.build.directory` must be a string\"\n                raise TypeError(message)\n\n        return self.normalize_build_directory(directory)\n\n    @cached_property\n    def skip_excluded_dirs(self) -> bool:\n        if \"skip-excluded-dirs\" in self.target_config:\n            skip_excluded_dirs = self.target_config[\"skip-excluded-dirs\"]\n            if not isinstance(skip_excluded_dirs, bool):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.skip-excluded-dirs` must be a boolean\"\n                raise TypeError(message)\n        else:\n            skip_excluded_dirs = self.build_config.get(\"skip-excluded-dirs\", False)\n            if not isinstance(skip_excluded_dirs, bool):\n                message = \"Field `tool.hatch.build.skip-excluded-dirs` must be a boolean\"\n                raise TypeError(message)\n\n        return skip_excluded_dirs\n\n    @cached_property\n    def ignore_vcs(self) -> bool:\n        if \"ignore-vcs\" in self.target_config:\n            ignore_vcs = self.target_config[\"ignore-vcs\"]\n            if not isinstance(ignore_vcs, bool):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.ignore-vcs` must be a boolean\"\n                raise TypeError(message)\n        else:\n            ignore_vcs = self.build_config.get(\"ignore-vcs\", False)\n            if not isinstance(ignore_vcs, bool):\n                message = \"Field `tool.hatch.build.ignore-vcs` must be a boolean\"\n                raise TypeError(message)\n\n        return ignore_vcs\n\n    @cached_property\n    def require_runtime_dependencies(self) -> bool:\n        if \"require-runtime-dependencies\" in self.target_config:\n            require_runtime_dependencies = self.target_config[\"require-runtime-dependencies\"]\n            if not isinstance(require_runtime_dependencies, bool):\n                message = (\n                    f\"Field `tool.hatch.build.targets.{self.plugin_name}.require-runtime-dependencies` \"\n                    f\"must be a boolean\"\n                )\n                raise TypeError(message)\n        else:\n            require_runtime_dependencies = self.build_config.get(\"require-runtime-dependencies\", False)\n            if not isinstance(require_runtime_dependencies, bool):\n                message = \"Field `tool.hatch.build.require-runtime-dependencies` must be a boolean\"\n                raise TypeError(message)\n\n        return require_runtime_dependencies\n\n    @cached_property\n    def require_runtime_features(self) -> list[str]:\n        if \"require-runtime-features\" in self.target_config:\n            features_config = self.target_config\n            features_location = f\"tool.hatch.build.targets.{self.plugin_name}.require-runtime-features\"\n        else:\n            features_config = self.build_config\n            features_location = \"tool.hatch.build.require-runtime-features\"\n\n        require_runtime_features = features_config.get(\"require-runtime-features\", [])\n        if not isinstance(require_runtime_features, list):\n            message = f\"Field `{features_location}` must be an array\"\n            raise TypeError(message)\n\n        all_features: dict[str, None] = {}\n        for i, raw_feature in enumerate(require_runtime_features, 1):\n            if not isinstance(raw_feature, str):\n                message = f\"Feature #{i} of field `{features_location}` must be a string\"\n                raise TypeError(message)\n\n            if not raw_feature:\n                message = f\"Feature #{i} of field `{features_location}` cannot be an empty string\"\n                raise ValueError(message)\n\n            feature = normalize_project_name(raw_feature)\n            if feature not in self.builder.metadata.core.optional_dependencies:\n                message = (\n                    f\"Feature `{feature}` of field `{features_location}` is not defined in \"\n                    f\"field `project.optional-dependencies`\"\n                )\n                raise ValueError(message)\n\n            all_features[feature] = None\n\n        return list(all_features)\n\n    @cached_property\n    def only_packages(self) -> bool:\n        \"\"\"\n        Whether or not the target should ignore non-artifact files that do not reside within a Python package.\n        \"\"\"\n        if \"only-packages\" in self.target_config:\n            only_packages = self.target_config[\"only-packages\"]\n            if not isinstance(only_packages, bool):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.only-packages` must be a boolean\"\n                raise TypeError(message)\n        else:\n            only_packages = self.build_config.get(\"only-packages\", False)\n            if not isinstance(only_packages, bool):\n                message = \"Field `tool.hatch.build.only-packages` must be a boolean\"\n                raise TypeError(message)\n\n        return only_packages\n\n    @cached_property\n    def reproducible(self) -> bool:\n        \"\"\"\n        Whether or not the target should be built in a reproducible manner, defaulting to true.\n        \"\"\"\n        if \"reproducible\" in self.target_config:\n            reproducible = self.target_config[\"reproducible\"]\n            if not isinstance(reproducible, bool):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.reproducible` must be a boolean\"\n                raise TypeError(message)\n        else:\n            reproducible = self.build_config.get(\"reproducible\", True)\n            if not isinstance(reproducible, bool):\n                message = \"Field `tool.hatch.build.reproducible` must be a boolean\"\n                raise TypeError(message)\n\n        return reproducible\n\n    @cached_property\n    def dev_mode_dirs(self) -> list[str]:\n        \"\"\"\n        Directories which must be added to Python's search path in\n        [dev mode](../config/environment/overview.md#dev-mode).\n        \"\"\"\n        if \"dev-mode-dirs\" in self.target_config:\n            dev_mode_dirs_config = self.target_config\n            dev_mode_dirs_location = f\"tool.hatch.build.targets.{self.plugin_name}.dev-mode-dirs\"\n        else:\n            dev_mode_dirs_config = self.build_config\n            dev_mode_dirs_location = \"tool.hatch.build.dev-mode-dirs\"\n\n        all_dev_mode_dirs = []\n\n        dev_mode_dirs = dev_mode_dirs_config.get(\"dev-mode-dirs\", [])\n        if not isinstance(dev_mode_dirs, list):\n            message = f\"Field `{dev_mode_dirs_location}` must be an array of strings\"\n            raise TypeError(message)\n\n        for i, dev_mode_dir in enumerate(dev_mode_dirs, 1):\n            if not isinstance(dev_mode_dir, str):\n                message = f\"Directory #{i} in field `{dev_mode_dirs_location}` must be a string\"\n                raise TypeError(message)\n\n            if not dev_mode_dir:\n                message = f\"Directory #{i} in field `{dev_mode_dirs_location}` cannot be an empty string\"\n                raise ValueError(message)\n\n            all_dev_mode_dirs.append(dev_mode_dir)\n\n        return all_dev_mode_dirs\n\n    @cached_property\n    def dev_mode_exact(self) -> bool:\n        if \"dev-mode-exact\" in self.target_config:\n            dev_mode_exact = self.target_config[\"dev-mode-exact\"]\n            if not isinstance(dev_mode_exact, bool):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.dev-mode-exact` must be a boolean\"\n                raise TypeError(message)\n        else:\n            dev_mode_exact = self.build_config.get(\"dev-mode-exact\", False)\n            if not isinstance(dev_mode_exact, bool):\n                message = \"Field `tool.hatch.build.dev-mode-exact` must be a boolean\"\n                raise TypeError(message)\n\n        return dev_mode_exact\n\n    @cached_property\n    def versions(self) -> list[str]:\n        # Used as an ordered set\n        all_versions: dict[str, None] = {}\n\n        versions = self.target_config.get(\"versions\", [])\n        if not isinstance(versions, list):\n            message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.versions` must be an array of strings\"\n            raise TypeError(message)\n\n        for i, version in enumerate(versions, 1):\n            if not isinstance(version, str):\n                message = (\n                    f\"Version #{i} in field `tool.hatch.build.targets.{self.plugin_name}.versions` must be a string\"\n                )\n                raise TypeError(message)\n\n            if not version:\n                message = (\n                    f\"Version #{i} in field `tool.hatch.build.targets.{self.plugin_name}.versions` \"\n                    f\"cannot be an empty string\"\n                )\n                raise ValueError(message)\n\n            all_versions[version] = None\n\n        if not all_versions:\n            default_versions = self.__builder.get_default_versions()\n            for version in default_versions:\n                all_versions[version] = None\n        else:\n            unknown_versions = set(all_versions) - set(self.__builder.get_version_api())\n            if unknown_versions:\n                message = (\n                    f\"Unknown versions in field `tool.hatch.build.targets.{self.plugin_name}.versions`: \"\n                    f\"{', '.join(map(str, sorted(unknown_versions)))}\"\n                )\n                raise ValueError(message)\n\n        return list(all_versions)\n\n    @cached_property\n    def dependencies(self) -> list[str]:\n        # Used as an ordered set\n        dependencies: dict[str, None] = {}\n\n        target_dependencies = self.target_config.get(\"dependencies\", [])\n        if not isinstance(target_dependencies, list):\n            message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.dependencies` must be an array\"\n            raise TypeError(message)\n\n        for i, dependency in enumerate(target_dependencies, 1):\n            if not isinstance(dependency, str):\n                message = (\n                    f\"Dependency #{i} of field `tool.hatch.build.targets.{self.plugin_name}.dependencies` \"\n                    f\"must be a string\"\n                )\n                raise TypeError(message)\n\n            dependencies[dependency] = None\n\n        global_dependencies = self.build_config.get(\"dependencies\", [])\n        if not isinstance(global_dependencies, list):\n            message = \"Field `tool.hatch.build.dependencies` must be an array\"\n            raise TypeError(message)\n\n        for i, dependency in enumerate(global_dependencies, 1):\n            if not isinstance(dependency, str):\n                message = f\"Dependency #{i} of field `tool.hatch.build.dependencies` must be a string\"\n                raise TypeError(message)\n\n            dependencies[dependency] = None\n\n        require_runtime_dependencies = self.require_runtime_dependencies\n        require_runtime_features = dict.fromkeys(self.require_runtime_features)\n        for hook_name, config in self.hook_config.items():\n            hook_require_runtime_dependencies = config.get(\"require-runtime-dependencies\", False)\n            if not isinstance(hook_require_runtime_dependencies, bool):\n                message = f\"Option `require-runtime-dependencies` of build hook `{hook_name}` must be a boolean\"\n                raise TypeError(message)\n\n            if hook_require_runtime_dependencies:\n                require_runtime_dependencies = True\n\n            hook_require_runtime_features = config.get(\"require-runtime-features\", [])\n            if not isinstance(hook_require_runtime_features, list):\n                message = f\"Option `require-runtime-features` of build hook `{hook_name}` must be an array\"\n                raise TypeError(message)\n\n            for i, raw_feature in enumerate(hook_require_runtime_features, 1):\n                if not isinstance(raw_feature, str):\n                    message = (\n                        f\"Feature #{i} of option `require-runtime-features` of build hook `{hook_name}` \"\n                        f\"must be a string\"\n                    )\n                    raise TypeError(message)\n\n                if not raw_feature:\n                    message = (\n                        f\"Feature #{i} of option `require-runtime-features` of build hook `{hook_name}` \"\n                        f\"cannot be an empty string\"\n                    )\n                    raise ValueError(message)\n\n                feature = normalize_project_name(raw_feature)\n                if feature not in self.builder.metadata.core.optional_dependencies:\n                    message = (\n                        f\"Feature `{feature}` of option `require-runtime-features` of build hook `{hook_name}` \"\n                        f\"is not defined in field `project.optional-dependencies`\"\n                    )\n                    raise ValueError(message)\n\n                require_runtime_features[feature] = None\n\n            hook_dependencies = config.get(\"dependencies\", [])\n            if not isinstance(hook_dependencies, list):\n                message = f\"Option `dependencies` of build hook `{hook_name}` must be an array\"\n                raise TypeError(message)\n\n            for i, dependency in enumerate(hook_dependencies, 1):\n                if not isinstance(dependency, str):\n                    message = f\"Dependency #{i} of option `dependencies` of build hook `{hook_name}` must be a string\"\n                    raise TypeError(message)\n\n                dependencies[dependency] = None\n\n        if require_runtime_dependencies:\n            for dependency in self.builder.metadata.core.dependencies:\n                dependencies[dependency] = None\n\n        if require_runtime_features:\n            for feature in require_runtime_features:\n                for dependency in self.builder.metadata.core.optional_dependencies[feature]:\n                    dependencies[dependency] = None\n\n        for dependency in self.dynamic_dependencies:\n            dependencies[dependency] = None\n\n        return list(dependencies)\n\n    @cached_property\n    def dynamic_dependencies(self) -> list[str]:\n        dependencies = []\n        for hook_name, config in self.hook_config.items():\n            build_hook_cls = self.builder.plugin_manager.build_hook.get(hook_name)\n            if build_hook_cls is None:\n                continue\n\n            # Hook exists but dynamic dependencies are not imported lazily.\n            # This happens for example when using the `custom` build hook.\n            try:\n                build_hook = build_hook_cls(\n                    self.root, config, self, self.builder.metadata, \"\", self.builder.PLUGIN_NAME, self.builder.app\n                )\n            except ImportError:\n                continue\n\n            dependencies.extend(build_hook.dependencies())\n\n        return dependencies\n\n    @cached_property\n    def sources(self) -> dict[str, str]:\n        if \"sources\" in self.target_config:\n            sources_config = self.target_config\n            sources_location = f\"tool.hatch.build.targets.{self.plugin_name}.sources\"\n        else:\n            sources_config = self.build_config\n            sources_location = \"tool.hatch.build.sources\"\n\n        sources = {}\n\n        raw_sources = sources_config.get(\"sources\", [])\n        if isinstance(raw_sources, list):\n            for i, source in enumerate(raw_sources, 1):\n                if not isinstance(source, str):\n                    message = f\"Source #{i} in field `{sources_location}` must be a string\"\n                    raise TypeError(message)\n\n                if not source:\n                    message = f\"Source #{i} in field `{sources_location}` cannot be an empty string\"\n                    raise ValueError(message)\n\n                sources[normalize_relative_directory(source)] = \"\"\n        elif isinstance(raw_sources, dict):\n            for source, path in raw_sources.items():\n                if not isinstance(path, str):\n                    message = f\"Path for source `{source}` in field `{sources_location}` must be a string\"\n                    raise TypeError(message)\n\n                normalized_path = normalize_relative_path(path)\n                if normalized_path == \".\":\n                    normalized_path = \"\"\n                else:\n                    normalized_path += os.sep\n\n                sources[normalize_relative_directory(source) if source else source] = normalized_path\n        else:\n            message = f\"Field `{sources_location}` must be a mapping or array of strings\"\n            raise TypeError(message)\n\n        for relative_path in self.packages:\n            source, _package = os.path.split(relative_path)\n            if source and normalize_relative_directory(relative_path) not in sources:\n                sources[normalize_relative_directory(source)] = \"\"\n\n        return dict(sorted(sources.items()))\n\n    @cached_property\n    def packages(self) -> list[str]:\n        if \"packages\" in self.target_config:\n            package_config = self.target_config\n            package_location = f\"tool.hatch.build.targets.{self.plugin_name}.packages\"\n        else:\n            package_config = self.build_config\n            package_location = \"tool.hatch.build.packages\"\n\n        packages = package_config.get(\"packages\", self.default_packages())\n        if not isinstance(packages, list):\n            message = f\"Field `{package_location}` must be an array of strings\"\n            raise TypeError(message)\n\n        for i, package in enumerate(packages, 1):\n            if not isinstance(package, str):\n                message = f\"Package #{i} in field `{package_location}` must be a string\"\n                raise TypeError(message)\n\n            if not package:\n                message = f\"Package #{i} in field `{package_location}` cannot be an empty string\"\n                raise ValueError(message)\n\n        return sorted(normalize_relative_path(package) for package in packages)\n\n    @cached_property\n    def force_include(self) -> dict[str, str]:\n        if \"force-include\" in self.target_config:\n            force_include_config = self.target_config\n            force_include_location = f\"tool.hatch.build.targets.{self.plugin_name}.force-include\"\n        else:\n            force_include_config = self.build_config\n            force_include_location = \"tool.hatch.build.force-include\"\n\n        force_include = force_include_config.get(\"force-include\", {})\n        if not isinstance(force_include, dict):\n            message = f\"Field `{force_include_location}` must be a mapping\"\n            raise TypeError(message)\n\n        for i, (source, relative_path) in enumerate(force_include.items(), 1):\n            if not source:\n                message = f\"Source #{i} in field `{force_include_location}` cannot be an empty string\"\n                raise ValueError(message)\n\n            if not isinstance(relative_path, str):\n                message = f\"Path for source `{source}` in field `{force_include_location}` must be a string\"\n                raise TypeError(message)\n\n            if not relative_path:\n                message = f\"Path for source `{source}` in field `{force_include_location}` cannot be an empty string\"\n                raise ValueError(message)\n\n        return normalize_inclusion_map(force_include, self.root)\n\n    @cached_property\n    def only_include(self) -> dict[str, str]:\n        if \"only-include\" in self.target_config:\n            only_include_config = self.target_config\n            only_include_location = f\"tool.hatch.build.targets.{self.plugin_name}.only-include\"\n        else:\n            only_include_config = self.build_config\n            only_include_location = \"tool.hatch.build.only-include\"\n\n        only_include = only_include_config.get(\"only-include\", self.default_only_include()) or self.packages\n        if not isinstance(only_include, list):\n            message = f\"Field `{only_include_location}` must be an array\"\n            raise TypeError(message)\n\n        inclusion_map = {}\n\n        for i, relative_path in enumerate(only_include, 1):\n            if not isinstance(relative_path, str):\n                message = f\"Path #{i} in field `{only_include_location}` must be a string\"\n                raise TypeError(message)\n\n            normalized_path = normalize_relative_path(relative_path)\n            if not normalized_path or normalized_path.startswith((\"~\", \"..\")):\n                message = f\"Path #{i} in field `{only_include_location}` must be relative: {relative_path}\"\n                raise ValueError(message)\n\n            if normalized_path in inclusion_map:\n                message = f\"Duplicate path in field `{only_include_location}`: {normalized_path}\"\n                raise ValueError(message)\n\n            inclusion_map[normalized_path] = normalized_path\n\n        return normalize_inclusion_map(inclusion_map, self.root)\n\n    def get_distribution_path(self, relative_path: str) -> str:\n        # src/foo/bar.py -> foo/bar.py\n        for source, replacement in self.sources.items():\n            if not source:\n                return replacement + relative_path\n\n            if relative_path.startswith(source):\n                return relative_path.replace(source, replacement, 1)\n\n        return relative_path\n\n    @cached_property\n    def vcs_exclusion_files(self) -> dict[str, list[str]]:\n        exclusion_files: dict[str, list[str]] = {\"git\": [], \"hg\": []}\n\n        local_gitignore = locate_file(self.root, \".gitignore\", boundary=\".git\")\n        if local_gitignore is not None:\n            exclusion_files[\"git\"].append(local_gitignore)\n\n        local_hgignore = locate_file(self.root, \".hgignore\", boundary=\".hg\")\n        if local_hgignore is not None:\n            exclusion_files[\"hg\"].append(local_hgignore)\n\n        return exclusion_files\n\n    def load_vcs_exclusion_patterns(self) -> list[str]:\n        patterns = []\n\n        # https://git-scm.com/docs/gitignore#_pattern_format\n        for exclusion_file in self.vcs_exclusion_files[\"git\"]:\n            with open(exclusion_file, encoding=\"utf-8\") as f:\n                patterns.extend(f.readlines())\n\n        # https://linux.die.net/man/5/hgignore\n        for exclusion_file in self.vcs_exclusion_files[\"hg\"]:\n            with open(exclusion_file, encoding=\"utf-8\") as f:\n                glob_mode = False\n                for line in f:\n                    exact_line = line.strip()\n                    if exact_line == \"syntax: glob\":\n                        glob_mode = True\n                        continue\n\n                    if exact_line.startswith(\"syntax: \"):\n                        glob_mode = False\n                        continue\n\n                    if glob_mode:\n                        patterns.append(line)\n\n        # validate project root is not excluded by vcs\n        exclude_spec = pathspec.GitIgnoreSpec.from_lines(patterns)\n        if exclude_spec.match_file(self.root):\n            return []\n\n        return patterns\n\n    def normalize_build_directory(self, build_directory: str) -> str:\n        if not os.path.isabs(build_directory):\n            build_directory = os.path.join(self.root, build_directory)\n\n        return os.path.normpath(build_directory)\n\n    def default_include(self) -> list:  # noqa: PLR6301\n        return []\n\n    def default_exclude(self) -> list:  # noqa: PLR6301\n        return []\n\n    def default_packages(self) -> list:  # noqa: PLR6301\n        return []\n\n    def default_only_include(self) -> list:  # noqa: PLR6301\n        return []\n\n    def default_global_exclude(self) -> list[str]:  # noqa: PLR6301\n        patterns = [\"*.py[cdo]\", f\"/{DEFAULT_BUILD_DIRECTORY}\"]\n        patterns.sort()\n        return patterns\n\n    def set_exclude_all(self) -> None:\n        self.__exclude_all = True\n\n    def get_force_include(self) -> dict[str, str]:\n        force_include = self.force_include.copy()\n        force_include.update(self.build_force_include)\n        return force_include\n\n    @contextmanager\n    def set_build_data(self, build_data: dict[str, Any]) -> Generator:\n        try:\n            # Include anything the hooks indicate\n            build_artifacts = build_data[\"artifacts\"]\n            if build_artifacts:\n                self.build_artifact_spec = pathspec.GitIgnoreSpec.from_lines(build_artifacts)\n\n            self.build_force_include.update(normalize_inclusion_map(build_data[\"force_include\"], self.root))\n\n            for inclusion_map in (self.force_include, self.build_force_include):\n                for source, target in inclusion_map.items():\n                    # Ignore source\n                    # old/ -> new/\n                    # old.ext -> new.ext\n                    if source.startswith(f\"{self.root}{os.sep}\"):\n                        self.build_reserved_paths.add(self.get_distribution_path(os.path.relpath(source, self.root)))\n                    # Ignore target files only\n                    # ../out.ext -> ../in.ext\n                    elif os.path.isfile(source):\n                        self.build_reserved_paths.add(self.get_distribution_path(target))\n\n            yield\n        finally:\n            self.build_artifact_spec = None\n            self.build_force_include.clear()\n            self.build_reserved_paths.clear()\n\n\ndef env_var_enabled(env_var: str, *, default: bool = False) -> bool:\n    if env_var in os.environ:\n        return os.environ[env_var] in {\"1\", \"true\"}\n\n    return default\n\n\nBuilderConfigBound = TypeVar(\"BuilderConfigBound\", bound=BuilderConfig)\n"
  },
  {
    "path": "backend/src/hatchling/builders/constants.py",
    "content": "DEFAULT_BUILD_DIRECTORY = \"dist\"\n\nEXCLUDED_DIRECTORIES = frozenset((\n    # Python bytecode\n    \"__pycache__\",\n    # Single virtual environment\n    \".venv\",\n    # Git\n    \".git\",\n    # Mercurial\n    \".hg\",\n    # Hatch\n    \".hatch\",\n    # tox\n    \".tox\",\n    # nox\n    \".nox\",\n    # Ruff\n    \".ruff_cache\",\n    # pytest\n    \".pytest_cache\",\n    # Mypy\n    \".mypy_cache\",\n    # pixi\n    \".pixi\",\n))\nEXCLUDED_FILES = frozenset((\n    # https://en.wikipedia.org/wiki/.DS_Store\n    \".DS_Store\",\n))\n\n\nclass BuildEnvVars:\n    LOCATION = \"HATCH_BUILD_LOCATION\"\n    HOOKS_ONLY = \"HATCH_BUILD_HOOKS_ONLY\"\n    NO_HOOKS = \"HATCH_BUILD_NO_HOOKS\"\n    HOOKS_ENABLE = \"HATCH_BUILD_HOOKS_ENABLE\"\n    HOOK_ENABLE_PREFIX = \"HATCH_BUILD_HOOK_ENABLE_\"\n    CLEAN = \"HATCH_BUILD_CLEAN\"\n    CLEAN_HOOKS_AFTER = \"HATCH_BUILD_CLEAN_HOOKS_AFTER\"\n\n\nEDITABLES_REQUIREMENT = \"editables~=0.3\"\n"
  },
  {
    "path": "backend/src/hatchling/builders/custom.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom typing import TYPE_CHECKING, Any, Generic\n\nfrom hatchling.builders.plugin.interface import BuilderInterface\nfrom hatchling.metadata.core import ProjectMetadata\nfrom hatchling.plugin.manager import PluginManagerBound\nfrom hatchling.plugin.utils import load_plugin_from_script\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\nif TYPE_CHECKING:\n    from hatchling.bridge.app import Application\n\n\nclass CustomBuilder(Generic[PluginManagerBound]):\n    PLUGIN_NAME = \"custom\"\n\n    def __new__(  # type: ignore[misc]\n        cls,\n        root: str,\n        plugin_manager: PluginManagerBound | None = None,\n        config: dict[str, Any] | None = None,\n        metadata: ProjectMetadata | None = None,\n        app: Application | None = None,\n    ) -> BuilderInterface:\n        project_metadata = ProjectMetadata(root, plugin_manager, config)\n\n        target_config = project_metadata.hatch.build_targets.get(cls.PLUGIN_NAME, {})\n        if not isinstance(target_config, dict):\n            message = f\"Field `tool.hatch.build.targets.{cls.PLUGIN_NAME}` must be a table\"\n            raise TypeError(message)\n\n        build_script = target_config.get(\"path\", DEFAULT_BUILD_SCRIPT)\n        if not isinstance(build_script, str):\n            message = f\"Option `path` for builder `{cls.PLUGIN_NAME}` must be a string\"\n            raise TypeError(message)\n\n        if not build_script:\n            message = f\"Option `path` for builder `{cls.PLUGIN_NAME}` must not be empty if defined\"\n            raise ValueError(message)\n\n        path = os.path.normpath(os.path.join(root, build_script))\n        if not os.path.isfile(path):\n            message = f\"Build script does not exist: {build_script}\"\n            raise OSError(message)\n\n        hook_class = load_plugin_from_script(path, build_script, BuilderInterface, \"builder\")  # type: ignore[type-abstract]\n        hook = hook_class(root, plugin_manager=plugin_manager, config=config, metadata=metadata, app=app)\n\n        # Always keep the name to avoid confusion\n        hook.PLUGIN_NAME = cls.PLUGIN_NAME\n\n        return hook\n"
  },
  {
    "path": "backend/src/hatchling/builders/hooks/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/builders/hooks/custom.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom typing import Any\n\nfrom hatchling.builders.hooks.plugin.interface import BuildHookInterface\nfrom hatchling.plugin.utils import load_plugin_from_script\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\n\nclass CustomBuildHook:\n    PLUGIN_NAME = \"custom\"\n\n    def __new__(  # type: ignore[misc]\n        cls,\n        root: str,\n        config: dict[str, Any],\n        *args: Any,\n        **kwargs: Any,\n    ) -> BuildHookInterface:\n        build_script = config.get(\"path\", DEFAULT_BUILD_SCRIPT)\n        if not isinstance(build_script, str):\n            message = f\"Option `path` for build hook `{cls.PLUGIN_NAME}` must be a string\"\n            raise TypeError(message)\n\n        if not build_script:\n            message = f\"Option `path` for build hook `{cls.PLUGIN_NAME}` must not be empty if defined\"\n            raise ValueError(message)\n\n        path = os.path.normpath(os.path.join(root, build_script))\n        if not os.path.isfile(path):\n            message = f\"Build script does not exist: {build_script}\"\n            raise OSError(message)\n\n        hook_class = load_plugin_from_script(path, build_script, BuildHookInterface, \"build_hook\")\n        hook = hook_class(root, config, *args, **kwargs)\n\n        # Always keep the name to avoid confusion\n        hook.PLUGIN_NAME = cls.PLUGIN_NAME\n\n        return hook\n"
  },
  {
    "path": "backend/src/hatchling/builders/hooks/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/builders/hooks/plugin/hooks.py",
    "content": "from __future__ import annotations\n\nimport typing\n\nfrom hatchling.builders.hooks.custom import CustomBuildHook\nfrom hatchling.builders.hooks.version import VersionBuildHook\nfrom hatchling.plugin import hookimpl\n\nif typing.TYPE_CHECKING:\n    from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n\n@hookimpl\ndef hatch_register_build_hook() -> list[type[BuildHookInterface]]:\n    return [CustomBuildHook, VersionBuildHook]\n"
  },
  {
    "path": "backend/src/hatchling/builders/hooks/plugin/interface.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, Generic, cast\n\nfrom hatchling.builders.config import BuilderConfigBound\n\nif TYPE_CHECKING:\n    from hatchling.bridge.app import Application\n    from hatchling.metadata.core import ProjectMetadata\n\n\nclass BuildHookInterface(Generic[BuilderConfigBound]):  # no cov\n    \"\"\"\n    Example usage:\n\n    ```python tab=\"plugin.py\"\n    from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n\n    class SpecialBuildHook(BuildHookInterface):\n        PLUGIN_NAME = \"special\"\n        ...\n    ```\n\n    ```python tab=\"hooks.py\"\n    from hatchling.plugin import hookimpl\n\n    from .plugin import SpecialBuildHook\n\n\n    @hookimpl\n    def hatch_register_build_hook():\n        return SpecialBuildHook\n    ```\n    \"\"\"\n\n    PLUGIN_NAME = \"\"\n    \"\"\"The name used for selection.\"\"\"\n\n    def __init__(\n        self,\n        root: str,\n        config: dict[str, Any],\n        build_config: BuilderConfigBound,\n        metadata: ProjectMetadata,\n        directory: str,\n        target_name: str,\n        app: Application | None = None,\n    ) -> None:\n        self.__root = root\n        self.__config = config\n        self.__build_config = build_config\n        self.__metadata = metadata\n        self.__directory = directory\n        self.__target_name = target_name\n        self.__app = app\n\n    @property\n    def app(self) -> Application:\n        \"\"\"\n        An instance of [Application](../utilities.md#hatchling.bridge.app.Application).\n        \"\"\"\n        if self.__app is None:\n            from hatchling.bridge.app import Application\n\n            self.__app = cast(Application, Application().get_safe_application())\n\n        return self.__app\n\n    @property\n    def root(self) -> str:\n        \"\"\"\n        The root of the project tree.\n        \"\"\"\n        return self.__root\n\n    @property\n    def config(self) -> dict[str, Any]:\n        \"\"\"\n        The cumulative hook configuration.\n\n        ```toml config-example\n        [tool.hatch.build.hooks.<PLUGIN_NAME>]\n        [tool.hatch.build.targets.<TARGET_NAME>.hooks.<PLUGIN_NAME>]\n        ```\n        \"\"\"\n        return self.__config\n\n    @property\n    def metadata(self) -> ProjectMetadata:\n        # Undocumented for now\n        return self.__metadata\n\n    @property\n    def build_config(self) -> BuilderConfigBound:\n        \"\"\"\n        An instance of [BuilderConfig](../utilities.md#hatchling.builders.config.BuilderConfig).\n        \"\"\"\n        return self.__build_config\n\n    @property\n    def directory(self) -> str:\n        \"\"\"\n        The build directory.\n        \"\"\"\n        return self.__directory\n\n    @property\n    def target_name(self) -> str:\n        \"\"\"\n        The plugin name of the build target.\n        \"\"\"\n        return self.__target_name\n\n    def dependencies(self) -> list[str]:  # noqa: PLR6301\n        \"\"\"\n        A list of extra [dependencies](../../config/dependency.md) that must be installed\n        prior to builds.\n\n        !!! warning\n            - For this to have any effect the hook dependency itself cannot be dynamic and\n                must always be defined in `build-system.requires`.\n            - As the hook must be imported to call this method, imports that require these\n                dependencies must be evaluated lazily.\n        \"\"\"\n        return []\n\n    def clean(self, versions: list[str]) -> None:\n        \"\"\"\n        This occurs before the build process if the `-c`/`--clean` flag was passed to\n        the [`build`](../../cli/reference.md#hatch-build) command, or when invoking\n        the [`clean`](../../cli/reference.md#hatch-clean) command.\n        \"\"\"\n\n    def initialize(self, version: str, build_data: dict[str, Any]) -> None:\n        \"\"\"\n        This occurs immediately before each build.\n\n        Any modifications to the build data will be seen by the build target.\n        \"\"\"\n\n    def finalize(self, version: str, build_data: dict[str, Any], artifact_path: str) -> None:\n        \"\"\"\n        This occurs immediately after each build and will not run if the `--hooks-only` flag\n        was passed to the [`build`](../../cli/reference.md#hatch-build) command.\n\n        The build data will reflect any modifications done by the target during the build.\n        \"\"\"\n"
  },
  {
    "path": "backend/src/hatchling/builders/hooks/version.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\nfrom hatchling.builders.hooks.plugin.interface import BuildHookInterface\nfrom hatchling.version.core import VersionFile\n\n\nclass VersionBuildHook(BuildHookInterface):\n    PLUGIN_NAME = \"version\"\n\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, **kwargs)\n\n        self.__config_path: str | None = None\n        self.__config_template: str | None = None\n        self.__config_pattern: str | bool | None = None\n\n    @property\n    def config_path(self) -> str:\n        if self.__config_path is None:\n            path = self.config.get(\"path\", \"\")\n            if not isinstance(path, str):\n                message = f\"Option `path` for build hook `{self.PLUGIN_NAME}` must be a string\"\n                raise TypeError(message)\n\n            if not path:\n                message = f\"Option `path` for build hook `{self.PLUGIN_NAME}` is required\"\n                raise ValueError(message)\n\n            self.__config_path = path\n\n        return self.__config_path\n\n    @property\n    def config_template(self) -> str:\n        if self.__config_template is None:\n            template = self.config.get(\"template\", \"\")\n            if not isinstance(template, str):\n                message = f\"Option `template` for build hook `{self.PLUGIN_NAME}` must be a string\"\n                raise TypeError(message)\n\n            self.__config_template = template\n\n        return self.__config_template\n\n    @property\n    def config_pattern(self) -> str | bool:\n        if self.__config_pattern is None:\n            pattern = self.config.get(\"pattern\", \"\")\n            if not isinstance(pattern, (str, bool)):\n                message = f\"Option `pattern` for build hook `{self.PLUGIN_NAME}` must be a string or a boolean\"\n                raise TypeError(message)\n\n            self.__config_pattern = pattern\n\n        return self.__config_pattern\n\n    def initialize(\n        self,\n        version: str,  # noqa: ARG002\n        build_data: dict[str, Any],\n    ) -> None:\n        version_file = VersionFile(self.root, self.config_path)\n        if self.config_pattern:\n            version_file.read(pattern=self.config_pattern)\n            version_file.set_version(self.metadata.version)\n        else:\n            version_file.write(self.metadata.version, self.config_template)\n\n        build_data[\"artifacts\"].append(f\"/{self.config_path}\")\n"
  },
  {
    "path": "backend/src/hatchling/builders/macos.py",
    "content": "from __future__ import annotations\n\nimport os\nimport platform\nimport re\n\n__all__ = [\"process_macos_plat_tag\"]\n\n\ndef process_macos_plat_tag(plat: str, /, *, compat: bool) -> str:\n    \"\"\"\n    Process the macOS platform tag. This will normalize the macOS version to\n    10.16 if compat=True. If the MACOSX_DEPLOYMENT_TARGET environment variable\n    is set, then it will be used instead for the target version.  If archflags\n    is set, then the archs will be respected, including a universal build.\n    \"\"\"\n    # Default to a native build\n    current_arch = platform.machine()\n    arm = current_arch == \"arm64\"\n\n    # Look for cross-compiles\n    archflags = os.environ.get(\"ARCHFLAGS\", \"\")\n    if archflags and (archs := re.findall(r\"-arch (\\S+)\", archflags)):\n        new_arch = \"universal2\" if set(archs) == {\"x86_64\", \"arm64\"} else archs[0]\n        arm = archs == [\"arm64\"]\n        plat = f\"{plat[: plat.rfind(current_arch)]}{new_arch}\"\n\n    # Process macOS version\n    if sdk_match := re.search(r\"macosx_(\\d+_\\d+)\", plat):\n        macos_version = sdk_match.group(1)\n        target = os.environ.get(\"MACOSX_DEPLOYMENT_TARGET\", None)\n\n        try:\n            new_version = normalize_macos_version(target or macos_version, arm=arm, compat=compat)\n        except ValueError:\n            new_version = normalize_macos_version(macos_version, arm=arm, compat=compat)\n\n        return plat.replace(macos_version, new_version, 1)\n\n    return plat\n\n\ndef normalize_macos_version(version: str, *, arm: bool, compat: bool) -> str:\n    \"\"\"\n    Set minor version to 0 if major is 11+. Enforces 11+ if arm=True. 11+ is\n    converted to 10.16 if compat=True. Version is always returned in\n    \"major_minor\" format.\n    \"\"\"\n    version = version.replace(\".\", \"_\")\n    if \"_\" not in version:\n        version = f\"{version}_0\"\n    major, minor = (int(d) for d in version.split(\"_\")[:2])\n    major = max(major, 11) if arm else major\n    minor = 0 if major >= 11 else minor  # noqa: PLR2004\n    if compat and major >= 11:  # noqa: PLR2004\n        major = 10\n        minor = 16\n    return f\"{major}_{minor}\"\n"
  },
  {
    "path": "backend/src/hatchling/builders/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/builders/plugin/hooks.py",
    "content": "from __future__ import annotations\n\nimport typing\n\nfrom hatchling.builders.app import AppBuilder\nfrom hatchling.builders.binary import BinaryBuilder\nfrom hatchling.builders.custom import CustomBuilder\nfrom hatchling.builders.sdist import SdistBuilder\nfrom hatchling.builders.wheel import WheelBuilder\nfrom hatchling.plugin import hookimpl\n\nif typing.TYPE_CHECKING:\n    from hatchling.builders.plugin.interface import BuilderInterface\n\n\n@hookimpl\ndef hatch_register_builder() -> list[type[BuilderInterface]]:\n    return [AppBuilder, BinaryBuilder, CustomBuilder, SdistBuilder, WheelBuilder]  # type: ignore[list-item]\n"
  },
  {
    "path": "backend/src/hatchling/builders/plugin/interface.py",
    "content": "from __future__ import annotations\n\nimport os\nimport re\nfrom abc import ABC, abstractmethod\nfrom typing import TYPE_CHECKING, Any, Generic, cast\n\nfrom hatchling.builders.config import BuilderConfig, BuilderConfigBound, env_var_enabled\nfrom hatchling.builders.constants import EXCLUDED_DIRECTORIES, EXCLUDED_FILES, BuildEnvVars\nfrom hatchling.builders.utils import get_relative_path, safe_walk\nfrom hatchling.plugin.manager import PluginManagerBound\n\nif TYPE_CHECKING:\n    from collections.abc import Callable, Generator, Iterable\n\n    from hatchling.bridge.app import Application\n    from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n    from hatchling.metadata.core import ProjectMetadata\n\n\nclass IncludedFile:\n    __slots__ = (\"distribution_path\", \"path\", \"relative_path\")\n\n    def __init__(self, path: str, relative_path: str, distribution_path: str) -> None:\n        self.path = path\n        self.relative_path = relative_path\n        self.distribution_path = distribution_path\n\n\nclass BuilderInterface(ABC, Generic[BuilderConfigBound, PluginManagerBound]):\n    \"\"\"\n    Example usage:\n\n    ```python tab=\"plugin.py\"\n    from hatchling.builders.plugin.interface import BuilderInterface\n\n\n    class SpecialBuilder(BuilderInterface):\n        PLUGIN_NAME = \"special\"\n        ...\n    ```\n\n    ```python tab=\"hooks.py\"\n    from hatchling.plugin import hookimpl\n\n    from .plugin import SpecialBuilder\n\n\n    @hookimpl\n    def hatch_register_builder():\n        return SpecialBuilder\n    ```\n    \"\"\"\n\n    PLUGIN_NAME = \"\"\n    \"\"\"The name used for selection.\"\"\"\n\n    def __init__(\n        self,\n        root: str,\n        plugin_manager: PluginManagerBound | None = None,\n        config: dict[str, Any] | None = None,\n        metadata: ProjectMetadata | None = None,\n        app: Application | None = None,\n    ) -> None:\n        self.__root = root\n        self.__plugin_manager = cast(PluginManagerBound, plugin_manager)\n        self.__raw_config = config\n        self.__metadata = metadata\n        self.__app = app\n        self.__config = cast(BuilderConfigBound, None)\n        self.__project_config: dict[str, Any] | None = None\n        self.__hatch_config: dict[str, Any] | None = None\n        self.__build_config: dict[str, Any] | None = None\n        self.__build_targets: list[str] | None = None\n        self.__target_config: dict[str, Any] | None = None\n\n        # Metadata\n        self.__project_id: str | None = None\n\n    def build(\n        self,\n        *,\n        directory: str | None = None,\n        versions: list[str] | None = None,\n        hooks_only: bool | None = None,\n        clean: bool | None = None,\n        clean_hooks_after: bool | None = None,\n        clean_only: bool | None = False,\n    ) -> Generator[str, None, None]:\n        # Fail early for invalid project metadata\n        self.metadata.validate_fields()\n\n        if directory is None:\n            directory = (\n                self.config.normalize_build_directory(os.environ[BuildEnvVars.LOCATION])\n                if BuildEnvVars.LOCATION in os.environ\n                else self.config.directory\n            )\n\n        if not os.path.isdir(directory):\n            os.makedirs(directory)\n\n        version_api = self.get_version_api()\n\n        versions = versions or self.config.versions\n        if versions:\n            unknown_versions = set(versions) - set(version_api)\n            if unknown_versions:\n                message = (\n                    f\"Unknown versions for target `{self.PLUGIN_NAME}`: {', '.join(map(str, sorted(unknown_versions)))}\"\n                )\n                raise ValueError(message)\n\n        if hooks_only is None:\n            hooks_only = env_var_enabled(BuildEnvVars.HOOKS_ONLY)\n\n        configured_build_hooks = self.get_build_hooks(directory)\n        build_hooks = list(configured_build_hooks.values())\n\n        if clean_only:\n            clean = True\n        elif clean is None:\n            clean = env_var_enabled(BuildEnvVars.CLEAN)\n        if clean:\n            if not hooks_only:\n                self.clean(directory, versions)\n\n            for build_hook in build_hooks:\n                build_hook.clean(versions)\n\n            if clean_only:\n                return\n\n        if clean_hooks_after is None:\n            clean_hooks_after = env_var_enabled(BuildEnvVars.CLEAN_HOOKS_AFTER)\n\n        for version in versions:\n            self.app.display_debug(f\"Building `{self.PLUGIN_NAME}` version `{version}`\")\n\n            build_data = self.get_default_build_data()\n            self.set_build_data_defaults(build_data)\n\n            # Allow inspection of configured build hooks and the order in which they run\n            build_data[\"build_hooks\"] = tuple(configured_build_hooks)\n\n            # Execute all `initialize` build hooks\n            for build_hook in build_hooks:\n                build_hook.initialize(version, build_data)\n\n            if hooks_only:\n                self.app.display_debug(f\"Only ran build hooks for `{self.PLUGIN_NAME}` version `{version}`\")\n                continue\n\n            # Build the artifact\n            with self.config.set_build_data(build_data):\n                artifact = version_api[version](directory, **build_data)\n\n            # Execute all `finalize` build hooks\n            for build_hook in build_hooks:\n                build_hook.finalize(version, build_data, artifact)\n\n            if clean_hooks_after:\n                for build_hook in build_hooks:\n                    build_hook.clean([version])\n\n            yield artifact\n\n    def recurse_included_files(self) -> Iterable[IncludedFile]:\n        \"\"\"\n        Returns a consistently generated series of file objects for every file that should be distributed. Each file\n        object has three `str` attributes:\n\n        - `path` - the absolute path\n        - `relative_path` - the path relative to the project root; will be an empty string for external files\n        - `distribution_path` - the path to be distributed as\n        \"\"\"\n        yield from self.recurse_selected_project_files()\n        yield from self.recurse_forced_files(self.config.get_force_include())\n\n    def recurse_selected_project_files(self) -> Iterable[IncludedFile]:\n        if self.config.only_include:\n            yield from self.recurse_explicit_files(self.config.only_include)\n        else:\n            yield from self.recurse_project_files()\n\n    def recurse_project_files(self) -> Iterable[IncludedFile]:\n        for root, dirs, files in safe_walk(self.root):\n            relative_path = get_relative_path(root, self.root)\n\n            dirs[:] = sorted(d for d in dirs if not self.config.directory_is_excluded(d, relative_path))\n\n            files.sort()\n            is_package = \"__init__.py\" in files\n            for f in files:\n                if f in EXCLUDED_FILES:\n                    continue\n\n                relative_file_path = os.path.join(relative_path, f)\n                distribution_path = self.config.get_distribution_path(relative_file_path)\n                if self.config.path_is_reserved(distribution_path):\n                    continue\n\n                if self.config.include_path(relative_file_path, is_package=is_package):\n                    yield IncludedFile(\n                        os.path.join(root, f), relative_file_path, self.config.get_distribution_path(relative_file_path)\n                    )\n\n    def recurse_forced_files(self, inclusion_map: dict[str, str]) -> Iterable[IncludedFile]:\n        for source, target_path in inclusion_map.items():\n            external = not source.startswith(self.root)\n            if os.path.isfile(source):\n                yield IncludedFile(\n                    source,\n                    \"\" if external else os.path.relpath(source, self.root),\n                    self.config.get_distribution_path(target_path),\n                )\n            elif os.path.isdir(source):\n                for root, dirs, files in safe_walk(source):\n                    relative_directory = get_relative_path(root, source)\n\n                    dirs[:] = sorted(d for d in dirs if d not in EXCLUDED_DIRECTORIES)\n\n                    files.sort()\n                    for f in files:\n                        if f in EXCLUDED_FILES:\n                            continue\n\n                        relative_file_path = os.path.join(target_path, relative_directory, f)\n                        distribution_path = self.config.get_distribution_path(relative_file_path)\n                        if not self.config.path_is_reserved(distribution_path):\n                            yield IncludedFile(\n                                os.path.join(root, f),\n                                \"\" if external else relative_file_path,\n                                distribution_path,\n                            )\n            else:\n                msg = f\"Forced include not found: {source}\"\n                raise FileNotFoundError(msg)\n\n    def recurse_explicit_files(self, inclusion_map: dict[str, str]) -> Iterable[IncludedFile]:\n        for source, target_path in inclusion_map.items():\n            external = not source.startswith(self.root)\n            if os.path.isfile(source):\n                distribution_path = self.config.get_distribution_path(target_path)\n                if not self.config.path_is_reserved(distribution_path):\n                    yield IncludedFile(\n                        source,\n                        \"\" if external else os.path.relpath(source, self.root),\n                        self.config.get_distribution_path(target_path),\n                    )\n            elif os.path.isdir(source):\n                for root, dirs, files in safe_walk(source):\n                    relative_directory = get_relative_path(root, source)\n\n                    dirs[:] = sorted(d for d in dirs if d not in EXCLUDED_DIRECTORIES)\n\n                    files.sort()\n                    is_package = \"__init__.py\" in files\n                    for f in files:\n                        if f in EXCLUDED_FILES:\n                            continue\n\n                        relative_file_path = os.path.join(target_path, relative_directory, f)\n                        distribution_path = self.config.get_distribution_path(relative_file_path)\n                        if self.config.path_is_reserved(distribution_path):\n                            continue\n\n                        if self.config.include_path(relative_file_path, explicit=True, is_package=is_package):\n                            yield IncludedFile(\n                                os.path.join(root, f), \"\" if external else relative_file_path, distribution_path\n                            )\n\n    @property\n    def root(self) -> str:\n        \"\"\"\n        The root of the project tree.\n        \"\"\"\n        return self.__root\n\n    @property\n    def plugin_manager(self) -> PluginManagerBound:\n        if self.__plugin_manager is None:\n            from hatchling.plugin.manager import PluginManager\n\n            self.__plugin_manager = PluginManager()\n\n        return self.__plugin_manager\n\n    @property\n    def metadata(self) -> ProjectMetadata:\n        if self.__metadata is None:\n            from hatchling.metadata.core import ProjectMetadata\n\n            self.__metadata = ProjectMetadata(self.root, self.plugin_manager, self.__raw_config)\n\n        return self.__metadata\n\n    @property\n    def app(self) -> Application:\n        \"\"\"\n        An instance of [Application](../utilities.md#hatchling.bridge.app.Application).\n        \"\"\"\n        if self.__app is None:\n            from hatchling.bridge.app import Application\n\n            self.__app = cast(Application, Application().get_safe_application())\n\n        return self.__app\n\n    @property\n    def raw_config(self) -> dict[str, Any]:\n        if self.__raw_config is None:\n            self.__raw_config = self.metadata.config\n\n        return self.__raw_config\n\n    @property\n    def project_config(self) -> dict[str, Any]:\n        if self.__project_config is None:\n            self.__project_config = self.metadata.core.config\n\n        return self.__project_config\n\n    @property\n    def hatch_config(self) -> dict[str, Any]:\n        if self.__hatch_config is None:\n            self.__hatch_config = self.metadata.hatch.config\n\n        return self.__hatch_config\n\n    @property\n    def config(self) -> BuilderConfigBound:\n        \"\"\"\n        An instance of [BuilderConfig](../utilities.md#hatchling.builders.config.BuilderConfig).\n        \"\"\"\n        if self.__config is None:\n            self.__config = self.get_config_class()(\n                self, self.root, self.PLUGIN_NAME, self.build_config, self.target_config\n            )\n\n        return self.__config\n\n    @property\n    def build_config(self) -> dict[str, Any]:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.build]\n        ```\n        \"\"\"\n        if self.__build_config is None:\n            self.__build_config = self.metadata.hatch.build_config\n\n        return self.__build_config\n\n    @property\n    def target_config(self) -> dict[str, Any]:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.build.targets.<PLUGIN_NAME>]\n        ```\n        \"\"\"\n        if self.__target_config is None:\n            target_config: dict[str, Any] = self.metadata.hatch.build_targets.get(self.PLUGIN_NAME, {})\n            if not isinstance(target_config, dict):\n                message = f\"Field `tool.hatch.build.targets.{self.PLUGIN_NAME}` must be a table\"\n                raise TypeError(message)\n\n            self.__target_config = target_config\n\n        return self.__target_config\n\n    @property\n    def project_id(self) -> str:\n        if self.__project_id is None:\n            self.__project_id = f\"{self.normalize_file_name_component(self.metadata.core.name)}-{self.metadata.version}\"\n\n        return self.__project_id\n\n    def get_build_hooks(self, directory: str) -> dict[str, BuildHookInterface]:\n        configured_build_hooks = {}\n        for hook_name, config in self.config.hook_config.items():\n            build_hook = self.plugin_manager.build_hook.get(hook_name)\n            if build_hook is None:\n                from hatchling.plugin.exceptions import UnknownPluginError\n\n                message = f\"Unknown build hook: {hook_name}\"\n                raise UnknownPluginError(message)\n\n            configured_build_hooks[hook_name] = build_hook(\n                self.root, config, self.config, self.metadata, directory, self.PLUGIN_NAME, self.app\n            )\n\n        return configured_build_hooks\n\n    @abstractmethod\n    def get_version_api(self) -> dict[str, Callable]:\n        \"\"\"\n        A mapping of `str` versions to a callable that is used for building.\n        Each callable must have the following signature:\n\n        ```python\n        def ...(build_dir: str, build_data: dict) -> str:\n        ```\n\n        The return value must be the absolute path to the built artifact.\n        \"\"\"\n\n    def get_default_versions(self) -> list[str]:\n        \"\"\"\n        A list of versions to build when users do not specify any, defaulting to all versions.\n        \"\"\"\n        return list(self.get_version_api())\n\n    def get_default_build_data(self) -> dict[str, Any]:  # noqa: PLR6301\n        \"\"\"\n        A mapping that can be modified by [build hooks](../build-hook/reference.md) to influence the behavior of builds.\n        \"\"\"\n        return {}\n\n    def set_build_data_defaults(self, build_data: dict[str, Any]) -> None:  # noqa: PLR6301\n        build_data.setdefault(\"artifacts\", [])\n        build_data.setdefault(\"force_include\", {})\n\n    def clean(self, directory: str, versions: list[str]) -> None:\n        \"\"\"\n        Called before builds if the `-c`/`--clean` flag was passed to the\n        [`build`](../../cli/reference.md#hatch-build) command.\n        \"\"\"\n\n    @classmethod\n    def get_config_class(cls) -> type[BuilderConfig]:\n        \"\"\"\n        Must return a subclass of [BuilderConfig](../utilities.md#hatchling.builders.config.BuilderConfig).\n        \"\"\"\n        return BuilderConfig\n\n    @staticmethod\n    def normalize_file_name_component(file_name: str) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0427/#escaping-and-unicode\n        \"\"\"\n        return re.sub(r\"[^\\w\\d.]+\", \"_\", file_name, flags=re.UNICODE)\n"
  },
  {
    "path": "backend/src/hatchling/builders/sdist.py",
    "content": "from __future__ import annotations\n\nimport gzip\nimport os\nimport tarfile\nimport tempfile\nfrom contextlib import closing\nfrom copy import copy\nfrom io import BytesIO\nfrom time import time as get_current_timestamp\nfrom typing import TYPE_CHECKING, Any\n\nfrom hatchling.builders.config import BuilderConfig\nfrom hatchling.builders.plugin.interface import BuilderInterface\nfrom hatchling.builders.utils import (\n    get_reproducible_timestamp,\n    normalize_archive_path,\n    normalize_artifact_permissions,\n    normalize_file_permissions,\n    normalize_relative_path,\n    replace_file,\n)\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION, get_core_metadata_constructors\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE\n\nif TYPE_CHECKING:\n    from collections.abc import Callable\n    from types import TracebackType\n\n\nclass SdistArchive:\n    def __init__(self, name: str, *, reproducible: bool) -> None:\n        \"\"\"\n        https://peps.python.org/pep-0517/#source-distributions\n        \"\"\"\n        self.name = name\n        self.reproducible = reproducible\n        self.timestamp: int | None = get_reproducible_timestamp() if reproducible else None\n\n        raw_fd, self.path = tempfile.mkstemp(suffix=\".tar.gz\")\n        self.fd = os.fdopen(raw_fd, \"w+b\")\n        self.gz = gzip.GzipFile(fileobj=self.fd, mode=\"wb\", mtime=self.timestamp)\n        self.tf = tarfile.TarFile(fileobj=self.gz, mode=\"w\", format=tarfile.PAX_FORMAT)\n        self.gettarinfo = lambda *args, **kwargs: self.normalize_tar_metadata(self.tf.gettarinfo(*args, **kwargs))\n\n    def create_file(self, contents: str | bytes, *relative_paths: str) -> None:\n        if not isinstance(contents, bytes):\n            contents = contents.encode(\"utf-8\")\n        tar_info = tarfile.TarInfo(normalize_archive_path(os.path.join(self.name, *relative_paths)))\n        tar_info.size = len(contents)\n        if self.reproducible and self.timestamp is not None:\n            tar_info.mtime = self.timestamp\n        else:\n            tar_info.mtime = int(get_current_timestamp())\n\n        with closing(BytesIO(contents)) as buffer:\n            self.tf.addfile(tar_info, buffer)\n\n    def normalize_tar_metadata(self, tar_info: tarfile.TarInfo | None) -> tarfile.TarInfo | None:\n        if not self.reproducible or tar_info is None:\n            return tar_info\n\n        tar_info = copy(tar_info)\n        tar_info.uid = 0\n        tar_info.gid = 0\n        tar_info.uname = \"\"\n        tar_info.gname = \"\"\n        tar_info.mode = normalize_file_permissions(tar_info.mode)\n        if self.timestamp is not None:\n            tar_info.mtime = self.timestamp\n\n        return tar_info\n\n    def __getattr__(self, name: str) -> Any:\n        attr = getattr(self.tf, name)\n        setattr(self, name, attr)\n        return attr\n\n    def __enter__(self) -> SdistArchive:  # noqa: PYI034\n        return self\n\n    def __exit__(\n        self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None\n    ) -> None:\n        self.tf.close()\n        self.gz.close()\n        self.fd.close()\n\n\nclass SdistBuilderConfig(BuilderConfig):\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, **kwargs)\n\n        self.__core_metadata_constructor: Callable[..., str] | None = None\n        self.__strict_naming: bool | None = None\n        self.__support_legacy: bool | None = None\n\n    @property\n    def core_metadata_constructor(self) -> Callable[..., str]:\n        if self.__core_metadata_constructor is None:\n            core_metadata_version = self.target_config.get(\"core-metadata-version\", DEFAULT_METADATA_VERSION)\n            if not isinstance(core_metadata_version, str):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.core-metadata-version` must be a string\"\n                raise TypeError(message)\n\n            constructors = get_core_metadata_constructors()\n            if core_metadata_version not in constructors:\n                message = (\n                    f\"Unknown metadata version `{core_metadata_version}` for field \"\n                    f\"`tool.hatch.build.targets.{self.plugin_name}.core-metadata-version`. \"\n                    f\"Available: {', '.join(sorted(constructors))}\"\n                )\n                raise ValueError(message)\n\n            self.__core_metadata_constructor = constructors[core_metadata_version]\n\n        return self.__core_metadata_constructor\n\n    @property\n    def strict_naming(self) -> bool:\n        if self.__strict_naming is None:\n            if \"strict-naming\" in self.target_config:\n                strict_naming = self.target_config[\"strict-naming\"]\n                if not isinstance(strict_naming, bool):\n                    message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.strict-naming` must be a boolean\"\n                    raise TypeError(message)\n            else:\n                strict_naming = self.build_config.get(\"strict-naming\", True)\n                if not isinstance(strict_naming, bool):\n                    message = \"Field `tool.hatch.build.strict-naming` must be a boolean\"\n                    raise TypeError(message)\n\n            self.__strict_naming = strict_naming\n\n        return self.__strict_naming\n\n    @property\n    def support_legacy(self) -> bool:\n        if self.__support_legacy is None:\n            self.__support_legacy = bool(self.target_config.get(\"support-legacy\", False))\n\n        return self.__support_legacy\n\n\nclass SdistBuilder(BuilderInterface):\n    \"\"\"\n    Build an archive of the source files\n    \"\"\"\n\n    PLUGIN_NAME = \"sdist\"\n\n    def get_version_api(self) -> dict[str, Callable]:\n        return {\"standard\": self.build_standard}\n\n    def get_default_versions(self) -> list[str]:  # noqa: PLR6301\n        return [\"standard\"]\n\n    def clean(  # noqa: PLR6301\n        self,\n        directory: str,\n        versions: list[str],  # noqa: ARG002\n    ) -> None:\n        for filename in os.listdir(directory):\n            if filename.endswith(\".tar.gz\"):\n                os.remove(os.path.join(directory, filename))\n\n    def build_standard(self, directory: str, **build_data: Any) -> str:\n        found_packages = set()\n\n        with SdistArchive(self.artifact_project_id, reproducible=self.config.reproducible) as archive:\n            for included_file in self.recurse_included_files():\n                if self.config.support_legacy:\n                    possible_package, file_name = os.path.split(included_file.relative_path)\n                    if file_name == \"__init__.py\":\n                        found_packages.add(possible_package)\n\n                tar_info = archive.gettarinfo(\n                    included_file.path,\n                    arcname=normalize_archive_path(\n                        os.path.join(self.artifact_project_id, included_file.distribution_path)\n                    ),\n                )\n                if tar_info is None:  # no cov\n                    continue\n\n                if tar_info.isfile():\n                    with open(included_file.path, \"rb\") as f:\n                        archive.addfile(tar_info, f)\n                else:  # no cov\n                    # TODO: Investigate if this is necessary (for symlinks, etc.)\n                    archive.addfile(tar_info)\n\n            archive.create_file(\n                self.config.core_metadata_constructor(self.metadata, extra_dependencies=build_data[\"dependencies\"]),\n                \"PKG-INFO\",\n            )\n\n            if self.config.support_legacy:\n                archive.create_file(\n                    self.construct_setup_py_file(sorted(found_packages), extra_dependencies=build_data[\"dependencies\"]),\n                    \"setup.py\",\n                )\n\n        target = os.path.join(directory, f\"{self.artifact_project_id}.tar.gz\")\n\n        replace_file(archive.path, target)\n        normalize_artifact_permissions(target)\n        return target\n\n    @property\n    def artifact_project_id(self) -> str:\n        return (\n            self.project_id\n            if self.config.strict_naming\n            else f\"{self.normalize_file_name_component(self.metadata.core.raw_name)}-{self.metadata.version}\"\n        )\n\n    def construct_setup_py_file(self, packages: list[str], extra_dependencies: tuple[()] = ()) -> str:\n        contents = \"from setuptools import setup\\n\\n\"\n\n        contents += \"setup(\\n\"\n\n        contents += f\"    name={self.metadata.core.name!r},\\n\"\n        contents += f\"    version={self.metadata.version!r},\\n\"\n\n        if self.metadata.core.description:\n            contents += f\"    description={self.metadata.core.description!r},\\n\"\n\n        if self.metadata.core.readme:\n            contents += f\"    long_description={self.metadata.core.readme!r},\\n\"\n\n        authors_data = self.metadata.core.authors_data\n        if authors_data[\"name\"]:\n            contents += f\"    author={', '.join(authors_data['name'])!r},\\n\"\n        if authors_data[\"email\"]:\n            contents += f\"    author_email={', '.join(authors_data['email'])!r},\\n\"\n\n        maintainers_data = self.metadata.core.maintainers_data\n        if maintainers_data[\"name\"]:\n            contents += f\"    maintainer={', '.join(maintainers_data['name'])!r},\\n\"\n        if maintainers_data[\"email\"]:\n            contents += f\"    maintainer_email={', '.join(maintainers_data['email'])!r},\\n\"\n\n        if self.metadata.core.classifiers:\n            contents += \"    classifiers=[\\n\"\n\n            for classifier in self.metadata.core.classifiers:\n                contents += f\"        {classifier!r},\\n\"\n\n            contents += \"    ],\\n\"\n\n        dependencies = list(self.metadata.core.dependencies)\n        dependencies.extend(extra_dependencies)\n        if dependencies:\n            contents += \"    install_requires=[\\n\"\n\n            for raw_specifier in dependencies:\n                specifier = raw_specifier.replace(\"'\", '\"')\n                contents += f\"        {specifier!r},\\n\"\n\n            contents += \"    ],\\n\"\n\n        if self.metadata.core.optional_dependencies:\n            contents += \"    extras_require={\\n\"\n\n            for option, specifiers in self.metadata.core.optional_dependencies.items():\n                if not specifiers:\n                    continue\n\n                contents += f\"        {option!r}: [\\n\"\n\n                for raw_specifier in specifiers:\n                    specifier = raw_specifier.replace(\"'\", '\"')\n                    contents += f\"            {specifier!r},\\n\"\n\n                contents += \"        ],\\n\"\n\n            contents += \"    },\\n\"\n\n        if self.metadata.core.scripts or self.metadata.core.gui_scripts or self.metadata.core.entry_points:\n            contents += \"    entry_points={\\n\"\n\n            if self.metadata.core.scripts:\n                contents += \"        'console_scripts': [\\n\"\n\n                for name, object_ref in self.metadata.core.scripts.items():\n                    contents += f\"            '{name} = {object_ref}',\\n\"\n\n                contents += \"        ],\\n\"\n\n            if self.metadata.core.gui_scripts:\n                contents += \"        'gui_scripts': [\\n\"\n\n                for name, object_ref in self.metadata.core.gui_scripts.items():\n                    contents += f\"            '{name} = {object_ref}',\\n\"\n\n                contents += \"        ],\\n\"\n\n            if self.metadata.core.entry_points:\n                for group, entry_points in self.metadata.core.entry_points.items():\n                    contents += f\"        {group!r}: [\\n\"\n\n                    for name, object_ref in entry_points.items():\n                        contents += f\"            '{name} = {object_ref}',\\n\"\n\n                    contents += \"        ],\\n\"\n\n            contents += \"    },\\n\"\n\n        if packages:\n            src_layout = False\n            contents += \"    packages=[\\n\"\n\n            for package in packages:\n                if package.startswith(f\"src{os.sep}\"):\n                    src_layout = True\n                    contents += f\"        {package.replace(os.sep, '.')[4:]!r},\\n\"\n                else:\n                    contents += f\"        {package.replace(os.sep, '.')!r},\\n\"\n\n            contents += \"    ],\\n\"\n\n            if src_layout:\n                contents += \"    package_dir={'': 'src'},\\n\"\n\n        contents += \")\\n\"\n\n        return contents\n\n    def get_default_build_data(self) -> dict[str, Any]:\n        force_include = {}\n        for filename in [\"pyproject.toml\", DEFAULT_CONFIG_FILE, DEFAULT_BUILD_SCRIPT]:\n            path = os.path.join(self.root, filename)\n            if os.path.exists(path):\n                force_include[path] = filename\n        build_data = {\"force_include\": force_include, \"dependencies\": []}\n\n        for exclusion_files in self.config.vcs_exclusion_files.values():\n            for exclusion_file in exclusion_files:\n                force_include[exclusion_file] = os.path.basename(exclusion_file)\n\n        readme_path = self.metadata.core.readme_path\n        if readme_path:\n            readme_path = normalize_relative_path(readme_path)\n            force_include[os.path.join(self.root, readme_path)] = readme_path\n\n        license_files = self.metadata.core.license_files\n        if license_files:\n            for license_file in license_files:\n                relative_path = normalize_relative_path(license_file)\n                force_include[os.path.join(self.root, relative_path)] = relative_path\n\n        return build_data\n\n    @classmethod\n    def get_config_class(cls) -> type[SdistBuilderConfig]:\n        return SdistBuilderConfig\n"
  },
  {
    "path": "backend/src/hatchling/builders/utils.py",
    "content": "from __future__ import annotations\n\nimport os\nimport shutil\nfrom base64 import urlsafe_b64encode\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from collections.abc import Iterable\n    from zipfile import ZipInfo\n\n\ndef replace_file(src: str, dst: str) -> None:\n    try:\n        os.replace(src, dst)\n    # Happens when on different filesystems like /tmp or caused by layering in containers\n    except OSError:\n        shutil.copy2(src, dst)\n        os.remove(src)\n\n\ndef safe_walk(path: str) -> Iterable[tuple[str, list[str], list[str]]]:\n    seen = set()\n    for root, dirs, files in os.walk(path, followlinks=True):\n        stat = os.stat(root)\n        identifier = stat.st_dev, stat.st_ino\n        if identifier in seen:\n            del dirs[:]\n            continue\n\n        seen.add(identifier)\n        yield root, dirs, files\n\n\ndef get_known_python_major_versions() -> map:\n    return map(str, sorted((2, 3)))\n\n\ndef get_relative_path(path: str, start: str) -> str:\n    relative_path = os.path.relpath(path, start)\n\n    # First iteration of `os.walk`\n    if relative_path == \".\":\n        return \"\"\n\n    return relative_path\n\n\ndef normalize_relative_path(path: str) -> str:\n    return os.path.normpath(path).strip(os.sep)\n\n\ndef normalize_relative_directory(path: str) -> str:\n    return normalize_relative_path(path) + os.sep\n\n\ndef normalize_inclusion_map(inclusion_map: dict[str, str], root: str) -> dict[str, str]:\n    normalized_inclusion_map = {}\n\n    for raw_source, relative_path in inclusion_map.items():\n        source = os.path.expanduser(os.path.normpath(raw_source))\n        if not os.path.isabs(source):\n            source = os.path.abspath(os.path.join(root, source))\n\n        normalized_inclusion_map[source] = normalize_relative_path(relative_path)\n\n    return dict(\n        sorted(\n            normalized_inclusion_map.items(),\n            key=lambda item: (item[1].count(os.sep), item[1], item[0]),\n        )\n    )\n\n\ndef normalize_archive_path(path: str) -> str:\n    if os.sep != \"/\":\n        return path.replace(os.sep, \"/\")\n\n    return path\n\n\ndef format_file_hash(digest: bytes) -> str:\n    # https://peps.python.org/pep-0427/#signed-wheel-files\n    return urlsafe_b64encode(digest).decode(\"ascii\").rstrip(\"=\")\n\n\ndef get_reproducible_timestamp() -> int:\n    \"\"\"\n    Returns an `int` derived from the `SOURCE_DATE_EPOCH` environment variable; see\n    https://reproducible-builds.org/specs/source-date-epoch/.\n\n    The default value will always be: `1580601600`\n    \"\"\"\n    return int(os.environ.get(\"SOURCE_DATE_EPOCH\", \"1580601600\"))\n\n\ndef normalize_file_permissions(st_mode: int) -> int:\n    \"\"\"\n    https://github.com/takluyver/flit/blob/6a2a8c6462e49f584941c667b70a6f48a7b3f9ab/flit_core/flit_core/common.py#L257\n\n    Normalize the permission bits in the st_mode field from stat to 644/755.\n\n    Popular VCSs only track whether a file is executable or not. The exact\n    permissions can vary on systems with different umasks. Normalizing\n    to 644 (non executable) or 755 (executable) makes builds more reproducible.\n    \"\"\"\n    # Set 644 permissions, leaving higher bits of st_mode unchanged\n    new_mode = (st_mode | 0o644) & ~0o133\n    if st_mode & 0o100:  # no cov\n        new_mode |= 0o111  # Executable: 644 -> 755\n    return new_mode\n\n\ndef normalize_artifact_permissions(path: str) -> None:\n    \"\"\"\n    Normalize the permission bits for artifacts\n    \"\"\"\n    file_stat = os.stat(path)\n    new_mode = normalize_file_permissions(file_stat.st_mode)\n    os.chmod(path, new_mode)\n\n\ndef set_zip_info_mode(zip_info: ZipInfo, mode: int = 0o644) -> None:\n    \"\"\"\n    https://github.com/python/cpython/blob/v3.12.3/Lib/zipfile/__init__.py#L574\n    https://github.com/takluyver/flit/commit/3889583719888aef9f28baaa010e698cb7884904\n    \"\"\"\n    zip_info.external_attr = (mode & 0xFFFF) << 16\n"
  },
  {
    "path": "backend/src/hatchling/builders/wheel.py",
    "content": "from __future__ import annotations\n\nimport csv\nimport hashlib\nimport os\nimport stat\nimport sys\nimport tempfile\nimport zipfile\nfrom functools import cached_property\nfrom io import StringIO\nfrom typing import TYPE_CHECKING, Any, NamedTuple, cast\n\nfrom hatchling.__about__ import __version__\nfrom hatchling.builders.config import BuilderConfig\nfrom hatchling.builders.constants import EDITABLES_REQUIREMENT\nfrom hatchling.builders.plugin.interface import BuilderInterface\nfrom hatchling.builders.utils import (\n    format_file_hash,\n    get_known_python_major_versions,\n    get_reproducible_timestamp,\n    normalize_archive_path,\n    normalize_artifact_permissions,\n    normalize_file_permissions,\n    normalize_inclusion_map,\n    replace_file,\n    set_zip_info_mode,\n)\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION, get_core_metadata_constructors\n\nif TYPE_CHECKING:\n    from collections.abc import Callable, Iterable, Sequence\n    from types import TracebackType\n\n    from hatchling.builders.plugin.interface import IncludedFile\n\n\nTIME_TUPLE = tuple[int, int, int, int, int, int]\n\n\nclass FileSelectionOptions(NamedTuple):\n    include: list[str]\n    exclude: list[str]\n    packages: list[str]\n    only_include: list[str]\n\n\nclass RecordFile:\n    def __init__(self) -> None:\n        self.__file_obj = StringIO()\n        self.__writer = csv.writer(self.__file_obj, delimiter=\",\", quotechar='\"', lineterminator=\"\\n\")\n\n    def write(self, record: Iterable[Any]) -> None:\n        self.__writer.writerow(record)\n\n    def construct(self) -> str:\n        return self.__file_obj.getvalue()\n\n    def __enter__(self) -> RecordFile:  # noqa: PYI034\n        return self\n\n    def __exit__(\n        self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None\n    ) -> None:\n        self.__file_obj.close()\n\n\nclass WheelArchive:\n    def __init__(self, project_id: str, *, reproducible: bool) -> None:\n        \"\"\"\n        https://peps.python.org/pep-0427/#abstract\n        \"\"\"\n        self.metadata_directory = f\"{project_id}.dist-info\"\n        self.shared_data_directory = f\"{project_id}.data\"\n        self.time_tuple: TIME_TUPLE | None = None\n\n        self.reproducible = reproducible\n        if self.reproducible:\n            self.time_tuple = self.get_reproducible_time_tuple()\n        else:\n            self.time_tuple = None\n\n        raw_fd, self.path = tempfile.mkstemp(suffix=\".whl\")\n        self.fd = os.fdopen(raw_fd, \"w+b\")\n        self.zf = zipfile.ZipFile(self.fd, \"w\", compression=zipfile.ZIP_DEFLATED)\n\n    @staticmethod\n    def get_reproducible_time_tuple() -> TIME_TUPLE:\n        from datetime import datetime, timezone\n\n        # `zipfile.ZipInfo` does not support timestamps before 1980\n        min_ts = 315532800  # 1980-01-01T00:00:00Z\n        d = datetime.fromtimestamp(max(get_reproducible_timestamp(), min_ts), timezone.utc)\n        return d.year, d.month, d.day, d.hour, d.minute, d.second\n\n    def add_file(self, included_file: IncludedFile) -> tuple[str, str, str]:\n        relative_path = normalize_archive_path(included_file.distribution_path)\n        file_stat = os.stat(included_file.path)\n\n        if self.reproducible:\n            zip_info = zipfile.ZipInfo(relative_path, cast(TIME_TUPLE, self.time_tuple))\n\n            # https://github.com/takluyver/flit/pull/66\n            new_mode = normalize_file_permissions(file_stat.st_mode)\n            set_zip_info_mode(zip_info, new_mode)\n            if stat.S_ISDIR(file_stat.st_mode):  # no cov\n                zip_info.external_attr |= 0x10\n            else:\n                zip_info.file_size = file_stat.st_size\n        else:\n            zip_info = zipfile.ZipInfo.from_file(included_file.path, relative_path)\n\n        zip_info.compress_type = zipfile.ZIP_DEFLATED\n\n        hash_obj = hashlib.sha256()\n        with open(included_file.path, \"rb\") as in_file, self.zf.open(zip_info, \"w\") as out_file:\n            while True:\n                chunk = in_file.read(16384)\n                if not chunk:\n                    break\n\n                hash_obj.update(chunk)\n                out_file.write(chunk)\n\n        hash_digest = format_file_hash(hash_obj.digest())\n        return relative_path, f\"sha256={hash_digest}\", str(file_stat.st_size)\n\n    def write_metadata(self, relative_path: str, contents: str | bytes) -> tuple[str, str, str]:\n        relative_path = f\"{self.metadata_directory}/{normalize_archive_path(relative_path)}\"\n        return self.write_file(relative_path, contents)\n\n    def write_shared_script(self, included_file: IncludedFile, contents: str | bytes) -> tuple[str, str, str]:\n        relative_path = (\n            f\"{self.shared_data_directory}/scripts/{normalize_archive_path(included_file.distribution_path)}\"\n        )\n        if sys.platform == \"win32\":\n            return self.write_file(relative_path, contents)\n\n        file_stat = os.stat(included_file.path)\n        return self.write_file(\n            relative_path,\n            contents,\n            mode=normalize_file_permissions(file_stat.st_mode) if self.reproducible else file_stat.st_mode,\n        )\n\n    def add_shared_file(self, shared_file: IncludedFile) -> tuple[str, str, str]:\n        shared_file.distribution_path = f\"{self.shared_data_directory}/data/{shared_file.distribution_path}\"\n        return self.add_file(shared_file)\n\n    def add_extra_metadata_file(self, extra_metadata_file: IncludedFile) -> tuple[str, str, str]:\n        extra_metadata_file.distribution_path = (\n            f\"{self.metadata_directory}/extra_metadata/{extra_metadata_file.distribution_path}\"\n        )\n        return self.add_file(extra_metadata_file)\n\n    def add_sbom_file(self, sbom_file: IncludedFile) -> tuple[str, str, str]:\n        \"\"\"Add SBOM file to .dist-info/sboms/ directory.\"\"\"\n        sbom_file.distribution_path = f\"{self.metadata_directory}/sboms/{sbom_file.distribution_path}\"\n        return self.add_file(sbom_file)\n\n    def write_file(\n        self,\n        relative_path: str,\n        contents: str | bytes,\n        *,\n        mode: int | None = None,\n    ) -> tuple[str, str, str]:\n        if not isinstance(contents, bytes):\n            contents = contents.encode(\"utf-8\")\n\n        time_tuple = self.time_tuple or (2020, 2, 2, 0, 0, 0)\n        zip_info = zipfile.ZipInfo(relative_path, time_tuple)\n        if mode is None:\n            set_zip_info_mode(zip_info)\n        else:\n            set_zip_info_mode(zip_info, mode)\n\n        hash_obj = hashlib.sha256(contents)\n        hash_digest = format_file_hash(hash_obj.digest())\n        self.zf.writestr(zip_info, contents, compress_type=zipfile.ZIP_DEFLATED)\n\n        return relative_path, f\"sha256={hash_digest}\", str(len(contents))\n\n    def __enter__(self) -> WheelArchive:  # noqa: PYI034\n        return self\n\n    def __exit__(\n        self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None\n    ) -> None:\n        self.zf.close()\n        self.fd.close()\n\n\nclass WheelBuilderConfig(BuilderConfig):\n    def __init__(self, *args: Any, **kwargs: Any) -> None:\n        super().__init__(*args, **kwargs)\n\n        self.__core_metadata_constructor: Callable[..., str] | None = None\n        self.__shared_data: dict[str, str] | None = None\n        self.__shared_scripts: dict[str, str] | None = None\n        self.__extra_metadata: dict[str, str] | None = None\n        self.__strict_naming: bool | None = None\n        self.__macos_max_compat: bool | None = None\n\n    @cached_property\n    def default_file_selection_options(self) -> FileSelectionOptions:\n        include = self.target_config.get(\"include\", self.build_config.get(\"include\", []))\n        exclude = self.target_config.get(\"exclude\", self.build_config.get(\"exclude\", []))\n        packages = self.target_config.get(\"packages\", self.build_config.get(\"packages\", []))\n        only_include = self.target_config.get(\"only-include\", self.build_config.get(\"only-include\", []))\n\n        if include or packages or only_include:\n            return FileSelectionOptions(include, exclude, packages, only_include)\n\n        project_names: set[str] = set()\n        for project_name in (\n            self.builder.normalize_file_name_component(self.builder.metadata.core.raw_name),\n            self.builder.normalize_file_name_component(self.builder.metadata.core.name),\n        ):\n            if os.path.isfile(os.path.join(self.root, project_name, \"__init__.py\")):\n                normalized_project_name = self.get_raw_fs_path_name(self.root, project_name)\n                return FileSelectionOptions([], exclude, [normalized_project_name], [])\n\n            if os.path.isfile(os.path.join(self.root, \"src\", project_name, \"__init__.py\")):\n                normalized_project_name = self.get_raw_fs_path_name(os.path.join(self.root, \"src\"), project_name)\n                return FileSelectionOptions([], exclude, [f\"src/{normalized_project_name}\"], [])\n\n            module_file = f\"{project_name}.py\"\n            if os.path.isfile(os.path.join(self.root, module_file)):\n                return FileSelectionOptions([], exclude, [], [module_file])\n\n            from glob import glob\n\n            possible_namespace_packages = glob(os.path.join(self.root, \"*\", project_name, \"__init__.py\"))\n            if len(possible_namespace_packages) == 1:\n                relative_path = os.path.relpath(possible_namespace_packages[0], self.root)\n                namespace = relative_path.split(os.sep)[0]\n                return FileSelectionOptions([], exclude, [namespace], [])\n            project_names.add(project_name)\n\n        if self.bypass_selection or self.build_artifact_spec is not None or self.get_force_include():\n            self.set_exclude_all()\n            return FileSelectionOptions([], exclude, [], [])\n\n        project_names_text = \" or \".join(sorted(project_names))\n        message = (\n            f\"Unable to determine which files to ship inside the wheel using the following heuristics: \"\n            f\"https://hatch.pypa.io/latest/plugins/builder/wheel/#default-file-selection\\n\\n\"\n            f\"The most likely cause of this is that there is no directory that matches the name of your \"\n            f\"project ({project_names_text}).\\n\\n\"\n            f\"At least one file selection option must be defined in the `tool.hatch.build.targets.wheel` \"\n            f\"table, see: https://hatch.pypa.io/latest/config/build/\\n\\n\"\n            f\"As an example, if you intend to ship a directory named `foo` that resides within a `src` \"\n            f\"directory located at the root of your project, you can define the following:\\n\\n\"\n            f\"[tool.hatch.build.targets.wheel]\\n\"\n            f'packages = [\"src/foo\"]'\n        )\n        raise ValueError(message)\n\n    def default_include(self) -> list[str]:\n        return self.default_file_selection_options.include\n\n    def default_exclude(self) -> list[str]:\n        return self.default_file_selection_options.exclude\n\n    def default_packages(self) -> list[str]:\n        return self.default_file_selection_options.packages\n\n    def default_only_include(self) -> list[str]:\n        return self.default_file_selection_options.only_include\n\n    @property\n    def core_metadata_constructor(self) -> Callable[..., str]:\n        if self.__core_metadata_constructor is None:\n            core_metadata_version = self.target_config.get(\"core-metadata-version\", DEFAULT_METADATA_VERSION)\n            if not isinstance(core_metadata_version, str):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.core-metadata-version` must be a string\"\n                raise TypeError(message)\n\n            constructors = get_core_metadata_constructors()\n            if core_metadata_version not in constructors:\n                message = (\n                    f\"Unknown metadata version `{core_metadata_version}` for field \"\n                    f\"`tool.hatch.build.targets.{self.plugin_name}.core-metadata-version`. \"\n                    f\"Available: {', '.join(sorted(constructors))}\"\n                )\n                raise ValueError(message)\n\n            self.__core_metadata_constructor = constructors[core_metadata_version]\n\n        return self.__core_metadata_constructor\n\n    @property\n    def shared_data(self) -> dict[str, str]:\n        if self.__shared_data is None:\n            shared_data = self.target_config.get(\"shared-data\", {})\n            if not isinstance(shared_data, dict):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.shared-data` must be a mapping\"\n                raise TypeError(message)\n\n            for i, (source, relative_path) in enumerate(shared_data.items(), 1):\n                if not source:\n                    message = (\n                        f\"Source #{i} in field `tool.hatch.build.targets.{self.plugin_name}.shared-data` \"\n                        f\"cannot be an empty string\"\n                    )\n                    raise ValueError(message)\n\n                if not isinstance(relative_path, str):\n                    message = (\n                        f\"Path for source `{source}` in field \"\n                        f\"`tool.hatch.build.targets.{self.plugin_name}.shared-data` must be a string\"\n                    )\n                    raise TypeError(message)\n\n                if not relative_path:\n                    message = (\n                        f\"Path for source `{source}` in field \"\n                        f\"`tool.hatch.build.targets.{self.plugin_name}.shared-data` cannot be an empty string\"\n                    )\n                    raise ValueError(message)\n\n            self.__shared_data = normalize_inclusion_map(shared_data, self.root)\n\n        return self.__shared_data\n\n    @property\n    def shared_scripts(self) -> dict[str, str]:\n        if self.__shared_scripts is None:\n            shared_scripts = self.target_config.get(\"shared-scripts\", {})\n            if not isinstance(shared_scripts, dict):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.shared-scripts` must be a mapping\"\n                raise TypeError(message)\n\n            for i, (source, relative_path) in enumerate(shared_scripts.items(), 1):\n                if not source:\n                    message = (\n                        f\"Source #{i} in field `tool.hatch.build.targets.{self.plugin_name}.shared-scripts` \"\n                        f\"cannot be an empty string\"\n                    )\n                    raise ValueError(message)\n\n                if not isinstance(relative_path, str):\n                    message = (\n                        f\"Path for source `{source}` in field \"\n                        f\"`tool.hatch.build.targets.{self.plugin_name}.shared-scripts` must be a string\"\n                    )\n                    raise TypeError(message)\n\n                if not relative_path:\n                    message = (\n                        f\"Path for source `{source}` in field \"\n                        f\"`tool.hatch.build.targets.{self.plugin_name}.shared-scripts` cannot be an empty string\"\n                    )\n                    raise ValueError(message)\n\n            self.__shared_scripts = normalize_inclusion_map(shared_scripts, self.root)\n\n        return self.__shared_scripts\n\n    @property\n    def extra_metadata(self) -> dict[str, str]:\n        if self.__extra_metadata is None:\n            extra_metadata = self.target_config.get(\"extra-metadata\", {})\n            if not isinstance(extra_metadata, dict):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.extra-metadata` must be a mapping\"\n                raise TypeError(message)\n\n            for i, (source, relative_path) in enumerate(extra_metadata.items(), 1):\n                if not source:\n                    message = (\n                        f\"Source #{i} in field `tool.hatch.build.targets.{self.plugin_name}.extra-metadata` \"\n                        f\"cannot be an empty string\"\n                    )\n                    raise ValueError(message)\n\n                if not isinstance(relative_path, str):\n                    message = (\n                        f\"Path for source `{source}` in field \"\n                        f\"`tool.hatch.build.targets.{self.plugin_name}.extra-metadata` must be a string\"\n                    )\n                    raise TypeError(message)\n\n                if not relative_path:\n                    message = (\n                        f\"Path for source `{source}` in field \"\n                        f\"`tool.hatch.build.targets.{self.plugin_name}.extra-metadata` cannot be an empty string\"\n                    )\n                    raise ValueError(message)\n\n            self.__extra_metadata = normalize_inclusion_map(extra_metadata, self.root)\n\n        return self.__extra_metadata\n\n    @property\n    def sbom_files(self) -> list[str]:\n        \"\"\"\n        https://peps.python.org/pep-0770/\n        \"\"\"\n        sbom_files = self.target_config.get(\"sbom-files\", [])\n        if not isinstance(sbom_files, list):\n            message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.sbom-files` must be an array\"\n            raise TypeError(message)\n\n        for i, sbom_file in enumerate(sbom_files, 1):\n            if not isinstance(sbom_file, str):\n                message = (\n                    f\"SBOM file #{i} in field `tool.hatch.build.targets.{self.plugin_name}.sbom-files` must be a string\"\n                )\n                raise TypeError(message)\n\n        return sbom_files\n\n    @property\n    def strict_naming(self) -> bool:\n        if self.__strict_naming is None:\n            if \"strict-naming\" in self.target_config:\n                strict_naming = self.target_config[\"strict-naming\"]\n                if not isinstance(strict_naming, bool):\n                    message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.strict-naming` must be a boolean\"\n                    raise TypeError(message)\n            else:\n                strict_naming = self.build_config.get(\"strict-naming\", True)\n                if not isinstance(strict_naming, bool):\n                    message = \"Field `tool.hatch.build.strict-naming` must be a boolean\"\n                    raise TypeError(message)\n\n            self.__strict_naming = strict_naming\n\n        return self.__strict_naming\n\n    @property\n    def macos_max_compat(self) -> bool:\n        if self.__macos_max_compat is None:\n            macos_max_compat = self.target_config.get(\"macos-max-compat\", False)\n            if not isinstance(macos_max_compat, bool):\n                message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.macos-max-compat` must be a boolean\"\n                raise TypeError(message)\n\n            self.__macos_max_compat = macos_max_compat\n\n        return self.__macos_max_compat\n\n    @cached_property\n    def bypass_selection(self) -> bool:\n        bypass_selection = self.target_config.get(\"bypass-selection\", False)\n        if not isinstance(bypass_selection, bool):\n            message = f\"Field `tool.hatch.build.targets.{self.plugin_name}.bypass-selection` must be a boolean\"\n            raise TypeError(message)\n\n        return bypass_selection\n\n    if sys.platform in {\"darwin\", \"win32\"}:\n\n        @staticmethod\n        def get_raw_fs_path_name(directory: str, name: str) -> str:\n            normalized = name.casefold()\n            entries = os.listdir(directory)\n            for entry in entries:\n                if entry.casefold() == normalized:\n                    return entry\n\n            return name  # no cov\n\n    else:\n\n        @staticmethod\n        def get_raw_fs_path_name(directory: str, name: str) -> str:  # noqa: ARG004\n            return name\n\n\nclass WheelBuilder(BuilderInterface):\n    \"\"\"\n    Build a binary distribution (.whl file)\n    \"\"\"\n\n    PLUGIN_NAME = \"wheel\"\n\n    def get_version_api(self) -> dict[str, Callable]:\n        return {\"standard\": self.build_standard, \"editable\": self.build_editable}\n\n    def get_default_versions(self) -> list[str]:  # noqa: PLR6301\n        return [\"standard\"]\n\n    def clean(  # noqa: PLR6301\n        self,\n        directory: str,\n        versions: list[str],  # noqa: ARG002\n    ) -> None:\n        for filename in os.listdir(directory):\n            if filename.endswith(\".whl\"):\n                os.remove(os.path.join(directory, filename))\n\n    def build_standard(self, directory: str, **build_data: Any) -> str:\n        if \"tag\" not in build_data:\n            if build_data[\"infer_tag\"]:\n                build_data[\"tag\"] = self.get_best_matching_tag()\n            else:\n                build_data[\"tag\"] = self.get_default_tag()\n\n        with (\n            WheelArchive(self.artifact_project_id, reproducible=self.config.reproducible) as archive,\n            RecordFile() as records,\n        ):\n            for included_file in self.recurse_included_files():\n                record = archive.add_file(included_file)\n                records.write(record)\n\n            self.write_data(archive, records, build_data, build_data[\"dependencies\"])\n\n            records.write((f\"{archive.metadata_directory}/RECORD\", \"\", \"\"))\n            archive.write_metadata(\"RECORD\", records.construct())\n\n        target = os.path.join(directory, f\"{self.artifact_project_id}-{build_data['tag']}.whl\")\n\n        replace_file(archive.path, target)\n        normalize_artifact_permissions(target)\n        return target\n\n    def build_editable(self, directory: str, **build_data: Any) -> str:\n        if self.config.dev_mode_dirs:\n            return self.build_editable_explicit(directory, **build_data)\n\n        return self.build_editable_detection(directory, **build_data)\n\n    def build_editable_detection(self, directory: str, **build_data: Any) -> str:\n        from editables import EditableProject\n\n        build_data[\"tag\"] = self.get_default_tag()\n\n        with (\n            WheelArchive(self.artifact_project_id, reproducible=self.config.reproducible) as archive,\n            RecordFile() as records,\n        ):\n            exposed_packages = {}\n            for included_file in self.recurse_selected_project_files():\n                if not included_file.path.endswith(\".py\"):\n                    continue\n\n                relative_path = included_file.relative_path\n                distribution_path = included_file.distribution_path\n                path_parts = relative_path.split(os.sep)\n\n                # Root file\n                if len(path_parts) == 1:  # no cov\n                    exposed_packages[os.path.splitext(relative_path)[0]] = os.path.join(self.root, relative_path)\n                    continue\n\n                # Root package\n                root_module = path_parts[0]\n                if distribution_path == relative_path:\n                    exposed_packages[root_module] = os.path.join(self.root, root_module)\n                else:\n                    distribution_module = distribution_path.split(os.sep)[0]\n                    try:\n                        exposed_packages[distribution_module] = os.path.join(\n                            self.root,\n                            f\"{relative_path[: relative_path.index(distribution_path)]}{distribution_module}\",\n                        )\n                    except ValueError:\n                        message = (\n                            \"Dev mode installations are unsupported when any path rewrite in the `sources` option \"\n                            \"changes a prefix rather than removes it, see: \"\n                            \"https://github.com/pfmoore/editables/issues/20\"\n                        )\n                        raise ValueError(message) from None\n\n            editable_project = EditableProject(self.metadata.core.name, self.root)\n\n            if self.config.dev_mode_exact:\n                for module, relative_path in exposed_packages.items():\n                    editable_project.map(module, relative_path)\n            else:\n                for relative_path in exposed_packages.values():\n                    editable_project.add_to_path(os.path.dirname(relative_path))\n\n            for raw_filename, content in sorted(editable_project.files()):\n                filename = raw_filename\n                if filename.endswith(\".pth\") and not filename.startswith(\"_\"):\n                    filename = f\"_{filename}\"\n\n                record = archive.write_file(filename, content)\n                records.write(record)\n\n            for included_file in self.recurse_forced_files(self.get_forced_inclusion_map(build_data)):\n                record = archive.add_file(included_file)\n                records.write(record)\n\n            extra_dependencies = list(build_data[\"dependencies\"])\n            for raw_dependency in editable_project.dependencies():\n                dependency = raw_dependency\n                if dependency == \"editables\":\n                    dependency = EDITABLES_REQUIREMENT\n                else:  # no cov\n                    pass\n\n                extra_dependencies.append(dependency)\n\n            self.write_data(archive, records, build_data, extra_dependencies)\n\n            records.write((f\"{archive.metadata_directory}/RECORD\", \"\", \"\"))\n            archive.write_metadata(\"RECORD\", records.construct())\n\n        target = os.path.join(directory, f\"{self.artifact_project_id}-{build_data['tag']}.whl\")\n\n        replace_file(archive.path, target)\n        normalize_artifact_permissions(target)\n        return target\n\n    def build_editable_explicit(self, directory: str, **build_data: Any) -> str:\n        build_data[\"tag\"] = self.get_default_tag()\n\n        with (\n            WheelArchive(self.artifact_project_id, reproducible=self.config.reproducible) as archive,\n            RecordFile() as records,\n        ):\n            directories = sorted(\n                os.path.normpath(os.path.join(self.root, relative_directory))\n                for relative_directory in self.config.dev_mode_dirs\n            )\n\n            record = archive.write_file(f\"_{self.metadata.core.name.replace('-', '_')}.pth\", \"\\n\".join(directories))\n            records.write(record)\n\n            for included_file in self.recurse_forced_files(self.get_forced_inclusion_map(build_data)):\n                record = archive.add_file(included_file)\n                records.write(record)\n\n            self.write_data(archive, records, build_data, build_data[\"dependencies\"])\n\n            records.write((f\"{archive.metadata_directory}/RECORD\", \"\", \"\"))\n            archive.write_metadata(\"RECORD\", records.construct())\n\n        target = os.path.join(directory, f\"{self.artifact_project_id}-{build_data['tag']}.whl\")\n\n        replace_file(archive.path, target)\n        normalize_artifact_permissions(target)\n        return target\n\n    def write_data(\n        self, archive: WheelArchive, records: RecordFile, build_data: dict[str, Any], extra_dependencies: Sequence[str]\n    ) -> None:\n        self.add_shared_data(archive, records, build_data)\n        self.add_shared_scripts(archive, records, build_data)\n\n        # Ensure metadata is written last, see https://peps.python.org/pep-0427/#recommended-archiver-features\n        self.write_metadata(archive, records, build_data, extra_dependencies=extra_dependencies)\n\n    def add_shared_data(self, archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None:\n        shared_data = dict(self.config.shared_data)\n        shared_data.update(normalize_inclusion_map(build_data[\"shared_data\"], self.root))\n\n        for shared_file in self.recurse_explicit_files(shared_data):\n            record = archive.add_shared_file(shared_file)\n            records.write(record)\n\n    def add_shared_scripts(self, archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None:\n        import re\n        from io import BytesIO\n\n        # https://packaging.python.org/en/latest/specifications/binary-distribution-format/#recommended-installer-features\n        shebang = re.compile(rb\"^#!.*(?:pythonw?|pypyw?)[0-9.]*(.*)\", flags=re.DOTALL)\n\n        shared_scripts = dict(self.config.shared_scripts)\n        shared_scripts.update(normalize_inclusion_map(build_data[\"shared_scripts\"], self.root))\n\n        for shared_script in self.recurse_explicit_files(shared_scripts):\n            with open(shared_script.path, \"rb\") as f:\n                content = BytesIO()\n                for line in f:\n                    # Ignore leading blank lines\n                    if not line.strip():\n                        continue\n\n                    match = shebang.match(line)\n                    if match is None:\n                        content.write(line)\n                    else:\n                        content.write(b\"#!python\")\n                        if remaining := match.group(1):\n                            content.write(remaining)\n\n                    content.write(f.read())\n                    break\n\n            record = archive.write_shared_script(shared_script, content.getvalue())\n            records.write(record)\n\n    def add_sboms(self, archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None:\n        sbom_files = self.config.sbom_files\n        sbom_files.extend(build_data[\"sbom_files\"])\n        if not sbom_files:\n            return\n\n        for sbom_file in sbom_files:\n            sbom_path = os.path.join(self.root, sbom_file)\n            if not os.path.isfile(sbom_path):\n                message = f\"SBOM file not found: {sbom_file}\"\n                raise FileNotFoundError(message)\n\n        sbom_map = {os.path.join(self.root, sbom_file): os.path.basename(sbom_file) for sbom_file in sbom_files}\n\n        for included_file in self.recurse_explicit_files(sbom_map):\n            record = archive.add_sbom_file(included_file)\n            records.write(record)\n\n    def write_metadata(\n        self,\n        archive: WheelArchive,\n        records: RecordFile,\n        build_data: dict[str, Any],\n        extra_dependencies: Sequence[str] = (),\n    ) -> None:\n        # <<< IMPORTANT >>>\n        # Ensure calls are ordered by the number of path components followed by the name of the components\n\n        # METADATA\n        self.write_project_metadata(archive, records, extra_dependencies=extra_dependencies)\n\n        # WHEEL\n        self.write_archive_metadata(archive, records, build_data)\n\n        # entry_points.txt\n        self.write_entry_points_file(archive, records)\n\n        # licenses/\n        self.add_licenses(archive, records)\n\n        # sboms/\n        self.add_sboms(archive, records, build_data)\n\n        # extra_metadata/ - write last\n        self.add_extra_metadata(archive, records, build_data)\n\n    @staticmethod\n    def write_archive_metadata(archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None:\n        from packaging.tags import parse_tag\n\n        metadata = f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: {\"true\" if build_data[\"pure_python\"] else \"false\"}\n\"\"\"\n\n        for tag in sorted(map(str, parse_tag(build_data[\"tag\"]))):\n            metadata += f\"Tag: {tag}\\n\"\n\n        record = archive.write_metadata(\"WHEEL\", metadata)\n        records.write(record)\n\n    def write_entry_points_file(self, archive: WheelArchive, records: RecordFile) -> None:\n        entry_points_file = self.construct_entry_points_file()\n        if entry_points_file:\n            record = archive.write_metadata(\"entry_points.txt\", entry_points_file)\n            records.write(record)\n\n    def write_project_metadata(\n        self, archive: WheelArchive, records: RecordFile, extra_dependencies: Sequence[str] = ()\n    ) -> None:\n        record = archive.write_metadata(\n            \"METADATA\", self.config.core_metadata_constructor(self.metadata, extra_dependencies=extra_dependencies)\n        )\n        records.write(record)\n\n    def add_licenses(self, archive: WheelArchive, records: RecordFile) -> None:\n        for relative_path in self.metadata.core.license_files:\n            license_file = os.path.normpath(os.path.join(self.root, relative_path))\n            with open(license_file, \"rb\") as f:\n                record = archive.write_metadata(f\"licenses/{relative_path}\", f.read())\n                records.write(record)\n\n    def add_extra_metadata(self, archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None:\n        extra_metadata = dict(self.config.extra_metadata)\n        extra_metadata.update(normalize_inclusion_map(build_data[\"extra_metadata\"], self.root))\n\n        for extra_metadata_file in self.recurse_explicit_files(extra_metadata):\n            record = archive.add_extra_metadata_file(extra_metadata_file)\n            records.write(record)\n\n    def construct_entry_points_file(self) -> str:\n        core_metadata = self.metadata.core\n        metadata_file = \"\"\n\n        if core_metadata.scripts:\n            metadata_file += \"\\n[console_scripts]\\n\"\n            for name, object_ref in core_metadata.scripts.items():\n                metadata_file += f\"{name} = {object_ref}\\n\"\n\n        if core_metadata.gui_scripts:\n            metadata_file += \"\\n[gui_scripts]\\n\"\n            for name, object_ref in core_metadata.gui_scripts.items():\n                metadata_file += f\"{name} = {object_ref}\\n\"\n\n        if core_metadata.entry_points:\n            for group, entry_points in core_metadata.entry_points.items():\n                metadata_file += f\"\\n[{group}]\\n\"\n                for name, object_ref in entry_points.items():\n                    metadata_file += f\"{name} = {object_ref}\\n\"\n\n        return metadata_file.lstrip()\n\n    def get_default_tag(self) -> str:\n        known_major_versions = list(get_known_python_major_versions())\n        max_version_part = 100\n        supported_python_versions = []\n        for major_version in known_major_versions:\n            for minor_version in range(max_version_part):\n                # Try an artificially high patch version to account for common cases like `>=3.11.4` or `>=3.10,<3.11`\n                if self.metadata.core.python_constraint.contains(f\"{major_version}.{minor_version}.{max_version_part}\"):\n                    supported_python_versions.append(f\"py{major_version}\")\n                    break\n\n        # Slow path, try all permutations to account for narrow support ranges like `<=3.11.4`\n        if not supported_python_versions:\n            for major_version in known_major_versions:\n                for minor_version in range(max_version_part):\n                    for patch_version in range(max_version_part):\n                        if self.metadata.core.python_constraint.contains(\n                            f\"{major_version}.{minor_version}.{patch_version}\"\n                        ):\n                            supported_python_versions.append(f\"py{major_version}\")\n                            break\n                    else:\n                        continue\n                    break\n\n        return f\"{'.'.join(supported_python_versions)}-none-any\"\n\n    def get_best_matching_tag(self) -> str:\n        import sys\n\n        from packaging.tags import sys_tags\n\n        # Linux tag is after many/musl; packaging tools are required to skip\n        # many/musl, see https://github.com/pypa/packaging/issues/160\n        tag = next(iter(t for t in sys_tags() if \"manylinux\" not in t.platform and \"musllinux\" not in t.platform))\n        tag_parts = [tag.interpreter, tag.abi, tag.platform]\n\n        if sys.platform == \"darwin\":\n            from hatchling.builders.macos import process_macos_plat_tag\n\n            tag_parts[2] = process_macos_plat_tag(tag_parts[2], compat=self.config.macos_max_compat)\n\n        return \"-\".join(tag_parts)\n\n    def get_default_build_data(self) -> dict[str, Any]:  # noqa: PLR6301\n        return {\n            \"infer_tag\": False,\n            \"pure_python\": True,\n            \"dependencies\": [],\n            \"force_include_editable\": {},\n            \"extra_metadata\": {},\n            \"shared_data\": {},\n            \"shared_scripts\": {},\n            \"sbom_files\": [],\n        }\n\n    def get_forced_inclusion_map(self, build_data: dict[str, Any]) -> dict[str, str]:\n        if not build_data[\"force_include_editable\"]:\n            return self.config.get_force_include()\n\n        return normalize_inclusion_map(build_data[\"force_include_editable\"], self.root)\n\n    @property\n    def artifact_project_id(self) -> str:\n        return (\n            self.project_id\n            if self.config.strict_naming\n            else f\"{self.normalize_file_name_component(self.metadata.core.raw_name)}-{self.metadata.version}\"\n        )\n\n    @classmethod\n    def get_config_class(cls) -> type[WheelBuilderConfig]:\n        return WheelBuilderConfig\n"
  },
  {
    "path": "backend/src/hatchling/cli/__init__.py",
    "content": "import argparse\n\nfrom hatchling.cli.build import build_command\nfrom hatchling.cli.dep import dep_command\nfrom hatchling.cli.metadata import metadata_command\nfrom hatchling.cli.version import version_command\n\n\ndef hatchling() -> int:\n    parser = argparse.ArgumentParser(prog=\"hatchling\", allow_abbrev=False)\n    subparsers = parser.add_subparsers()\n\n    defaults = {\"metavar\": \"\"}\n\n    build_command(subparsers, defaults)\n    dep_command(subparsers, defaults)\n    metadata_command(subparsers, defaults)\n    version_command(subparsers, defaults)\n\n    kwargs = vars(parser.parse_args())\n    try:\n        command = kwargs.pop(\"func\")\n    except KeyError:\n        parser.print_help()\n    else:\n        command(**kwargs)\n\n    return 0\n"
  },
  {
    "path": "backend/src/hatchling/cli/build/__init__.py",
    "content": "from __future__ import annotations\n\nimport argparse\nfrom typing import Any\n\n\ndef build_impl(\n    *,\n    called_by_app: bool,  # noqa: ARG001\n    directory: str,\n    targets: list[str],\n    hooks_only: bool,\n    no_hooks: bool,\n    clean: bool,\n    clean_hooks_after: bool,\n    clean_only: bool,\n    show_dynamic_deps: bool,\n) -> None:\n    import os\n\n    from hatchling.bridge.app import Application\n    from hatchling.builders.constants import BuildEnvVars\n    from hatchling.metadata.core import ProjectMetadata\n    from hatchling.plugin.manager import PluginManager\n\n    app = Application()\n\n    if hooks_only and no_hooks:\n        app.abort(\"Cannot use both --hooks-only and --no-hooks together\")\n\n    root = os.getcwd()\n    plugin_manager = PluginManager()\n    metadata = ProjectMetadata(root, plugin_manager)\n\n    target_data: dict[str, Any] = {}\n    if targets:\n        for data in targets:\n            target_name, _, version_data = data.partition(\":\")\n            versions = version_data.split(\",\") if version_data else []\n            target_data.setdefault(target_name, []).extend(versions)\n    else:  # no cov\n        target_data[\"sdist\"] = []\n        target_data[\"wheel\"] = []\n\n    builders = {}\n    unknown_targets = []\n    for target_name in target_data:\n        builder_class = plugin_manager.builder.get(target_name)\n        if builder_class is None:\n            unknown_targets.append(target_name)\n        else:\n            builders[target_name] = builder_class\n\n    if unknown_targets:\n        app.abort(f\"Unknown build targets: {', '.join(sorted(unknown_targets))}\")\n\n    # We guarantee that builds occur within the project directory\n    root = os.getcwd()\n\n    if no_hooks:\n        os.environ[BuildEnvVars.NO_HOOKS] = \"true\"\n\n    dynamic_dependencies: dict[str, None] = {}\n    for i, (target_name, versions) in enumerate(target_data.items()):\n        # Separate targets with a blank line\n        if not (clean_only or show_dynamic_deps) and i != 0:  # no cov\n            app.display_info()\n\n        builder_class = builders[target_name]\n\n        # Display name before instantiation in case of errors\n        if not (clean_only or show_dynamic_deps) and len(target_data) > 1:\n            app.display_mini_header(target_name)\n\n        builder = builder_class(root, plugin_manager=plugin_manager, metadata=metadata, app=app.get_safe_application())\n        if show_dynamic_deps:\n            for dependency in builder.config.dynamic_dependencies:\n                dynamic_dependencies[dependency] = None\n\n            continue\n\n        for artifact in builder.build(\n            directory=directory,\n            versions=versions,\n            hooks_only=hooks_only,\n            clean=clean,\n            clean_hooks_after=clean_hooks_after,\n            clean_only=clean_only,\n        ):\n            if os.path.isfile(artifact) and artifact.startswith(root):\n                app.display_info(os.path.relpath(artifact, root))\n            else:  # no cov\n                app.display_info(artifact)\n\n    if show_dynamic_deps:\n        app.display(str(list(dynamic_dependencies)))\n\n\ndef build_command(subparsers: argparse._SubParsersAction, defaults: Any) -> None:\n    parser = subparsers.add_parser(\"build\")\n    parser.add_argument(\n        \"-d\", \"--directory\", dest=\"directory\", help=\"The directory in which to build artifacts\", **defaults\n    )\n    parser.add_argument(\n        \"-t\",\n        \"--target\",\n        dest=\"targets\",\n        action=\"append\",\n        help=\"Comma-separated list of targets to build, overriding project defaults\",\n        **defaults,\n    )\n    parser.add_argument(\"--hooks-only\", dest=\"hooks_only\", action=\"store_true\", default=None)\n    parser.add_argument(\"--no-hooks\", dest=\"no_hooks\", action=\"store_true\", default=None)\n    parser.add_argument(\"-c\", \"--clean\", dest=\"clean\", action=\"store_true\", default=None)\n    parser.add_argument(\"--clean-hooks-after\", dest=\"clean_hooks_after\", action=\"store_true\", default=None)\n    parser.add_argument(\"--clean-only\", dest=\"clean_only\", action=\"store_true\")\n    parser.add_argument(\"--show-dynamic-deps\", dest=\"show_dynamic_deps\", action=\"store_true\")\n    parser.add_argument(\"--app\", dest=\"called_by_app\", action=\"store_true\", help=argparse.SUPPRESS)\n    parser.set_defaults(func=build_impl)\n"
  },
  {
    "path": "backend/src/hatchling/cli/dep/__init__.py",
    "content": "from __future__ import annotations\n\nimport sys\nfrom typing import TYPE_CHECKING, Any\n\nif TYPE_CHECKING:\n    import argparse\n\n\ndef synced_impl(*, dependencies: list[str], python: str) -> None:\n    import subprocess\n    from ast import literal_eval\n\n    from packaging.requirements import Requirement\n\n    from hatchling.cli.dep.core import dependencies_in_sync\n\n    sys_path = None\n    if python:\n        output = subprocess.check_output([python, \"-c\", \"import sys;print([path for path in sys.path if path])\"])\n        sys_path = literal_eval(output.strip().decode(\"utf-8\"))\n\n    sys.exit(0 if dependencies_in_sync(list(map(Requirement, dependencies)), sys_path) else 1)\n\n\ndef synced_command(subparsers: argparse._SubParsersAction, defaults: Any) -> None:\n    parser = subparsers.add_parser(\"synced\")\n    parser.add_argument(\"dependencies\", nargs=\"+\")\n    parser.add_argument(\"-p\", \"--python\", dest=\"python\", **defaults)\n    parser.set_defaults(func=synced_impl)\n\n\ndef dep_command(subparsers: argparse._SubParsersAction, defaults: Any) -> None:\n    parser = subparsers.add_parser(\"dep\")\n    subparsers = parser.add_subparsers()\n\n    synced_command(subparsers, defaults)\n"
  },
  {
    "path": "backend/src/hatchling/cli/dep/core.py",
    "content": "from __future__ import annotations\n\nimport re\nimport sys\nfrom importlib.metadata import Distribution, DistributionFinder\n\nfrom packaging.markers import default_environment\nfrom packaging.requirements import Requirement\n\n\nclass DistributionCache:\n    def __init__(self, sys_path: list[str]) -> None:\n        self._resolver = Distribution.discover(context=DistributionFinder.Context(path=sys_path))\n        self._distributions: dict[str, Distribution] = {}\n        self._search_exhausted = False\n        self._canonical_regex = re.compile(r\"[-_.]+\")\n\n    def __getitem__(self, item: str) -> Distribution | None:\n        item = self._canonical_regex.sub(\"-\", item).lower()\n        possible_distribution = self._distributions.get(item)\n        if possible_distribution is not None:\n            return possible_distribution\n\n        # Be safe even though the code as-is will never reach this since\n        # the first unknown distribution will fail fast\n        if self._search_exhausted:  # no cov\n            return None\n\n        for distribution in self._resolver:\n            name = distribution.metadata[\"Name\"]\n            if name is None:\n                continue\n\n            name = self._canonical_regex.sub(\"-\", name).lower()\n            self._distributions[name] = distribution\n            if name == item:\n                return distribution\n\n        self._search_exhausted = True\n\n        return None\n\n\ndef dependency_in_sync(\n    requirement: Requirement, environment: dict[str, str], installed_distributions: DistributionCache\n) -> bool:\n    if requirement.marker and not requirement.marker.evaluate(environment):\n        return True\n\n    distribution = installed_distributions[requirement.name]\n    if distribution is None:\n        return False\n\n    extras = requirement.extras\n    if extras:\n        transitive_requirements: list[str] = distribution.metadata.get_all(\"Requires-Dist\", [])\n        if not transitive_requirements:\n            return False\n\n        available_extras: list[str] = distribution.metadata.get_all(\"Provides-Extra\", [])\n\n        for requirement_string in transitive_requirements:\n            transitive_requirement = Requirement(requirement_string)\n            if not transitive_requirement.marker:\n                continue\n\n            for extra in extras:\n                # FIXME: This may cause a build to never be ready if newer versions do not provide the desired\n                # extra and it's just a user error/typo. See: https://github.com/pypa/pip/issues/7122\n                if extra not in available_extras:\n                    return False\n\n                extra_environment = dict(environment)\n                extra_environment[\"extra\"] = extra\n                if not dependency_in_sync(transitive_requirement, extra_environment, installed_distributions):\n                    return False\n\n    if requirement.specifier and not requirement.specifier.contains(distribution.version):\n        return False\n\n    # TODO: handle https://discuss.python.org/t/11938\n    if requirement.url:\n        direct_url_file = distribution.read_text(\"direct_url.json\")\n        if direct_url_file is not None:\n            import json\n\n            # https://packaging.python.org/specifications/direct-url/\n            direct_url_data = json.loads(direct_url_file)\n            if \"vcs_info\" in direct_url_data:\n                url = direct_url_data[\"url\"]\n                vcs_info = direct_url_data[\"vcs_info\"]\n                vcs = vcs_info[\"vcs\"]\n                commit_id = vcs_info[\"commit_id\"]\n                requested_revision = vcs_info.get(\"requested_revision\")\n\n                # Try a few variations, see https://peps.python.org/pep-0440/#direct-references\n                if (\n                    requested_revision and requirement.url == f\"{vcs}+{url}@{requested_revision}#{commit_id}\"\n                ) or requirement.url == f\"{vcs}+{url}@{commit_id}\":\n                    return True\n\n                if requirement.url in {f\"{vcs}+{url}\", f\"{vcs}+{url}@{requested_revision}\"}:\n                    import subprocess\n\n                    if vcs == \"git\":\n                        vcs_cmd = [vcs, \"ls-remote\", url]\n                        if requested_revision:\n                            vcs_cmd.append(requested_revision)\n                    # TODO: add elifs for hg, svn, and bzr https://github.com/pypa/hatch/issues/760\n                    else:\n                        return False\n                    result = subprocess.run(vcs_cmd, capture_output=True, text=True)  # noqa: PLW1510\n                    if result.returncode or not result.stdout.strip():\n                        return False\n                    latest_commit_id, *_ = result.stdout.split()\n                    return commit_id == latest_commit_id\n\n                return False\n\n    return True\n\n\ndef dependencies_in_sync(\n    requirements: list[Requirement], sys_path: list[str] | None = None, environment: dict[str, str] | None = None\n) -> bool:\n    if sys_path is None:\n        sys_path = sys.path\n    if environment is None:\n        environment = default_environment()  # type: ignore[assignment]\n\n    installed_distributions = DistributionCache(sys_path)\n    return all(dependency_in_sync(requirement, environment, installed_distributions) for requirement in requirements)  # type: ignore[arg-type]\n"
  },
  {
    "path": "backend/src/hatchling/cli/metadata/__init__.py",
    "content": "from __future__ import annotations\n\nimport argparse\nfrom typing import Any\n\n\ndef metadata_impl(\n    *,\n    called_by_app: bool,  # noqa: ARG001\n    field: str,\n    compact: bool,\n) -> None:\n    import json\n    import os\n\n    from hatchling.bridge.app import Application\n    from hatchling.metadata.core import ProjectMetadata\n    from hatchling.metadata.utils import resolve_metadata_fields\n    from hatchling.plugin.manager import PluginManager\n\n    app = Application()\n\n    root = os.getcwd()\n    plugin_manager = PluginManager()\n    project_metadata = ProjectMetadata(root, plugin_manager)\n\n    metadata = resolve_metadata_fields(project_metadata)\n    if field:  # no cov\n        if field not in metadata:\n            app.abort(f\"Unknown metadata field: {field}\")\n        elif field == \"readme\":\n            app.display(metadata[field][\"text\"])\n        elif isinstance(metadata[field], str):\n            app.display(metadata[field])\n        else:\n            app.display(json.dumps(metadata[field], indent=4))\n\n        return\n\n    for key, value in list(metadata.items()):\n        if not value:\n            metadata.pop(key)\n\n    if compact:\n        app.display(json.dumps(metadata, separators=(\",\", \":\")))\n    else:  # no cov\n        app.display(json.dumps(metadata, indent=4))\n\n\ndef metadata_command(\n    subparsers: argparse._SubParsersAction,\n    defaults: Any,  # noqa: ARG001\n) -> None:\n    parser = subparsers.add_parser(\"metadata\")\n    parser.add_argument(\"field\", nargs=\"?\")\n    parser.add_argument(\"-c\", \"--compact\", action=\"store_true\")\n    parser.add_argument(\"--app\", dest=\"called_by_app\", action=\"store_true\", help=argparse.SUPPRESS)\n    parser.set_defaults(func=metadata_impl)\n"
  },
  {
    "path": "backend/src/hatchling/cli/version/__init__.py",
    "content": "from __future__ import annotations\n\nimport argparse\nfrom typing import Any\n\n\ndef version_impl(\n    *,\n    called_by_app: bool,  # noqa: ARG001\n    desired_version: str,\n) -> None:\n    import os\n\n    from hatchling.bridge.app import Application\n    from hatchling.metadata.core import ProjectMetadata\n    from hatchling.plugin.manager import PluginManager\n\n    app = Application()\n\n    root = os.getcwd()\n    plugin_manager = PluginManager()\n    metadata = ProjectMetadata(root, plugin_manager)\n\n    if \"version\" in metadata.config.get(\"project\", {}):\n        if desired_version:\n            app.abort(\"Cannot set version when it is statically defined by the `project.version` field\")\n        else:\n            app.display(metadata.core.version)\n            return\n\n    source = metadata.hatch.version.source\n\n    version_data = source.get_version_data()\n    original_version = version_data[\"version\"]\n\n    if not desired_version:\n        app.display(original_version)\n        return\n\n    updated_version = metadata.hatch.version.scheme.update(desired_version, original_version, version_data)\n    source.set_version(updated_version, version_data)\n\n    app.display_info(f\"Old: {original_version}\")\n    app.display_info(f\"New: {updated_version}\")\n\n\ndef version_command(subparsers: argparse._SubParsersAction, defaults: Any) -> None:\n    parser = subparsers.add_parser(\"version\")\n    parser.add_argument(\"desired_version\", default=\"\", nargs=\"?\", **defaults)\n    parser.add_argument(\"--app\", dest=\"called_by_app\", action=\"store_true\", help=argparse.SUPPRESS)\n    parser.set_defaults(func=version_impl)\n"
  },
  {
    "path": "backend/src/hatchling/dep/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/dep/core.py",
    "content": "from hatchling.cli.dep.core import dependencies_in_sync  # noqa: F401\n"
  },
  {
    "path": "backend/src/hatchling/licenses/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/licenses/supported.py",
    "content": "from packaging.licenses._spdx import VERSION  # noqa: F401, PLC2701\n"
  },
  {
    "path": "backend/src/hatchling/metadata/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/metadata/core.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\nfrom contextlib import suppress\nfrom copy import deepcopy\nfrom typing import TYPE_CHECKING, Any, Generic, cast\n\nfrom hatchling.metadata.utils import (\n    format_dependency,\n    is_valid_project_name,\n    normalize_project_name,\n    normalize_requirement,\n)\nfrom hatchling.plugin.manager import PluginManagerBound\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\nfrom hatchling.utils.fs import locate_file\n\nif TYPE_CHECKING:\n    from packaging.requirements import Requirement\n    from packaging.specifiers import SpecifierSet\n\n    from hatchling.metadata.plugin.interface import MetadataHookInterface\n    from hatchling.utils.context import Context\n    from hatchling.version.scheme.plugin.interface import VersionSchemeInterface\n    from hatchling.version.source.plugin.interface import VersionSourceInterface\n\nif sys.version_info >= (3, 11):\n    import tomllib\nelse:\n    import tomli as tomllib\n\n\ndef load_toml(path: str) -> dict[str, Any]:\n    with open(path, encoding=\"utf-8\") as f:\n        return tomllib.loads(f.read())\n\n\nclass ProjectMetadata(Generic[PluginManagerBound]):\n    def __init__(\n        self,\n        root: str,\n        plugin_manager: PluginManagerBound | None,\n        config: dict[str, Any] | None = None,\n    ) -> None:\n        self.root = root\n        self.plugin_manager = plugin_manager\n        self._config = config\n\n        self._context: Context | None = None\n        self._build: BuildMetadata | None = None\n        self._core: CoreMetadata | None = None\n        self._hatch: HatchMetadata | None = None\n\n        self._core_raw_metadata: dict[str, Any] | None = None\n        self._dynamic: list[str] | None = None\n        self._name: str | None = None\n        self._version: str | None = None\n        self._project_file: str | None = None\n\n        # App already loaded config\n        if config is not None and root is not None:\n            self._project_file = os.path.join(root, \"pyproject.toml\")\n\n    def has_project_file(self) -> bool:\n        _ = self.config\n        if self._project_file is None:\n            return False\n        return os.path.isfile(self._project_file)\n\n    @property\n    def context(self) -> Context:\n        if self._context is None:\n            from hatchling.utils.context import Context\n\n            self._context = Context(self.root)\n\n        return self._context\n\n    @property\n    def core_raw_metadata(self) -> dict[str, Any]:\n        if self._core_raw_metadata is None:\n            if \"project\" not in self.config:\n                message = \"Missing `project` metadata table in configuration\"\n                raise ValueError(message)\n\n            core_raw_metadata = self.config[\"project\"]\n            if not isinstance(core_raw_metadata, dict):\n                message = \"The `project` configuration must be a table\"\n                raise TypeError(message)\n\n            core_raw_metadata = deepcopy(core_raw_metadata)\n            pkg_info = os.path.join(self.root, \"PKG-INFO\")\n            if os.path.isfile(pkg_info):\n                from hatchling.metadata.spec import PROJECT_CORE_METADATA_FIELDS, project_metadata_from_core_metadata\n\n                with open(pkg_info, encoding=\"utf-8\") as f:\n                    pkg_info_contents = f.read()\n\n                base_metadata = project_metadata_from_core_metadata(pkg_info_contents)\n                defined_dynamic = core_raw_metadata.get(\"dynamic\", [])\n                for field in list(defined_dynamic):\n                    if field in PROJECT_CORE_METADATA_FIELDS and field in base_metadata:\n                        core_raw_metadata[field] = base_metadata[field]\n                        defined_dynamic.remove(field)\n\n            self._core_raw_metadata = core_raw_metadata\n\n        return self._core_raw_metadata\n\n    @property\n    def dynamic(self) -> list[str]:\n        # Keep track of the original dynamic fields before depopulation\n        if self._dynamic is None:\n            dynamic = self.core_raw_metadata.get(\"dynamic\", [])\n            if not isinstance(dynamic, list):\n                message = \"Field `project.dynamic` must be an array\"\n                raise TypeError(message)\n\n            for i, field in enumerate(dynamic, 1):\n                if not isinstance(field, str):\n                    message = f\"Field #{i} of field `project.dynamic` must be a string\"\n                    raise TypeError(message)\n\n            self._dynamic = list(dynamic)\n\n        return self._dynamic\n\n    @property\n    def name(self) -> str:\n        # Duplicate the name parsing here for situations where it's\n        # needed but metadata plugins might not be available\n        if self._name is None:\n            name = self.core_raw_metadata.get(\"name\", \"\")\n            if not name:\n                message = \"Missing required field `project.name`\"\n                raise ValueError(message)\n\n            self._name = normalize_project_name(name)\n\n        return self._name\n\n    @property\n    def version(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#version\n        \"\"\"\n        if self._version is None:\n            self._version = self._get_version()\n            with suppress(ValueError):\n                self.core.dynamic.remove(\"version\")\n\n        return self._version\n\n    @property\n    def config(self) -> dict[str, Any]:\n        if self._config is None:\n            project_file = locate_file(self.root, \"pyproject.toml\")\n            if project_file is None:\n                self._config = {}\n            else:\n                self._project_file = project_file\n                self._config = load_toml(project_file)\n\n        return self._config\n\n    @property\n    def build(self) -> BuildMetadata:\n        if self._build is None:\n            build_metadata = self.config.get(\"build-system\", {})\n            if not isinstance(build_metadata, dict):\n                message = \"The `build-system` configuration must be a table\"\n                raise TypeError(message)\n\n            self._build = BuildMetadata(self.root, build_metadata)\n\n        return self._build\n\n    @property\n    def core(self) -> CoreMetadata:\n        if self._core is None:\n            metadata = CoreMetadata(self.root, self.core_raw_metadata, self.hatch.metadata, self.context)\n\n            # Save the fields\n            _ = self.dynamic\n\n            metadata_hooks = self.hatch.metadata.hooks\n            if metadata_hooks:\n                static_fields = set(self.core_raw_metadata)\n                if \"version\" in self.hatch.config:\n                    self._version = self._get_version(metadata)\n                    self.core_raw_metadata[\"version\"] = self.version\n\n                if metadata.dynamic:\n                    for metadata_hook in metadata_hooks.values():\n                        metadata_hook.update(self.core_raw_metadata)\n                        metadata.add_known_classifiers(metadata_hook.get_known_classifiers())\n\n                    new_fields = set(self.core_raw_metadata) - static_fields\n                    for new_field in new_fields:\n                        if new_field in metadata.dynamic:\n                            metadata.dynamic.remove(new_field)\n                        else:\n                            message = (\n                                f\"The field `{new_field}` was set dynamically and therefore must be \"\n                                f\"listed in `project.dynamic`\"\n                            )\n                            raise ValueError(message)\n\n            self._core = metadata\n\n        return self._core\n\n    @property\n    def hatch(self) -> HatchMetadata:\n        if self._hatch is None:\n            tool_config = self.config.get(\"tool\", {})\n            if not isinstance(tool_config, dict):\n                message = \"The `tool` configuration must be a table\"\n                raise TypeError(message)\n\n            hatch_config = tool_config.get(\"hatch\", {})\n            if not isinstance(hatch_config, dict):\n                message = \"The `tool.hatch` configuration must be a table\"\n                raise TypeError(message)\n\n            hatch_file = (\n                os.path.join(os.path.dirname(self._project_file), DEFAULT_CONFIG_FILE)\n                if self._project_file is not None\n                else locate_file(self.root, DEFAULT_CONFIG_FILE) or \"\"\n            )\n\n            if hatch_file and os.path.isfile(hatch_file):\n                config = load_toml(hatch_file)\n                hatch_config = hatch_config.copy()\n                hatch_config.update(config)\n\n            self._hatch = HatchMetadata(self.root, hatch_config, self.plugin_manager)\n\n        return self._hatch\n\n    def _get_version(self, core_metadata: CoreMetadata | None = None) -> str:\n        if core_metadata is None:\n            core_metadata = self.core\n\n        version = core_metadata.version\n        if version is None:\n            version = self.hatch.version.cached\n            source = f\"source `{self.hatch.version.source_name}`\"\n            core_metadata._version_set = True  # noqa: SLF001\n        else:\n            source = \"field `project.version`\"\n\n        from packaging.version import InvalidVersion, Version\n\n        try:\n            normalized_version = str(Version(version))\n        except InvalidVersion:\n            message = f\"Invalid version `{version}` from {source}, see https://peps.python.org/pep-0440/\"\n            raise ValueError(message) from None\n        else:\n            return normalized_version\n\n    def validate_fields(self) -> None:\n        _ = self.version\n        self.core.validate_fields()\n\n\nclass BuildMetadata:\n    \"\"\"\n    https://peps.python.org/pep-0517/\n    \"\"\"\n\n    def __init__(self, root: str, config: dict[str, str | list[str]]) -> None:\n        self.root = root\n        self.config = config\n\n        self._requires: list[str] | None = None\n        self._requires_complex: list[Requirement] | None = None\n        self._build_backend: str | None = None\n        self._backend_path: list[str] | None = None\n\n    @property\n    def requires_complex(self) -> list[Requirement]:\n        if self._requires_complex is None:\n            from packaging.requirements import InvalidRequirement, Requirement\n\n            requires = self.config.get(\"requires\", [])\n            if not isinstance(requires, list):\n                message = \"Field `build-system.requires` must be an array\"\n                raise TypeError(message)\n\n            requires_complex = []\n\n            for i, entry in enumerate(requires, 1):\n                if not isinstance(entry, str):\n                    message = f\"Dependency #{i} of field `build-system.requires` must be a string\"\n                    raise TypeError(message)\n\n                try:\n                    requires_complex.append(Requirement(entry))\n                except InvalidRequirement as e:\n                    message = f\"Dependency #{i} of field `build-system.requires` is invalid: {e}\"\n                    raise ValueError(message) from None\n\n            self._requires_complex = requires_complex\n\n        return self._requires_complex\n\n    @property\n    def requires(self) -> list[str]:\n        if self._requires is None:\n            self._requires = [str(r) for r in self.requires_complex]\n\n        return self._requires\n\n    @property\n    def build_backend(self) -> str:\n        if self._build_backend is None:\n            build_backend = self.config.get(\"build-backend\", \"\")\n            if not isinstance(build_backend, str):\n                message = \"Field `build-system.build-backend` must be a string\"\n                raise TypeError(message)\n\n            self._build_backend = build_backend\n\n        return self._build_backend\n\n    @property\n    def backend_path(self) -> list[str]:\n        if self._backend_path is None:\n            backend_path = self.config.get(\"backend-path\", [])\n            if not isinstance(backend_path, list):\n                message = \"Field `build-system.backend-path` must be an array\"\n                raise TypeError(message)\n\n            for i, entry in enumerate(backend_path, 1):\n                if not isinstance(entry, str):\n                    message = f\"Entry #{i} of field `build-system.backend-path` must be a string\"\n                    raise TypeError(message)\n\n            self._backend_path = backend_path\n\n        return self._backend_path\n\n\nclass CoreMetadata:\n    \"\"\"\n    https://peps.python.org/pep-0621/\n    \"\"\"\n\n    def __init__(\n        self,\n        root: str,\n        config: dict[str, Any],\n        hatch_metadata: HatchMetadataSettings,\n        context: Context,\n    ) -> None:\n        self.root = root\n        self.config = config\n        self.hatch_metadata = hatch_metadata\n        self.context = context\n\n        self._raw_name: str | None = None\n        self._name: str | None = None\n        self._version: str | None = None\n        self._description: str | None = None\n        self._readme: str | None = None\n        self._readme_content_type: str | None = None\n        self._readme_path: str | None = None\n        self._requires_python: str | None = None\n        self._python_constraint: SpecifierSet | None = None\n        self._license: str | None = None\n        self._license_expression: str | None = None\n        self._license_files: list[str] | None = None\n        self._authors: list[str] | None = None\n        self._authors_data: dict[str, list[str]] | None = None\n        self._maintainers: list[str] | None = None\n        self._maintainers_data: dict[str, list[str]] | None = None\n        self._keywords: list[str] | None = None\n        self._classifiers: list[str] | None = None\n        self._extra_classifiers: set[str] = set()\n        self._urls: dict[str, str] | None = None\n        self._scripts: dict[str, str] | None = None\n        self._gui_scripts: dict[str, str] | None = None\n        self._entry_points: dict[str, dict[str, str]] | None = None\n        self._dependencies_complex: dict[str, Requirement] | None = None\n        self._dependencies: list[str] | None = None\n        self._optional_dependencies_complex: dict[str, dict[str, Requirement]] | None = None\n        self._optional_dependencies: dict[str, list[str]] | None = None\n        self._dynamic: list[str] | None = None\n\n        # Indicates that the version has been successfully set dynamically\n        self._version_set: bool = False\n\n    @property\n    def raw_name(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#name\n        \"\"\"\n        if self._raw_name is None:\n            if \"name\" in self.dynamic:\n                message = \"Static metadata field `name` cannot be present in field `project.dynamic`\"\n                raise ValueError(message)\n\n            raw_name = self.config.get(\"name\", \"\")\n            if not raw_name:\n                message = \"Missing required field `project.name`\"\n                raise ValueError(message)\n\n            if not isinstance(raw_name, str):\n                message = \"Field `project.name` must be a string\"\n                raise TypeError(message)\n\n            if not is_valid_project_name(raw_name):\n                message = (\n                    \"Required field `project.name` must only contain ASCII letters/digits, underscores, \"\n                    \"hyphens, and periods, and must begin and end with ASCII letters/digits.\"\n                )\n                raise ValueError(message)\n\n            self._raw_name = raw_name\n\n        return self._raw_name\n\n    @property\n    def name(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#name\n        \"\"\"\n        if self._name is None:\n            self._name = normalize_project_name(self.raw_name)\n\n        return self._name\n\n    @property\n    def version(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#version\n        \"\"\"\n        version: str\n\n        if self._version is None:\n            if \"version\" not in self.config:\n                if not self._version_set and \"version\" not in self.dynamic:\n                    message = (\n                        \"Field `project.version` can only be resolved dynamically \"\n                        \"if `version` is in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                if \"version\" in self.dynamic:\n                    message = (\n                        \"Metadata field `version` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n\n                version = self.config[\"version\"]\n                if not isinstance(version, str):\n                    message = \"Field `project.version` must be a string\"\n                    raise TypeError(message)\n\n                self._version = version\n\n        return cast(str, self._version)\n\n    @property\n    def description(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#description\n        \"\"\"\n        if self._description is None:\n            if \"description\" in self.config:\n                description = self.config[\"description\"]\n                if \"description\" in self.dynamic:\n                    message = (\n                        \"Metadata field `description` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                description = \"\"\n\n            if not isinstance(description, str):\n                message = \"Field `project.description` must be a string\"\n                raise TypeError(message)\n            self._description = \" \".join(description.splitlines())\n\n        return self._description\n\n    @property\n    def readme(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#readme\n        \"\"\"\n        readme: str | dict[str, str] | None\n        content_type: str | None\n\n        if self._readme is None:\n            if \"readme\" in self.config:\n                readme = self.config[\"readme\"]\n                if \"readme\" in self.dynamic:\n                    message = (\n                        \"Metadata field `readme` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                readme = None\n\n            if readme is None:\n                self._readme = \"\"\n                self._readme_content_type = \"text/markdown\"\n                self._readme_path = \"\"\n            elif isinstance(readme, str):\n                normalized_path = readme.lower()\n                if normalized_path.endswith(\".md\"):\n                    content_type = \"text/markdown\"\n                elif normalized_path.endswith(\".rst\"):\n                    content_type = \"text/x-rst\"\n                elif normalized_path.endswith(\".txt\"):\n                    content_type = \"text/plain\"\n                else:\n                    message = f\"Unable to determine the content-type based on the extension of readme file: {readme}\"\n                    raise TypeError(message)\n\n                readme_path = os.path.normpath(os.path.join(self.root, readme))\n                if not os.path.isfile(readme_path):\n                    message = f\"Readme file does not exist: {readme}\"\n                    raise OSError(message)\n\n                with open(readme_path, encoding=\"utf-8\") as f:\n                    self._readme = f.read()\n\n                self._readme_content_type = content_type\n                self._readme_path = readme\n            elif isinstance(readme, dict):\n                content_type = readme.get(\"content-type\")\n                if content_type is None:\n                    message = \"Field `content-type` is required in the `project.readme` table\"\n                    raise ValueError(message)\n\n                if not isinstance(content_type, str):\n                    message = \"Field `content-type` in the `project.readme` table must be a string\"\n                    raise TypeError(message)\n\n                if content_type not in {\"text/markdown\", \"text/x-rst\", \"text/plain\"}:\n                    message = (\n                        \"Field `content-type` in the `project.readme` table must be one of the following: \"\n                        \"text/markdown, text/x-rst, text/plain\"\n                    )\n                    raise ValueError(message)\n\n                if \"file\" in readme and \"text\" in readme:\n                    message = \"Cannot specify both `file` and `text` in the `project.readme` table\"\n                    raise ValueError(message)\n\n                if \"file\" in readme:\n                    relative_path = readme[\"file\"]\n                    if not isinstance(relative_path, str):\n                        message = \"Field `file` in the `project.readme` table must be a string\"\n                        raise TypeError(message)\n\n                    path = os.path.normpath(os.path.join(self.root, relative_path))\n                    if not os.path.isfile(path):\n                        message = f\"Readme file does not exist: {relative_path}\"\n                        raise OSError(message)\n\n                    with open(path, encoding=readme.get(\"charset\", \"utf-8\")) as f:\n                        contents = f.read()\n\n                    readme_path = relative_path\n                elif \"text\" in readme:\n                    contents = readme[\"text\"]\n                    if not isinstance(contents, str):\n                        message = \"Field `text` in the `project.readme` table must be a string\"\n                        raise TypeError(message)\n\n                    readme_path = \"\"\n                else:\n                    message = \"Must specify either `file` or `text` in the `project.readme` table\"\n                    raise ValueError(message)\n\n                self._readme = contents\n                self._readme_content_type = content_type\n                self._readme_path = readme_path\n            else:\n                message = \"Field `project.readme` must be a string or a table\"\n                raise TypeError(message)\n\n        return self._readme\n\n    @property\n    def readme_content_type(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#readme\n        \"\"\"\n        if self._readme_content_type is None:\n            _ = self.readme\n\n        return cast(str, self._readme_content_type)\n\n    @property\n    def readme_path(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#readme\n        \"\"\"\n        if self._readme_path is None:\n            _ = self.readme\n\n        return cast(str, self._readme_path)\n\n    @property\n    def requires_python(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#requires-python\n        \"\"\"\n        if self._requires_python is None:\n            from packaging.specifiers import InvalidSpecifier, SpecifierSet\n\n            if \"requires-python\" in self.config:\n                requires_python = self.config[\"requires-python\"]\n                if \"requires-python\" in self.dynamic:\n                    message = (\n                        \"Metadata field `requires-python` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                requires_python = \"\"\n\n            if not isinstance(requires_python, str):\n                message = \"Field `project.requires-python` must be a string\"\n                raise TypeError(message)\n\n            try:\n                self._python_constraint = SpecifierSet(requires_python)\n            except InvalidSpecifier as e:\n                message = f\"Field `project.requires-python` is invalid: {e}\"\n                raise ValueError(message) from None\n\n            self._requires_python = str(self._python_constraint)\n\n        return self._requires_python\n\n    @property\n    def python_constraint(self) -> SpecifierSet:\n        from packaging.specifiers import SpecifierSet\n\n        if self._python_constraint is None:\n            _ = self.requires_python\n\n        return cast(SpecifierSet, self._python_constraint)\n\n    @property\n    def license(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0621/#license\n        \"\"\"\n        if self._license is None:\n            if \"license\" in self.config:\n                data = self.config[\"license\"]\n                if \"license\" in self.dynamic:\n                    message = (\n                        \"Metadata field `license` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                data = None\n\n            if data is None:\n                self._license = \"\"\n                self._license_expression = \"\"\n            elif isinstance(data, str):\n                from packaging.licenses import canonicalize_license_expression\n\n                try:\n                    self._license_expression = str(canonicalize_license_expression(data))\n                except ValueError as e:\n                    message = f\"Error parsing field `project.license` - {e}\"\n                    raise ValueError(message) from None\n\n                self._license = \"\"\n            elif isinstance(data, dict):\n                if \"file\" in data and \"text\" in data:\n                    message = \"Cannot specify both `file` and `text` in the `project.license` table\"\n                    raise ValueError(message)\n\n                if \"file\" in data:\n                    relative_path = data[\"file\"]\n                    if not isinstance(relative_path, str):\n                        message = \"Field `file` in the `project.license` table must be a string\"\n                        raise TypeError(message)\n\n                    path = os.path.normpath(os.path.join(self.root, relative_path))\n                    if not os.path.isfile(path):\n                        message = f\"License file does not exist: {relative_path}\"\n                        raise OSError(message)\n\n                    with open(path, encoding=\"utf-8\") as f:\n                        contents = f.read()\n                elif \"text\" in data:\n                    contents = data[\"text\"]\n                    if not isinstance(contents, str):\n                        message = \"Field `text` in the `project.license` table must be a string\"\n                        raise TypeError(message)\n                else:\n                    message = \"Must specify either `file` or `text` in the `project.license` table\"\n                    raise ValueError(message)\n\n                self._license = contents\n                self._license_expression = \"\"\n            else:\n                message = \"Field `project.license` must be a string or a table\"\n                raise TypeError(message)\n\n        return self._license\n\n    @property\n    def license_expression(self) -> str:\n        \"\"\"\n        https://peps.python.org/pep-0639/\n        \"\"\"\n        if self._license_expression is None:\n            _ = self.license\n\n        return cast(str, self._license_expression)\n\n    @property\n    def license_files(self) -> list[str]:\n        \"\"\"\n        https://peps.python.org/pep-0639/\n        \"\"\"\n        if self._license_files is None:\n            if \"license-files\" in self.config:\n                globs = self.config[\"license-files\"]\n                if \"license-files\" in self.dynamic:\n                    message = (\n                        \"Metadata field `license-files` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n\n                if isinstance(globs, dict):\n                    globs = globs.get(\"globs\", globs.get(\"paths\", []))\n            else:\n                globs = [\"LICEN[CS]E*\", \"COPYING*\", \"NOTICE*\", \"AUTHORS*\"]\n\n            from glob import glob\n\n            license_files: list[str] = []\n            if not isinstance(globs, list):\n                message = \"Field `project.license-files` must be an array\"\n                raise TypeError(message)\n\n            for i, pattern in enumerate(globs, 1):\n                if not isinstance(pattern, str):\n                    message = f\"Entry #{i} of field `project.license-files` must be a string\"\n                    raise TypeError(message)\n\n                full_pattern = os.path.normpath(os.path.join(self.root, pattern))\n                license_files.extend(\n                    os.path.relpath(path, self.root).replace(\"\\\\\", \"/\")\n                    for path in glob(full_pattern)\n                    if os.path.isfile(path)\n                )\n\n            self._license_files = sorted(license_files)\n\n        return self._license_files\n\n    @property\n    def authors(self) -> list[str]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#authors-maintainers\n        \"\"\"\n        authors: list[str]\n        authors_data: dict[str, list[str]]\n\n        if self._authors is None:\n            if \"authors\" in self.config:\n                authors = self.config[\"authors\"]\n                if \"authors\" in self.dynamic:\n                    message = (\n                        \"Metadata field `authors` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                authors = []\n\n            if not isinstance(authors, list):\n                message = \"Field `project.authors` must be an array\"\n                raise TypeError(message)\n\n            from email.headerregistry import Address\n\n            authors = deepcopy(authors)\n            authors_data = {\"name\": [], \"email\": []}\n\n            for i, data in enumerate(authors, 1):\n                if not isinstance(data, dict):\n                    message = f\"Author #{i} of field `project.authors` must be an inline table\"\n                    raise TypeError(message)\n\n                name = data.get(\"name\", \"\")\n                if not isinstance(name, str):\n                    message = f\"Name of author #{i} of field `project.authors` must be a string\"\n                    raise TypeError(message)\n\n                email = data.get(\"email\", \"\")\n                if not isinstance(email, str):\n                    message = f\"Email of author #{i} of field `project.authors` must be a string\"\n                    raise TypeError(message)\n\n                if name and email:\n                    authors_data[\"email\"].append(str(Address(display_name=name, addr_spec=email)))\n                elif email:\n                    authors_data[\"email\"].append(str(Address(addr_spec=email)))\n                elif name:\n                    authors_data[\"name\"].append(name)\n                else:\n                    message = f\"Author #{i} of field `project.authors` must specify either `name` or `email`\"\n                    raise ValueError(message)\n\n            self._authors = authors\n            self._authors_data = authors_data\n\n        return self._authors\n\n    @property\n    def authors_data(self) -> dict[str, list[str]]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#authors-maintainers\n        \"\"\"\n        if self._authors_data is None:\n            _ = self.authors\n\n        return cast(dict, self._authors_data)\n\n    @property\n    def maintainers(self) -> list[str]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#authors-maintainers\n        \"\"\"\n        maintainers: list[str]\n\n        if self._maintainers is None:\n            if \"maintainers\" in self.config:\n                maintainers = self.config[\"maintainers\"]\n                if \"maintainers\" in self.dynamic:\n                    message = (\n                        \"Metadata field `maintainers` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                maintainers = []\n\n            if not isinstance(maintainers, list):\n                message = \"Field `project.maintainers` must be an array\"\n                raise TypeError(message)\n\n            from email.headerregistry import Address\n\n            maintainers = deepcopy(maintainers)\n            maintainers_data: dict[str, list[str]] = {\"name\": [], \"email\": []}\n\n            for i, data in enumerate(maintainers, 1):\n                if not isinstance(data, dict):\n                    message = f\"Maintainer #{i} of field `project.maintainers` must be an inline table\"\n                    raise TypeError(message)\n\n                name = data.get(\"name\", \"\")\n                if not isinstance(name, str):\n                    message = f\"Name of maintainer #{i} of field `project.maintainers` must be a string\"\n                    raise TypeError(message)\n\n                email = data.get(\"email\", \"\")\n                if not isinstance(email, str):\n                    message = f\"Email of maintainer #{i} of field `project.maintainers` must be a string\"\n                    raise TypeError(message)\n\n                if name and email:\n                    maintainers_data[\"email\"].append(str(Address(display_name=name, addr_spec=email)))\n                elif email:\n                    maintainers_data[\"email\"].append(str(Address(addr_spec=email)))\n                elif name:\n                    maintainers_data[\"name\"].append(name)\n                else:\n                    message = f\"Maintainer #{i} of field `project.maintainers` must specify either `name` or `email`\"\n                    raise ValueError(message)\n\n            self._maintainers = maintainers\n            self._maintainers_data = maintainers_data\n\n        return self._maintainers\n\n    @property\n    def maintainers_data(self) -> dict[str, list[str]]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#authors-maintainers\n        \"\"\"\n        if self._maintainers_data is None:\n            _ = self.maintainers\n\n        return cast(dict, self._maintainers_data)\n\n    @property\n    def keywords(self) -> list[str]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#keywords\n        \"\"\"\n        if self._keywords is None:\n            if \"keywords\" in self.config:\n                keywords = self.config[\"keywords\"]\n                if \"keywords\" in self.dynamic:\n                    message = (\n                        \"Metadata field `keywords` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                keywords = []\n\n            if not isinstance(keywords, list):\n                message = \"Field `project.keywords` must be an array\"\n                raise TypeError(message)\n\n            unique_keywords = set()\n\n            for i, keyword in enumerate(keywords, 1):\n                if not isinstance(keyword, str):\n                    message = f\"Keyword #{i} of field `project.keywords` must be a string\"\n                    raise TypeError(message)\n\n                unique_keywords.add(keyword)\n\n            self._keywords = sorted(unique_keywords)\n\n        return self._keywords\n\n    @property\n    def classifiers(self) -> list[str]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#classifiers\n        \"\"\"\n        if self._classifiers is None:\n            import bisect\n\n            if \"classifiers\" in self.config:\n                classifiers = self.config[\"classifiers\"]\n                if \"classifiers\" in self.dynamic:\n                    message = (\n                        \"Metadata field `classifiers` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                classifiers = []\n\n            if not isinstance(classifiers, list):\n                message = \"Field `project.classifiers` must be an array\"\n                raise TypeError(message)\n\n            verify_classifiers = not os.environ.get(\"HATCH_METADATA_CLASSIFIERS_NO_VERIFY\")\n            if verify_classifiers:\n                import trove_classifiers\n\n                known_classifiers = trove_classifiers.classifiers | self._extra_classifiers\n                sorted_classifiers = list(trove_classifiers.sorted_classifiers)\n\n                for classifier in sorted(self._extra_classifiers - trove_classifiers.classifiers):\n                    bisect.insort(sorted_classifiers, classifier)\n\n            unique_classifiers = set()\n\n            for i, classifier in enumerate(classifiers, 1):\n                if not isinstance(classifier, str):\n                    message = f\"Classifier #{i} of field `project.classifiers` must be a string\"\n                    raise TypeError(message)\n\n                if (\n                    not self.__classifier_is_private(classifier)\n                    and verify_classifiers\n                    and classifier not in known_classifiers\n                ):\n                    message = f\"Unknown classifier in field `project.classifiers`: {classifier}\"\n                    raise ValueError(message)\n\n                unique_classifiers.add(classifier)\n\n            if not verify_classifiers:\n                import re\n\n                # combined text-numeric sort that ensures that Python versions sort correctly\n                split_re = re.compile(r\"(\\D*)(\\d*)\")\n                sorted_classifiers = sorted(\n                    classifiers,\n                    key=lambda value: ([(a, int(b) if b else None) for a, b in split_re.findall(value)]),\n                )\n\n            self._classifiers = sorted(\n                unique_classifiers, key=lambda c: -1 if self.__classifier_is_private(c) else sorted_classifiers.index(c)\n            )\n\n        return self._classifiers\n\n    @property\n    def urls(self) -> dict[str, str]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#urls\n        \"\"\"\n        if self._urls is None:\n            if \"urls\" in self.config:\n                urls = self.config[\"urls\"]\n                if \"urls\" in self.dynamic:\n                    message = (\n                        \"Metadata field `urls` cannot be both statically defined and listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                urls = {}\n\n            if not isinstance(urls, dict):\n                message = \"Field `project.urls` must be a table\"\n                raise TypeError(message)\n\n            sorted_urls = {}\n\n            for label, url in urls.items():\n                if not isinstance(url, str):\n                    message = f\"URL `{label}` of field `project.urls` must be a string\"\n                    raise TypeError(message)\n\n                sorted_urls[label] = url\n\n            self._urls = sorted_urls\n\n        return self._urls\n\n    @property\n    def scripts(self) -> dict[str, str]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#entry-points\n        \"\"\"\n        if self._scripts is None:\n            if \"scripts\" in self.config:\n                scripts = self.config[\"scripts\"]\n                if \"scripts\" in self.dynamic:\n                    message = (\n                        \"Metadata field `scripts` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                scripts = {}\n\n            if not isinstance(scripts, dict):\n                message = \"Field `project.scripts` must be a table\"\n                raise TypeError(message)\n\n            sorted_scripts = {}\n\n            for name, object_ref in sorted(scripts.items()):\n                if not isinstance(object_ref, str):\n                    message = f\"Object reference `{name}` of field `project.scripts` must be a string\"\n                    raise TypeError(message)\n\n                sorted_scripts[name] = object_ref\n\n            self._scripts = sorted_scripts\n\n        return self._scripts\n\n    @property\n    def gui_scripts(self) -> dict[str, str]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#entry-points\n        \"\"\"\n        if self._gui_scripts is None:\n            if \"gui-scripts\" in self.config:\n                gui_scripts = self.config[\"gui-scripts\"]\n                if \"gui-scripts\" in self.dynamic:\n                    message = (\n                        \"Metadata field `gui-scripts` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                gui_scripts = {}\n\n            if not isinstance(gui_scripts, dict):\n                message = \"Field `project.gui-scripts` must be a table\"\n                raise TypeError(message)\n\n            sorted_gui_scripts = {}\n\n            for name, object_ref in sorted(gui_scripts.items()):\n                if not isinstance(object_ref, str):\n                    message = f\"Object reference `{name}` of field `project.gui-scripts` must be a string\"\n                    raise TypeError(message)\n\n                sorted_gui_scripts[name] = object_ref\n\n            self._gui_scripts = sorted_gui_scripts\n\n        return self._gui_scripts\n\n    @property\n    def entry_points(self) -> dict[str, dict[str, str]]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#entry-points\n        \"\"\"\n        if self._entry_points is None:\n            if \"entry-points\" in self.config:\n                defined_entry_point_groups = self.config[\"entry-points\"]\n                if \"entry-points\" in self.dynamic:\n                    message = (\n                        \"Metadata field `entry-points` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                defined_entry_point_groups = {}\n\n            if not isinstance(defined_entry_point_groups, dict):\n                message = \"Field `project.entry-points` must be a table\"\n                raise TypeError(message)\n\n            for forbidden_field, expected_field in ((\"console_scripts\", \"scripts\"), (\"gui-scripts\", \"gui-scripts\")):\n                if forbidden_field in defined_entry_point_groups:\n                    message = (\n                        f\"Field `{forbidden_field}` must be defined as `project.{expected_field}` \"\n                        f\"instead of in the `project.entry-points` table\"\n                    )\n                    raise ValueError(message)\n\n            entry_point_groups = {}\n\n            for group, entry_point_data in sorted(defined_entry_point_groups.items()):\n                if not isinstance(entry_point_data, dict):\n                    message = f\"Field `project.entry-points.{group}` must be a table\"\n                    raise TypeError(message)\n\n                entry_points = {}\n\n                for name, object_ref in sorted(entry_point_data.items()):\n                    if not isinstance(object_ref, str):\n                        message = f\"Object reference `{name}` of field `project.entry-points.{group}` must be a string\"\n                        raise TypeError(message)\n\n                    entry_points[name] = object_ref\n\n                if entry_points:\n                    entry_point_groups[group] = entry_points\n\n            self._entry_points = entry_point_groups\n\n        return self._entry_points\n\n    @property\n    def dependencies_complex(self) -> dict[str, Requirement]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#dependencies-optional-dependencies\n        \"\"\"\n        if self._dependencies_complex is None:\n            from packaging.requirements import InvalidRequirement, Requirement\n\n            if \"dependencies\" in self.config:\n                dependencies = self.config[\"dependencies\"]\n                if \"dependencies\" in self.dynamic:\n                    message = (\n                        \"Metadata field `dependencies` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                dependencies = []\n\n            if not isinstance(dependencies, list):\n                message = \"Field `project.dependencies` must be an array\"\n                raise TypeError(message)\n\n            dependencies_complex = {}\n\n            for i, entry in enumerate(dependencies, 1):\n                if not isinstance(entry, str):\n                    message = f\"Dependency #{i} of field `project.dependencies` must be a string\"\n                    raise TypeError(message)\n\n                try:\n                    requirement = Requirement(self.context.format(entry))\n                except InvalidRequirement as e:\n                    message = f\"Dependency #{i} of field `project.dependencies` is invalid: {e}\"\n                    raise ValueError(message) from None\n                else:\n                    if requirement.url and not self.hatch_metadata.allow_direct_references:\n                        message = (\n                            f\"Dependency #{i} of field `project.dependencies` cannot be a direct reference unless \"\n                            f\"field `tool.hatch.metadata.allow-direct-references` is set to `true`\"\n                        )\n                        raise ValueError(message)\n\n                    normalize_requirement(requirement)\n                    dependencies_complex[format_dependency(requirement)] = requirement\n\n            self._dependencies_complex = dict(sorted(dependencies_complex.items()))\n\n        return self._dependencies_complex\n\n    @property\n    def dependencies(self) -> list[str]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#dependencies-optional-dependencies\n        \"\"\"\n        if self._dependencies is None:\n            self._dependencies = list(self.dependencies_complex)\n\n        return self._dependencies\n\n    @property\n    def optional_dependencies_complex(self) -> dict[str, dict[str, Requirement]]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#dependencies-optional-dependencies\n        \"\"\"\n        if self._optional_dependencies_complex is None:\n            from packaging.requirements import InvalidRequirement, Requirement\n\n            if \"optional-dependencies\" in self.config:\n                optional_dependencies = self.config[\"optional-dependencies\"]\n                if \"optional-dependencies\" in self.dynamic:\n                    message = (\n                        \"Metadata field `optional-dependencies` cannot be both statically defined and \"\n                        \"listed in field `project.dynamic`\"\n                    )\n                    raise ValueError(message)\n            else:\n                optional_dependencies = {}\n\n            if not isinstance(optional_dependencies, dict):\n                message = \"Field `project.optional-dependencies` must be a table\"\n                raise TypeError(message)\n\n            normalized_options: dict[str, str] = {}\n            optional_dependency_entries = {}\n            inherited_options: dict[str, set[str]] = {}\n\n            for option, dependencies in optional_dependencies.items():\n                if not is_valid_project_name(option):\n                    message = (\n                        f\"Optional dependency group `{option}` of field `project.optional-dependencies` must only \"\n                        f\"contain ASCII letters/digits, underscores, hyphens, and periods, and must begin and end with \"\n                        f\"ASCII letters/digits.\"\n                    )\n                    raise ValueError(message)\n\n                normalized_option = (\n                    option if self.hatch_metadata.allow_ambiguous_features else normalize_project_name(option)\n                )\n                if normalized_option in normalized_options:\n                    message = (\n                        f\"Optional dependency groups `{normalized_options[normalized_option]}` and `{option}` of \"\n                        f\"field `project.optional-dependencies` both evaluate to `{normalized_option}`.\"\n                    )\n                    raise ValueError(message)\n\n                if not isinstance(dependencies, list):\n                    message = (\n                        f\"Dependencies for option `{option}` of field `project.optional-dependencies` must be an array\"\n                    )\n                    raise TypeError(message)\n\n                entries = {}\n\n                for i, entry in enumerate(dependencies, 1):\n                    if not isinstance(entry, str):\n                        message = (\n                            f\"Dependency #{i} of option `{option}` of field `project.optional-dependencies` \"\n                            f\"must be a string\"\n                        )\n                        raise TypeError(message)\n\n                    try:\n                        requirement = Requirement(self.context.format(entry))\n                    except InvalidRequirement as e:\n                        message = (\n                            f\"Dependency #{i} of option `{option}` of field `project.optional-dependencies` \"\n                            f\"is invalid: {e}\"\n                        )\n                        raise ValueError(message) from None\n                    else:\n                        if requirement.url and not self.hatch_metadata.allow_direct_references:\n                            message = (\n                                f\"Dependency #{i} of option `{option}` of field `project.optional-dependencies` \"\n                                f\"cannot be a direct reference unless field \"\n                                f\"`tool.hatch.metadata.allow-direct-references` is set to `true`\"\n                            )\n                            raise ValueError(message)\n\n                        normalize_requirement(requirement)\n                        if requirement.name == self.name:\n                            if normalized_option in inherited_options:\n                                inherited_options[normalized_option].update(requirement.extras)\n                            else:\n                                inherited_options[normalized_option] = set(requirement.extras)\n                        else:\n                            entries[format_dependency(requirement)] = requirement\n\n                normalized_options[normalized_option] = option\n                optional_dependency_entries[normalized_option] = entries\n\n            visited: set[str] = set()\n            resolved: set[str] = set()\n            for dependent_option in inherited_options:\n                _resolve_optional_dependencies(\n                    optional_dependency_entries, dependent_option, inherited_options, visited, resolved\n                )\n\n            self._optional_dependencies_complex = {\n                option: dict(sorted(entries.items())) for option, entries in sorted(optional_dependency_entries.items())\n            }\n\n        return self._optional_dependencies_complex\n\n    @property\n    def optional_dependencies(self) -> dict[str, list[str]]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#dependencies-optional-dependencies\n        \"\"\"\n        if self._optional_dependencies is None:\n            self._optional_dependencies = {\n                option: list(entries) for option, entries in self.optional_dependencies_complex.items()\n            }\n\n        return self._optional_dependencies\n\n    @property\n    def dynamic(self) -> list[str]:\n        \"\"\"\n        https://peps.python.org/pep-0621/#dynamic\n        \"\"\"\n        if self._dynamic is None:\n            dynamic = self.config.get(\"dynamic\", [])\n\n            if not isinstance(dynamic, list):\n                message = \"Field `project.dynamic` must be an array\"\n                raise TypeError(message)\n\n            if not all(isinstance(entry, str) for entry in dynamic):\n                message = \"Field `project.dynamic` must only contain strings\"\n                raise TypeError(message)\n\n            self._dynamic = sorted(dynamic)\n\n        return self._dynamic\n\n    def add_known_classifiers(self, classifiers: list[str]) -> None:\n        self._extra_classifiers.update(classifiers)\n\n    def validate_fields(self) -> None:\n        # Trigger validation for everything\n        for attribute in dir(self):\n            getattr(self, attribute)\n\n    @staticmethod\n    def __classifier_is_private(classifier: str) -> bool:\n        return classifier.lower().startswith(\"private ::\")\n\n\nclass HatchMetadata(Generic[PluginManagerBound]):\n    def __init__(self, root: str, config: dict[str, dict[str, Any]], plugin_manager: PluginManagerBound) -> None:\n        self.root = root\n        self.config = config\n        self.plugin_manager = plugin_manager\n\n        self._metadata: HatchMetadataSettings | None = None\n        self._build_config: dict[str, Any] | None = None\n        self._build_targets: dict[str, Any] | None = None\n        self._version: HatchVersionConfig | None = None\n\n    @property\n    def metadata(self) -> HatchMetadataSettings:\n        if self._metadata is None:\n            metadata_config = self.config.get(\"metadata\", {})\n            if not isinstance(metadata_config, dict):\n                message = \"Field `tool.hatch.metadata` must be a table\"\n                raise TypeError(message)\n\n            self._metadata = HatchMetadataSettings(self.root, metadata_config, self.plugin_manager)\n\n        return self._metadata\n\n    @property\n    def build_config(self) -> dict[str, Any]:\n        if self._build_config is None:\n            build_config = self.config.get(\"build\", {})\n            if not isinstance(build_config, dict):\n                message = \"Field `tool.hatch.build` must be a table\"\n                raise TypeError(message)\n\n            self._build_config = build_config\n\n        return self._build_config\n\n    @property\n    def build_targets(self) -> dict[str, Any]:\n        if self._build_targets is None:\n            build_targets: dict = self.build_config.get(\"targets\", {})\n            if not isinstance(build_targets, dict):\n                message = \"Field `tool.hatch.build.targets` must be a table\"\n                raise TypeError(message)\n\n            self._build_targets = build_targets\n\n        return self._build_targets\n\n    @property\n    def version(self) -> HatchVersionConfig:\n        if self._version is None:\n            if \"version\" not in self.config:\n                message = \"Missing `tool.hatch.version` configuration\"\n                raise ValueError(message)\n\n            options = self.config[\"version\"]\n            if not isinstance(options, dict):\n                message = \"Field `tool.hatch.version` must be a table\"\n                raise TypeError(message)\n\n            self._version = HatchVersionConfig(self.root, deepcopy(options), self.plugin_manager)\n\n        return self._version\n\n\nclass HatchVersionConfig(Generic[PluginManagerBound]):\n    def __init__(self, root: str, config: dict[str, Any], plugin_manager: PluginManagerBound) -> None:\n        self.root = root\n        self.config = config\n        self.plugin_manager = plugin_manager\n\n        self._cached: str | None = None\n        self._source_name: str | None = None\n        self._scheme_name: str | None = None\n        self._source: VersionSourceInterface | None = None\n        self._scheme: VersionSchemeInterface | None = None\n\n    @property\n    def cached(self) -> str:\n        if self._cached is None:\n            try:\n                self._cached = self.source.get_version_data()[\"version\"]\n            except Exception as e:  # noqa: BLE001\n                message = f\"Error getting the version from source `{self.source.PLUGIN_NAME}`: {e}\"\n                raise type(e)(message) from None\n\n        return self._cached\n\n    @property\n    def source_name(self) -> str:\n        if self._source_name is None:\n            source: str = self.config.get(\"source\", \"regex\")\n            if not source:\n                message = \"The `source` option under the `tool.hatch.version` table must not be empty if defined\"\n                raise ValueError(message)\n\n            if not isinstance(source, str):\n                message = \"Field `tool.hatch.version.source` must be a string\"\n                raise TypeError(message)\n\n            self._source_name = source\n\n        return self._source_name\n\n    @property\n    def scheme_name(self) -> str:\n        if self._scheme_name is None:\n            scheme: str = self.config.get(\"scheme\", \"standard\")\n            if not scheme:\n                message = \"The `scheme` option under the `tool.hatch.version` table must not be empty if defined\"\n                raise ValueError(message)\n\n            if not isinstance(scheme, str):\n                message = \"Field `tool.hatch.version.scheme` must be a string\"\n                raise TypeError(message)\n\n            self._scheme_name = scheme\n\n        return self._scheme_name\n\n    @property\n    def source(self) -> VersionSourceInterface:\n        if self._source is None:\n            from copy import deepcopy\n\n            source_name = self.source_name\n            version_source = self.plugin_manager.version_source.get(source_name)\n            if version_source is None:\n                from hatchling.plugin.exceptions import UnknownPluginError\n\n                message = f\"Unknown version source: {source_name}\"\n                raise UnknownPluginError(message)\n\n            self._source = version_source(self.root, deepcopy(self.config))\n\n        return self._source\n\n    @property\n    def scheme(self) -> VersionSchemeInterface:\n        if self._scheme is None:\n            from copy import deepcopy\n\n            scheme_name = self.scheme_name\n            version_scheme = self.plugin_manager.version_scheme.get(scheme_name)\n            if version_scheme is None:\n                from hatchling.plugin.exceptions import UnknownPluginError\n\n                message = f\"Unknown version scheme: {scheme_name}\"\n                raise UnknownPluginError(message)\n\n            self._scheme = version_scheme(self.root, deepcopy(self.config))\n\n        return self._scheme\n\n\nclass HatchMetadataSettings(Generic[PluginManagerBound]):\n    def __init__(self, root: str, config: dict[str, Any], plugin_manager: PluginManagerBound) -> None:\n        self.root = root\n        self.config = config\n        self.plugin_manager = plugin_manager\n\n        self._allow_direct_references: bool | None = None\n        self._allow_ambiguous_features: bool | None = None\n        self._hook_config: dict[str, Any] | None = None\n        self._hooks: dict[str, MetadataHookInterface] | None = None\n\n    @property\n    def allow_direct_references(self) -> bool:\n        if self._allow_direct_references is None:\n            allow_direct_references: bool = self.config.get(\"allow-direct-references\", False)\n            if not isinstance(allow_direct_references, bool):\n                message = \"Field `tool.hatch.metadata.allow-direct-references` must be a boolean\"\n                raise TypeError(message)\n\n            self._allow_direct_references = allow_direct_references\n\n        return self._allow_direct_references\n\n    @property\n    def allow_ambiguous_features(self) -> bool:\n        # TODO: remove in the first minor release after Jan 1, 2024\n        if self._allow_ambiguous_features is None:\n            allow_ambiguous_features: bool = self.config.get(\"allow-ambiguous-features\", False)\n            if not isinstance(allow_ambiguous_features, bool):\n                message = \"Field `tool.hatch.metadata.allow-ambiguous-features` must be a boolean\"\n                raise TypeError(message)\n\n            self._allow_ambiguous_features = allow_ambiguous_features\n\n        return self._allow_ambiguous_features\n\n    @property\n    def hook_config(self) -> dict[str, Any]:\n        if self._hook_config is None:\n            hook_config: dict[str, Any] = self.config.get(\"hooks\", {})\n            if not isinstance(hook_config, dict):\n                message = \"Field `tool.hatch.metadata.hooks` must be a table\"\n                raise TypeError(message)\n\n            self._hook_config = hook_config\n\n        return self._hook_config\n\n    @property\n    def hooks(self) -> dict[str, MetadataHookInterface]:\n        if self._hooks is None:\n            hook_config = self.hook_config\n\n            configured_hooks = {}\n            for hook_name, config in hook_config.items():\n                metadata_hook = self.plugin_manager.metadata_hook.get(hook_name)\n                if metadata_hook is None:\n                    from hatchling.plugin.exceptions import UnknownPluginError\n\n                    message = f\"Unknown metadata hook: {hook_name}\"\n                    raise UnknownPluginError(message)\n\n                configured_hooks[hook_name] = metadata_hook(self.root, config)\n\n            self._hooks = configured_hooks\n\n        return self._hooks\n\n\ndef _resolve_optional_dependencies(\n    optional_dependencies_complex, dependent_option, inherited_options, visited, resolved\n):\n    if dependent_option in resolved:\n        return\n\n    if dependent_option in visited:\n        message = f\"Field `project.optional-dependencies` defines a circular dependency group: {dependent_option}\"\n        raise ValueError(message)\n\n    visited.add(dependent_option)\n    if dependent_option in inherited_options:\n        for selected_option in inherited_options[dependent_option]:\n            _resolve_optional_dependencies(\n                optional_dependencies_complex, selected_option, inherited_options, visited, resolved\n            )\n            if selected_option not in optional_dependencies_complex:\n                message = (\n                    f\"Unknown recursive dependency group in field `project.optional-dependencies`: {selected_option}\"\n                )\n                raise ValueError(message)\n\n            optional_dependencies_complex[dependent_option].update(optional_dependencies_complex[selected_option])\n\n    resolved.add(dependent_option)\n    visited.remove(dependent_option)\n"
  },
  {
    "path": "backend/src/hatchling/metadata/custom.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom typing import Any\n\nfrom hatchling.metadata.plugin.interface import MetadataHookInterface\nfrom hatchling.plugin.utils import load_plugin_from_script\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\n\nclass CustomMetadataHook:\n    PLUGIN_NAME = \"custom\"\n\n    def __new__(  # type: ignore[misc]\n        cls,\n        root: str,\n        config: dict[str, Any],\n        *args: Any,\n        **kwargs: Any,\n    ) -> MetadataHookInterface:\n        build_script = config.get(\"path\", DEFAULT_BUILD_SCRIPT)\n        if not isinstance(build_script, str):\n            message = f\"Option `path` for metadata hook `{cls.PLUGIN_NAME}` must be a string\"\n            raise TypeError(message)\n\n        if not build_script:\n            message = f\"Option `path` for metadata hook `{cls.PLUGIN_NAME}` must not be empty if defined\"\n            raise ValueError(message)\n\n        path = os.path.normpath(os.path.join(root, build_script))\n        if not os.path.isfile(path):\n            message = f\"Build script does not exist: {build_script}\"\n            raise OSError(message)\n\n        hook_class = load_plugin_from_script(path, build_script, MetadataHookInterface, \"metadata_hook\")  # type: ignore[type-abstract]\n        hook = hook_class(root, config, *args, **kwargs)\n\n        # Always keep the name to avoid confusion\n        hook.PLUGIN_NAME = cls.PLUGIN_NAME\n\n        return hook\n"
  },
  {
    "path": "backend/src/hatchling/metadata/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/metadata/plugin/hooks.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom hatchling.metadata.custom import CustomMetadataHook\nfrom hatchling.plugin import hookimpl\n\nif TYPE_CHECKING:\n    from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n\n@hookimpl\ndef hatch_register_metadata_hook() -> type[MetadataHookInterface]:\n    return CustomMetadataHook  # type: ignore[return-value]\n"
  },
  {
    "path": "backend/src/hatchling/metadata/plugin/interface.py",
    "content": "from __future__ import annotations\n\nfrom abc import ABC, abstractmethod\n\n\nclass MetadataHookInterface(ABC):  # no cov\n    \"\"\"\n    Example usage:\n\n    ```python tab=\"plugin.py\"\n    from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n\n    class SpecialMetadataHook(MetadataHookInterface):\n        PLUGIN_NAME = \"special\"\n        ...\n    ```\n\n    ```python tab=\"hooks.py\"\n    from hatchling.plugin import hookimpl\n\n    from .plugin import SpecialMetadataHook\n\n\n    @hookimpl\n    def hatch_register_metadata_hook():\n        return SpecialMetadataHook\n    ```\n    \"\"\"\n\n    PLUGIN_NAME = \"\"\n    \"\"\"The name used for selection.\"\"\"\n\n    def __init__(self, root: str, config: dict) -> None:\n        self.__root = root\n        self.__config = config\n\n    @property\n    def root(self) -> str:\n        \"\"\"\n        The root of the project tree.\n        \"\"\"\n        return self.__root\n\n    @property\n    def config(self) -> dict:\n        \"\"\"\n        The hook configuration.\n\n        ```toml config-example\n        [tool.hatch.metadata.hooks.<PLUGIN_NAME>]\n        ```\n        \"\"\"\n        return self.__config\n\n    @abstractmethod\n    def update(self, metadata: dict) -> None:\n        \"\"\"\n        This updates the metadata mapping of the `project` table in-place.\n        \"\"\"\n\n    def get_known_classifiers(self) -> list[str]:  # noqa: PLR6301\n        \"\"\"\n        This returns extra classifiers that should be considered valid in addition to the ones known to PyPI.\n        \"\"\"\n        return []\n"
  },
  {
    "path": "backend/src/hatchling/metadata/spec.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any\n\nif TYPE_CHECKING:\n    from collections.abc import Callable\n\n    from hatchling.metadata.core import ProjectMetadata\n\nDEFAULT_METADATA_VERSION = \"2.4\"\nLATEST_METADATA_VERSION = \"2.4\"\nCORE_METADATA_PROJECT_FIELDS = {\n    \"Author\": (\"authors\",),\n    \"Author-email\": (\"authors\",),\n    \"Classifier\": (\"classifiers\",),\n    \"Description\": (\"readme\",),\n    \"Description-Content-Type\": (\"readme\",),\n    \"Dynamic\": (\"dynamic\",),\n    \"Keywords\": (\"keywords\",),\n    \"License\": (\"license\",),\n    \"License-Expression\": (\"license\",),\n    \"License-Files\": (\"license-files\",),\n    \"Maintainer\": (\"maintainers\",),\n    \"Maintainer-email\": (\"maintainers\",),\n    \"Name\": (\"name\",),\n    \"Provides-Extra\": (\"dependencies\", \"optional-dependencies\"),\n    \"Requires-Dist\": (\"dependencies\",),\n    \"Requires-Python\": (\"requires-python\",),\n    \"Summary\": (\"description\",),\n    \"Project-URL\": (\"urls\",),\n    \"Version\": (\"version\",),\n}\nPROJECT_CORE_METADATA_FIELDS = {\n    \"authors\": (\"Author\", \"Author-email\"),\n    \"classifiers\": (\"Classifier\",),\n    \"dependencies\": (\"Requires-Dist\",),\n    \"dynamic\": (\"Dynamic\",),\n    \"keywords\": (\"Keywords\",),\n    \"license\": (\"License\", \"License-Expression\"),\n    \"license-files\": (\"License-Files\",),\n    \"maintainers\": (\"Maintainer\", \"Maintainer-email\"),\n    \"name\": (\"Name\",),\n    \"optional-dependencies\": (\"Requires-Dist\", \"Provides-Extra\"),\n    \"readme\": (\"Description\", \"Description-Content-Type\"),\n    \"requires-python\": (\"Requires-Python\",),\n    \"description\": (\"Summary\",),\n    \"urls\": (\"Project-URL\",),\n    \"version\": (\"Version\",),\n}\n\n\ndef get_core_metadata_constructors() -> dict[str, Callable]:\n    \"\"\"\n    https://packaging.python.org/specifications/core-metadata/\n    \"\"\"\n    return {\n        \"1.2\": construct_metadata_file_1_2,\n        \"2.1\": construct_metadata_file_2_1,\n        \"2.2\": construct_metadata_file_2_2,\n        \"2.3\": construct_metadata_file_2_3,\n        \"2.4\": construct_metadata_file_2_4,\n    }\n\n\ndef project_metadata_from_core_metadata(core_metadata: str) -> dict[str, Any]:\n    # https://packaging.python.org/en/latest/specifications/core-metadata/\n    import email\n    from email.headerregistry import HeaderRegistry\n\n    header_registry = HeaderRegistry()\n\n    message = email.message_from_string(core_metadata)\n    metadata: dict[str, Any] = {}\n\n    if name := message.get(\"Name\"):\n        metadata[\"name\"] = name\n    else:\n        error_message = \"Missing required core metadata: Name\"\n        raise ValueError(error_message)\n\n    if version := message.get(\"Version\"):\n        metadata[\"version\"] = version\n    else:\n        error_message = \"Missing required core metadata: Version\"\n        raise ValueError(error_message)\n\n    if (dynamic_fields := message.get_all(\"Dynamic\")) is not None:\n        # Use as an ordered set to retain bidirectional formatting.\n        # This likely doesn't matter but we try hard around here.\n        metadata[\"dynamic\"] = list({\n            project_field: None\n            for core_metadata_field in dynamic_fields\n            for project_field in CORE_METADATA_PROJECT_FIELDS.get(core_metadata_field, ())\n        })\n\n    if description := message.get_payload():\n        metadata[\"readme\"] = {\n            \"content-type\": message.get(\"Description-Content-Type\", \"text/plain\"),\n            \"text\": description,\n        }\n\n    if (license_expression := message.get(\"License-Expression\")) is not None:\n        metadata[\"license\"] = license_expression\n    elif (license_text := message.get(\"License\")) is not None:\n        metadata[\"license\"] = {\"text\": license_text}\n\n    if (license_files := message.get_all(\"License-File\")) is not None:\n        metadata[\"license-files\"] = license_files\n\n    if (summary := message.get(\"Summary\")) is not None:\n        metadata[\"description\"] = summary\n\n    if (keywords := message.get(\"Keywords\")) is not None:\n        metadata[\"keywords\"] = keywords.split(\",\")\n\n    if (classifiers := message.get_all(\"Classifier\")) is not None:\n        metadata[\"classifiers\"] = classifiers\n\n    if (project_urls := message.get_all(\"Project-URL\")) is not None:\n        urls = {}\n        for project_url in project_urls:\n            label, url = project_url.split(\",\", maxsplit=1)\n            urls[label.strip()] = url.strip()\n        metadata[\"urls\"] = urls\n\n    authors = []\n    if (author := message.get(\"Author\")) is not None:\n        authors.append({\"name\": author})\n\n    if (author_email := message.get(\"Author-email\")) is not None:\n        address_header = header_registry(\"resent-from\", author_email)\n        for address in address_header.addresses:  # type: ignore[attr-defined]\n            data = {\"email\": address.addr_spec}\n            if name := address.display_name:\n                data[\"name\"] = name\n            authors.append(data)\n\n    if authors:\n        metadata[\"authors\"] = authors\n\n    maintainers = []\n    if (maintainer := message.get(\"Maintainer\")) is not None:\n        maintainers.append({\"name\": maintainer})\n\n    if (maintainer_email := message.get(\"Maintainer-email\")) is not None:\n        address_header = header_registry(\"resent-from\", maintainer_email)\n        for address in address_header.addresses:  # type: ignore[attr-defined]\n            data = {\"email\": address.addr_spec}\n            if name := address.display_name:\n                data[\"name\"] = name\n            maintainers.append(data)\n\n    if maintainers:\n        metadata[\"maintainers\"] = maintainers\n\n    if (requires_python := message.get(\"Requires-Python\")) is not None:\n        metadata[\"requires-python\"] = requires_python\n\n    optional_dependencies: dict[str, list[str]] = {}\n    if (extras := message.get_all(\"Provides-Extra\")) is not None:\n        for extra in extras:\n            optional_dependencies[extra] = []\n\n    if (requirements := message.get_all(\"Requires-Dist\")) is not None:\n        from packaging.requirements import Requirement\n\n        dependencies = []\n        for requirement in requirements:\n            req = Requirement(requirement)\n            if req.marker is None:\n                dependencies.append(str(req))\n                continue\n\n            markers = req.marker._markers  # noqa: SLF001\n            for i, marker in enumerate(markers):\n                if isinstance(marker, tuple):\n                    left, _, right = marker\n                    if left.value == \"extra\":\n                        extra = right.value\n                        del markers[i]  # noqa: B909\n                        # If there was only one marker then there will be an unnecessary\n                        # trailing semicolon in the string representation\n                        if not markers:\n                            req.marker = None\n                        # Otherwise we need to remove the preceding `and` operation\n                        else:\n                            del markers[i - 1]\n\n                        optional_dependencies.setdefault(extra, []).append(str(req))\n                        break\n            else:\n                dependencies.append(str(req))\n\n        metadata[\"dependencies\"] = dependencies\n\n    if optional_dependencies:\n        metadata[\"optional-dependencies\"] = optional_dependencies\n\n    return metadata\n\n\ndef construct_metadata_file_1_2(metadata: ProjectMetadata, extra_dependencies: tuple[str] | None = None) -> str:\n    \"\"\"\n    https://peps.python.org/pep-0345/\n    \"\"\"\n    metadata_file = \"Metadata-Version: 1.2\\n\"\n    metadata_file += f\"Name: {metadata.core.raw_name}\\n\"\n    metadata_file += f\"Version: {metadata.version}\\n\"\n\n    if metadata.core.description:\n        metadata_file += f\"Summary: {metadata.core.description}\\n\"\n\n    if metadata.core.urls:\n        for label, url in metadata.core.urls.items():\n            metadata_file += f\"Project-URL: {label}, {url}\\n\"\n\n    authors_data = metadata.core.authors_data\n    if authors_data[\"name\"]:\n        metadata_file += f\"Author: {', '.join(authors_data['name'])}\\n\"\n    if authors_data[\"email\"]:\n        metadata_file += f\"Author-email: {', '.join(authors_data['email'])}\\n\"\n\n    maintainers_data = metadata.core.maintainers_data\n    if maintainers_data[\"name\"]:\n        metadata_file += f\"Maintainer: {', '.join(maintainers_data['name'])}\\n\"\n    if maintainers_data[\"email\"]:\n        metadata_file += f\"Maintainer-email: {', '.join(maintainers_data['email'])}\\n\"\n\n    if metadata.core.license:\n        license_start = \"License: \"\n        indent = \" \" * (len(license_start) - 1)\n        metadata_file += license_start\n\n        for i, line in enumerate(metadata.core.license.splitlines()):\n            if i == 0:\n                metadata_file += f\"{line}\\n\"\n            else:\n                metadata_file += f\"{indent}{line}\\n\"\n    elif metadata.core.license_expression:\n        metadata_file += f\"License: {metadata.core.license_expression}\\n\"\n\n    if metadata.core.keywords:\n        metadata_file += f\"Keywords: {','.join(metadata.core.keywords)}\\n\"\n\n    if metadata.core.classifiers:\n        for classifier in metadata.core.classifiers:\n            metadata_file += f\"Classifier: {classifier}\\n\"\n\n    if metadata.core.requires_python:\n        metadata_file += f\"Requires-Python: {metadata.core.requires_python}\\n\"\n\n    if metadata.core.dependencies:\n        for dependency in metadata.core.dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    if extra_dependencies:\n        for dependency in extra_dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    return metadata_file\n\n\ndef construct_metadata_file_2_1(metadata: ProjectMetadata, extra_dependencies: tuple[str] | None = None) -> str:\n    \"\"\"\n    https://peps.python.org/pep-0566/\n    \"\"\"\n    metadata_file = \"Metadata-Version: 2.1\\n\"\n    metadata_file += f\"Name: {metadata.core.raw_name}\\n\"\n    metadata_file += f\"Version: {metadata.version}\\n\"\n\n    if metadata.core.description:\n        metadata_file += f\"Summary: {metadata.core.description}\\n\"\n\n    if metadata.core.urls:\n        for label, url in metadata.core.urls.items():\n            metadata_file += f\"Project-URL: {label}, {url}\\n\"\n\n    authors_data = metadata.core.authors_data\n    if authors_data[\"name\"]:\n        metadata_file += f\"Author: {', '.join(authors_data['name'])}\\n\"\n    if authors_data[\"email\"]:\n        metadata_file += f\"Author-email: {', '.join(authors_data['email'])}\\n\"\n\n    maintainers_data = metadata.core.maintainers_data\n    if maintainers_data[\"name\"]:\n        metadata_file += f\"Maintainer: {', '.join(maintainers_data['name'])}\\n\"\n    if maintainers_data[\"email\"]:\n        metadata_file += f\"Maintainer-email: {', '.join(maintainers_data['email'])}\\n\"\n\n    if metadata.core.license:\n        license_start = \"License: \"\n        indent = \" \" * (len(license_start) - 1)\n        metadata_file += license_start\n\n        for i, line in enumerate(metadata.core.license.splitlines()):\n            if i == 0:\n                metadata_file += f\"{line}\\n\"\n            else:\n                metadata_file += f\"{indent}{line}\\n\"\n    elif metadata.core.license_expression:\n        metadata_file += f\"License: {metadata.core.license_expression}\\n\"\n\n    if metadata.core.keywords:\n        metadata_file += f\"Keywords: {','.join(metadata.core.keywords)}\\n\"\n\n    if metadata.core.classifiers:\n        for classifier in metadata.core.classifiers:\n            metadata_file += f\"Classifier: {classifier}\\n\"\n\n    if metadata.core.requires_python:\n        metadata_file += f\"Requires-Python: {metadata.core.requires_python}\\n\"\n\n    if metadata.core.dependencies:\n        for dependency in metadata.core.dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    if extra_dependencies:\n        for dependency in extra_dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    if metadata.core.optional_dependencies:\n        for option, dependencies in metadata.core.optional_dependencies.items():\n            metadata_file += f\"Provides-Extra: {option}\\n\"\n            for dependency in dependencies:\n                if \";\" in dependency:\n                    dep_name, dep_env_marker = dependency.split(\";\", maxsplit=1)\n                    metadata_file += f\"Requires-Dist: {dep_name}; ({dep_env_marker.strip()}) and extra == {option!r}\\n\"\n                elif \"@ \" in dependency:\n                    metadata_file += f\"Requires-Dist: {dependency} ; extra == {option!r}\\n\"\n                else:\n                    metadata_file += f\"Requires-Dist: {dependency}; extra == {option!r}\\n\"\n\n    if metadata.core.readme:\n        metadata_file += f\"Description-Content-Type: {metadata.core.readme_content_type}\\n\"\n        metadata_file += f\"\\n{metadata.core.readme}\"\n\n    return metadata_file\n\n\ndef construct_metadata_file_2_2(metadata: ProjectMetadata, extra_dependencies: tuple[str] | None = None) -> str:\n    \"\"\"\n    https://peps.python.org/pep-0643/\n    \"\"\"\n    metadata_file = \"Metadata-Version: 2.2\\n\"\n    metadata_file += f\"Name: {metadata.core.raw_name}\\n\"\n    metadata_file += f\"Version: {metadata.version}\\n\"\n\n    if metadata.core.dynamic:\n        # Ordered set\n        for field in {\n            core_metadata_field: None\n            for project_field in metadata.core.dynamic\n            for core_metadata_field in PROJECT_CORE_METADATA_FIELDS.get(project_field, ())\n        }:\n            metadata_file += f\"Dynamic: {field}\\n\"\n\n    if metadata.core.description:\n        metadata_file += f\"Summary: {metadata.core.description}\\n\"\n\n    if metadata.core.urls:\n        for label, url in metadata.core.urls.items():\n            metadata_file += f\"Project-URL: {label}, {url}\\n\"\n\n    authors_data = metadata.core.authors_data\n    if authors_data[\"name\"]:\n        metadata_file += f\"Author: {', '.join(authors_data['name'])}\\n\"\n    if authors_data[\"email\"]:\n        metadata_file += f\"Author-email: {', '.join(authors_data['email'])}\\n\"\n\n    maintainers_data = metadata.core.maintainers_data\n    if maintainers_data[\"name\"]:\n        metadata_file += f\"Maintainer: {', '.join(maintainers_data['name'])}\\n\"\n    if maintainers_data[\"email\"]:\n        metadata_file += f\"Maintainer-email: {', '.join(maintainers_data['email'])}\\n\"\n\n    if metadata.core.license:\n        license_start = \"License: \"\n        indent = \" \" * (len(license_start) - 1)\n        metadata_file += license_start\n\n        for i, line in enumerate(metadata.core.license.splitlines()):\n            if i == 0:\n                metadata_file += f\"{line}\\n\"\n            else:\n                metadata_file += f\"{indent}{line}\\n\"\n    elif metadata.core.license_expression:\n        metadata_file += f\"License: {metadata.core.license_expression}\\n\"\n\n    if metadata.core.keywords:\n        metadata_file += f\"Keywords: {','.join(metadata.core.keywords)}\\n\"\n\n    if metadata.core.classifiers:\n        for classifier in metadata.core.classifiers:\n            metadata_file += f\"Classifier: {classifier}\\n\"\n\n    if metadata.core.requires_python:\n        metadata_file += f\"Requires-Python: {metadata.core.requires_python}\\n\"\n\n    if metadata.core.dependencies:\n        for dependency in metadata.core.dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    if extra_dependencies:\n        for dependency in extra_dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    if metadata.core.optional_dependencies:\n        for option, dependencies in metadata.core.optional_dependencies.items():\n            metadata_file += f\"Provides-Extra: {option}\\n\"\n            for dependency in dependencies:\n                if \";\" in dependency:\n                    dep_name, dep_env_marker = dependency.split(\";\", maxsplit=1)\n                    metadata_file += f\"Requires-Dist: {dep_name}; ({dep_env_marker.strip()}) and extra == {option!r}\\n\"\n                elif \"@ \" in dependency:\n                    metadata_file += f\"Requires-Dist: {dependency} ; extra == {option!r}\\n\"\n                else:\n                    metadata_file += f\"Requires-Dist: {dependency}; extra == {option!r}\\n\"\n\n    if metadata.core.readme:\n        metadata_file += f\"Description-Content-Type: {metadata.core.readme_content_type}\\n\"\n        metadata_file += f\"\\n{metadata.core.readme}\"\n\n    return metadata_file\n\n\ndef construct_metadata_file_2_3(metadata: ProjectMetadata, extra_dependencies: tuple[str] | None = None) -> str:\n    \"\"\"\n    https://peps.python.org/pep-0685/\n    \"\"\"\n    metadata_file = \"Metadata-Version: 2.3\\n\"\n    metadata_file += f\"Name: {metadata.core.raw_name}\\n\"\n    metadata_file += f\"Version: {metadata.version}\\n\"\n\n    if metadata.core.dynamic:\n        # Ordered set\n        for field in {\n            core_metadata_field: None\n            for project_field in metadata.core.dynamic\n            for core_metadata_field in PROJECT_CORE_METADATA_FIELDS.get(project_field, ())\n        }:\n            metadata_file += f\"Dynamic: {field}\\n\"\n\n    if metadata.core.description:\n        metadata_file += f\"Summary: {metadata.core.description}\\n\"\n\n    if metadata.core.urls:\n        for label, url in metadata.core.urls.items():\n            metadata_file += f\"Project-URL: {label}, {url}\\n\"\n\n    authors_data = metadata.core.authors_data\n    if authors_data[\"name\"]:\n        metadata_file += f\"Author: {', '.join(authors_data['name'])}\\n\"\n    if authors_data[\"email\"]:\n        metadata_file += f\"Author-email: {', '.join(authors_data['email'])}\\n\"\n\n    maintainers_data = metadata.core.maintainers_data\n    if maintainers_data[\"name\"]:\n        metadata_file += f\"Maintainer: {', '.join(maintainers_data['name'])}\\n\"\n    if maintainers_data[\"email\"]:\n        metadata_file += f\"Maintainer-email: {', '.join(maintainers_data['email'])}\\n\"\n\n    if metadata.core.license:\n        license_start = \"License: \"\n        indent = \" \" * (len(license_start) - 1)\n        metadata_file += license_start\n\n        for i, line in enumerate(metadata.core.license.splitlines()):\n            if i == 0:\n                metadata_file += f\"{line}\\n\"\n            else:\n                metadata_file += f\"{indent}{line}\\n\"\n    elif metadata.core.license_expression:\n        metadata_file += f\"License: {metadata.core.license_expression}\\n\"\n\n    if metadata.core.keywords:\n        metadata_file += f\"Keywords: {','.join(metadata.core.keywords)}\\n\"\n\n    if metadata.core.classifiers:\n        for classifier in metadata.core.classifiers:\n            metadata_file += f\"Classifier: {classifier}\\n\"\n\n    if metadata.core.requires_python:\n        metadata_file += f\"Requires-Python: {metadata.core.requires_python}\\n\"\n\n    if metadata.core.dependencies:\n        for dependency in metadata.core.dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    if extra_dependencies:\n        for dependency in extra_dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    if metadata.core.optional_dependencies:\n        for option, dependencies in metadata.core.optional_dependencies.items():\n            metadata_file += f\"Provides-Extra: {option}\\n\"\n            for dependency in dependencies:\n                if \";\" in dependency:\n                    dep_name, dep_env_marker = dependency.split(\";\", maxsplit=1)\n                    metadata_file += f\"Requires-Dist: {dep_name}; ({dep_env_marker.strip()}) and extra == {option!r}\\n\"\n                elif \"@ \" in dependency:\n                    metadata_file += f\"Requires-Dist: {dependency} ; extra == {option!r}\\n\"\n                else:\n                    metadata_file += f\"Requires-Dist: {dependency}; extra == {option!r}\\n\"\n\n    if metadata.core.readme:\n        metadata_file += f\"Description-Content-Type: {metadata.core.readme_content_type}\\n\"\n        metadata_file += f\"\\n{metadata.core.readme}\"\n\n    return metadata_file\n\n\ndef construct_metadata_file_2_4(metadata: ProjectMetadata, extra_dependencies: tuple[str] | None = None) -> str:\n    \"\"\"\n    https://peps.python.org/pep-0639/\n    \"\"\"\n    metadata_file = \"Metadata-Version: 2.4\\n\"\n    metadata_file += f\"Name: {metadata.core.raw_name}\\n\"\n    metadata_file += f\"Version: {metadata.version}\\n\"\n\n    if metadata.core.dynamic:\n        # Ordered set\n        for field in {\n            core_metadata_field: None\n            for project_field in metadata.core.dynamic\n            for core_metadata_field in PROJECT_CORE_METADATA_FIELDS.get(project_field, ())\n        }:\n            metadata_file += f\"Dynamic: {field}\\n\"\n\n    if metadata.core.description:\n        metadata_file += f\"Summary: {metadata.core.description}\\n\"\n\n    if metadata.core.urls:\n        for label, url in metadata.core.urls.items():\n            metadata_file += f\"Project-URL: {label}, {url}\\n\"\n\n    authors_data = metadata.core.authors_data\n    if authors_data[\"name\"]:\n        metadata_file += f\"Author: {', '.join(authors_data['name'])}\\n\"\n    if authors_data[\"email\"]:\n        metadata_file += f\"Author-email: {', '.join(authors_data['email'])}\\n\"\n\n    maintainers_data = metadata.core.maintainers_data\n    if maintainers_data[\"name\"]:\n        metadata_file += f\"Maintainer: {', '.join(maintainers_data['name'])}\\n\"\n    if maintainers_data[\"email\"]:\n        metadata_file += f\"Maintainer-email: {', '.join(maintainers_data['email'])}\\n\"\n\n    if metadata.core.license:\n        license_start = \"License: \"\n        indent = \" \" * (len(license_start) - 1)\n        metadata_file += license_start\n\n        for i, line in enumerate(metadata.core.license.splitlines()):\n            if i == 0:\n                metadata_file += f\"{line}\\n\"\n            else:\n                metadata_file += f\"{indent}{line}\\n\"\n\n    if metadata.core.license_expression:\n        metadata_file += f\"License-Expression: {metadata.core.license_expression}\\n\"\n\n    if metadata.core.license_files:\n        for license_file in metadata.core.license_files:\n            metadata_file += f\"License-File: {license_file}\\n\"\n\n    if metadata.core.keywords:\n        metadata_file += f\"Keywords: {','.join(metadata.core.keywords)}\\n\"\n\n    if metadata.core.classifiers:\n        for classifier in metadata.core.classifiers:\n            metadata_file += f\"Classifier: {classifier}\\n\"\n\n    if metadata.core.requires_python:\n        metadata_file += f\"Requires-Python: {metadata.core.requires_python}\\n\"\n\n    if metadata.core.dependencies:\n        for dependency in metadata.core.dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    if extra_dependencies:\n        for dependency in extra_dependencies:\n            metadata_file += f\"Requires-Dist: {dependency}\\n\"\n\n    if metadata.core.optional_dependencies:\n        for option, dependencies in metadata.core.optional_dependencies.items():\n            metadata_file += f\"Provides-Extra: {option}\\n\"\n            for dependency in dependencies:\n                if \";\" in dependency:\n                    dep_name, dep_env_marker = dependency.split(\";\", maxsplit=1)\n                    metadata_file += f\"Requires-Dist: {dep_name}; ({dep_env_marker.strip()}) and extra == {option!r}\\n\"\n                elif \"@ \" in dependency:\n                    metadata_file += f\"Requires-Dist: {dependency} ; extra == {option!r}\\n\"\n                else:\n                    metadata_file += f\"Requires-Dist: {dependency}; extra == {option!r}\\n\"\n\n    if metadata.core.readme:\n        metadata_file += f\"Description-Content-Type: {metadata.core.readme_content_type}\\n\"\n        metadata_file += f\"\\n{metadata.core.readme}\"\n\n    return metadata_file\n"
  },
  {
    "path": "backend/src/hatchling/metadata/utils.py",
    "content": "from __future__ import annotations\n\nimport re\nfrom typing import TYPE_CHECKING, Any\n\nif TYPE_CHECKING:\n    from packaging.requirements import Requirement\n\n    from hatchling.metadata.core import ProjectMetadata\n\n# NOTE: this module should rarely be changed because it is likely to be used by other packages like Hatch\n\n\ndef is_valid_project_name(project_name: str) -> bool:\n    # https://peps.python.org/pep-0508/#names\n    return re.search(\"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$\", project_name, re.IGNORECASE) is not None\n\n\ndef normalize_project_name(project_name: str) -> str:\n    # https://peps.python.org/pep-0503/#normalized-names\n    return re.sub(r\"[-_.]+\", \"-\", project_name).lower()\n\n\ndef normalize_requirement(requirement: Requirement) -> None:\n    # Changes to this function affect reproducibility between versions\n    from packaging.specifiers import SpecifierSet\n\n    requirement.name = normalize_project_name(requirement.name)\n\n    if requirement.specifier:\n        requirement.specifier = SpecifierSet(str(requirement.specifier).lower())\n\n    if requirement.extras:\n        requirement.extras = {normalize_project_name(extra) for extra in requirement.extras}\n\n\ndef format_dependency(requirement: Requirement) -> str:\n    # All TOML writers use double quotes, so allow direct writing or copy/pasting to avoid escaping\n    return str(requirement).replace('\"', \"'\")\n\n\ndef get_normalized_dependency(requirement: Requirement) -> str:\n    normalize_requirement(requirement)\n    return format_dependency(requirement)\n\n\ndef resolve_metadata_fields(metadata: ProjectMetadata) -> dict[str, Any]:\n    # https://packaging.python.org/en/latest/specifications/declaring-project-metadata/\n    return {\n        \"name\": metadata.core.name,\n        \"version\": metadata.version,\n        \"description\": metadata.core.description,\n        \"readme\": {\"content-type\": metadata.core.readme_content_type, \"text\": metadata.core.readme},\n        \"requires-python\": metadata.core.requires_python,\n        \"license\": metadata.core.license_expression or metadata.core.license,\n        \"authors\": metadata.core.authors,\n        \"maintainers\": metadata.core.maintainers,\n        \"keywords\": metadata.core.keywords,\n        \"classifiers\": metadata.core.classifiers,\n        \"urls\": metadata.core.urls,\n        \"scripts\": metadata.core.scripts,\n        \"gui-scripts\": metadata.core.gui_scripts,\n        \"entry-points\": metadata.core.entry_points,\n        \"dependencies\": metadata.core.dependencies,\n        \"optional-dependencies\": metadata.core.optional_dependencies,\n    }\n"
  },
  {
    "path": "backend/src/hatchling/ouroboros.py",
    "content": "from __future__ import annotations\n\nimport os\nimport re\nfrom ast import literal_eval\nfrom typing import Any\n\nfrom hatchling.build import *  # noqa: F403\n\n\ndef read_dependencies() -> list[str]:\n    pattern = r\"^dependencies = (\\[.*?\\])$\"\n\n    with open(os.path.join(os.getcwd(), \"pyproject.toml\"), encoding=\"utf-8\") as f:\n        # Windows \\r\\n prevents match\n        contents = \"\\n\".join(line.rstrip() for line in f)\n\n    match = re.search(pattern, contents, flags=re.MULTILINE | re.DOTALL)\n    if match is None:\n        message = \"No dependencies found\"\n        raise ValueError(message)\n\n    return literal_eval(match.group(1))\n\n\ndef get_requires_for_build_sdist(  # type: ignore[no-redef]\n    config_settings: dict[str, Any] | None = None,  # noqa: ARG001\n) -> list[str]:\n    \"\"\"\n    https://peps.python.org/pep-0517/#get-requires-for-build-sdist\n    \"\"\"\n    return read_dependencies()\n\n\ndef get_requires_for_build_wheel(  # type: ignore[no-redef]\n    config_settings: dict[str, Any] | None = None,  # noqa: ARG001\n) -> list[str]:\n    \"\"\"\n    https://peps.python.org/pep-0517/#get-requires-for-build-wheel\n    \"\"\"\n    return read_dependencies()\n\n\ndef get_requires_for_build_editable(  # type: ignore[no-redef]\n    config_settings: dict[str, Any] | None = None,  # noqa: ARG001\n) -> list[str]:\n    \"\"\"\n    https://peps.python.org/pep-0660/#get-requires-for-build-editable\n    \"\"\"\n    from hatchling.builders.constants import EDITABLES_REQUIREMENT\n\n    return [*read_dependencies(), EDITABLES_REQUIREMENT]\n"
  },
  {
    "path": "backend/src/hatchling/plugin/__init__.py",
    "content": "import pluggy\n\nhookimpl = pluggy.HookimplMarker(\"hatch\")\n"
  },
  {
    "path": "backend/src/hatchling/plugin/exceptions.py",
    "content": "class UnknownPluginError(ValueError):\n    pass\n"
  },
  {
    "path": "backend/src/hatchling/plugin/manager.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, TypeVar\n\nimport pluggy\n\nif TYPE_CHECKING:\n    from collections.abc import Callable\n\n\nclass PluginManager:\n    def __init__(self) -> None:\n        self.manager = pluggy.PluginManager(\"hatch\")\n        self.third_party_plugins = ThirdPartyPlugins(self.manager)\n        self.initialized = False\n\n    def initialize(self) -> None:\n        from hatchling.plugin import specs\n\n        self.manager.add_hookspecs(specs)\n\n    def __getattr__(self, name: str) -> ClassRegister:\n        if not self.initialized:\n            self.initialize()\n            self.initialized = True\n\n        hook_name = f\"hatch_register_{name}\"\n        hook = getattr(self, hook_name, None)\n        if hook:\n            hook()\n\n        register = ClassRegister(getattr(self.manager.hook, hook_name), \"PLUGIN_NAME\", self.third_party_plugins)\n        setattr(self, name, register)\n        return register\n\n    def hatch_register_version_source(self) -> None:\n        from hatchling.version.source.plugin import hooks\n\n        self.manager.register(hooks)\n\n    def hatch_register_version_scheme(self) -> None:\n        from hatchling.version.scheme.plugin import hooks\n\n        self.manager.register(hooks)\n\n    def hatch_register_builder(self) -> None:\n        from hatchling.builders.plugin import hooks\n\n        self.manager.register(hooks)\n\n    def hatch_register_build_hook(self) -> None:\n        from hatchling.builders.hooks.plugin import hooks\n\n        self.manager.register(hooks)\n\n    def hatch_register_metadata_hook(self) -> None:\n        from hatchling.metadata.plugin import hooks\n\n        self.manager.register(hooks)\n\n\nclass ClassRegister:\n    def __init__(self, registration_method: Callable, identifier: str, third_party_plugins: ThirdPartyPlugins) -> None:\n        self.registration_method = registration_method\n        self.identifier = identifier\n        self.third_party_plugins = third_party_plugins\n\n    def collect(self, *, include_third_party: bool = True) -> dict:\n        if include_third_party and not self.third_party_plugins.loaded:\n            self.third_party_plugins.load()\n\n        classes: dict[str, type] = {}\n\n        for raw_registered_classes in self.registration_method():\n            registered_classes = (\n                raw_registered_classes if isinstance(raw_registered_classes, list) else [raw_registered_classes]\n            )\n            for registered_class in registered_classes:\n                name = getattr(registered_class, self.identifier, None)\n                if not name:  # no cov\n                    message = f\"Class `{registered_class.__name__}` does not have a {name} attribute.\"\n                    raise ValueError(message)\n\n                if name in classes:  # no cov\n                    message = (\n                        f\"Class `{registered_class.__name__}` defines its name as `{name}` but \"\n                        f\"that name is already used by `{classes[name].__name__}`.\"\n                    )\n                    raise ValueError(message)\n\n                classes[name] = registered_class\n\n        return classes\n\n    def get(self, name: str) -> type | None:\n        if not self.third_party_plugins.loaded:\n            classes = self.collect(include_third_party=False)\n            if name in classes:\n                return classes[name]\n\n        return self.collect().get(name)\n\n\nclass ThirdPartyPlugins:\n    def __init__(self, manager: pluggy.PluginManager) -> None:\n        self.manager = manager\n        self.loaded = False\n\n    def load(self) -> None:\n        self.manager.load_setuptools_entrypoints(\"hatch\")\n        self.loaded = True\n\n\nPluginManagerBound = TypeVar(\"PluginManagerBound\", bound=PluginManager)\n"
  },
  {
    "path": "backend/src/hatchling/plugin/specs.py",
    "content": "import pluggy\n\nhookspec = pluggy.HookspecMarker(\"hatch\")\n\n\n@hookspec\ndef hatch_register_version_source() -> None:\n    \"\"\"Register new classes that adhere to the version source interface.\"\"\"\n\n\n@hookspec\ndef hatch_register_builder() -> None:\n    \"\"\"Register new classes that adhere to the builder interface.\"\"\"\n\n\n@hookspec\ndef hatch_register_build_hook() -> None:\n    \"\"\"Register new classes that adhere to the build hook interface.\"\"\"\n\n\n@hookspec\ndef hatch_register_metadata_hook() -> None:\n    \"\"\"Register new classes that adhere to the metadata hook interface.\"\"\"\n"
  },
  {
    "path": "backend/src/hatchling/plugin/utils.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, TypeVar\n\nif TYPE_CHECKING:\n    from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n    from hatchling.builders.plugin.interface import BuilderInterface\n    from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n    T = TypeVar(\"T\", BuilderInterface, BuildHookInterface, MetadataHookInterface)\n\n\ndef load_plugin_from_script(path: str, script_name: str, plugin_class: type[T], plugin_id: str) -> type[T]:\n    from importlib.util import module_from_spec, spec_from_file_location\n\n    spec = spec_from_file_location(script_name, path)\n    module = module_from_spec(spec)  # type: ignore[arg-type]\n    spec.loader.exec_module(module)  # type: ignore[union-attr]\n\n    plugin_finder = f\"get_{plugin_id}\"\n    names = dir(module)\n    if plugin_finder in names:\n        return getattr(module, plugin_finder)()\n\n    subclasses = []\n    for name in names:\n        obj = getattr(module, name)\n        if obj is plugin_class:\n            continue\n\n        try:\n            if issubclass(obj, plugin_class):\n                subclasses.append(obj)\n        except TypeError:\n            continue\n\n    if not subclasses:\n        message = f\"Unable to find a subclass of `{plugin_class.__name__}` in `{script_name}`: {path}\"\n        raise ValueError(message)\n\n    if len(subclasses) > 1:\n        message = (\n            f\"Multiple subclasses of `{plugin_class.__name__}` found in `{script_name}`, \"\n            f\"select one by defining a function named `{plugin_finder}`: {path}\"\n        )\n        raise ValueError(message)\n\n    return subclasses[0]\n"
  },
  {
    "path": "backend/src/hatchling/py.typed",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/utils/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/utils/constants.py",
    "content": "DEFAULT_BUILD_SCRIPT = \"hatch_build.py\"\nDEFAULT_CONFIG_FILE = \"hatch.toml\"\n\n\nclass VersionEnvVars:\n    VALIDATE_BUMP = \"HATCH_VERSION_VALIDATE_BUMP\"\n"
  },
  {
    "path": "backend/src/hatchling/utils/context.py",
    "content": "from __future__ import annotations\n\nimport os\nimport string\nfrom abc import ABC, abstractmethod\nfrom collections import ChainMap\nfrom contextlib import contextmanager\nfrom typing import TYPE_CHECKING, Any\n\nfrom hatchling.utils.fs import path_to_uri\n\nif TYPE_CHECKING:\n    from collections.abc import Iterable, Iterator, Mapping, MutableMapping, Sequence\n\n\nclass ContextFormatter(ABC):\n    @abstractmethod\n    def get_formatters(self) -> MutableMapping:\n        \"\"\"\n        This returns a mapping of supported field names to their respective formatting functions. Each function\n        accepts 2 arguments:\n\n        - the `value` that was passed to the format call, defaulting to `None`\n        - the modifier `data`, defaulting to an empty string\n        \"\"\"\n\n    @classmethod\n    def format_path(cls, path: str, modifier: str) -> str:\n        if not modifier:\n            return os.path.normpath(path)\n\n        modifiers = modifier.split(\":\")[::-1]\n        while modifiers and modifiers[-1] == \"parent\":\n            path = os.path.dirname(path)\n            modifiers.pop()\n\n        if not modifiers:\n            return path\n\n        if len(modifiers) > 1:\n            message = f\"Expected a single path modifier and instead got: {', '.join(reversed(modifiers))}\"\n            raise ValueError(message)\n\n        modifier = modifiers[0]\n        if modifier == \"uri\":\n            return path_to_uri(path)\n\n        if modifier == \"real\":\n            return os.path.realpath(path)\n\n        message = f\"Unknown path modifier: {modifier}\"\n        raise ValueError(message)\n\n\nclass DefaultContextFormatter(ContextFormatter):\n    CONTEXT_NAME = \"default\"\n\n    def __init__(self, root: str) -> None:\n        self.__root = root\n\n    def get_formatters(self) -> MutableMapping:\n        return {\n            \"/\": self.__format_directory_separator,\n            \";\": self.__format_path_separator,\n            \"env\": self.__format_env,\n            \"home\": self.__format_home,\n            \"root\": self.__format_root,\n        }\n\n    def __format_directory_separator(self, value: str, data: str) -> str:  # noqa: ARG002, PLR6301\n        return os.sep\n\n    def __format_path_separator(self, value: str, data: str) -> str:  # noqa: ARG002, PLR6301\n        return os.pathsep\n\n    def __format_root(self, value: str, data: str) -> str:  # noqa: ARG002\n        return self.format_path(self.__root, data)\n\n    def __format_home(self, value: str, data: str) -> str:  # noqa: ARG002\n        return self.format_path(os.path.expanduser(\"~\"), data)\n\n    def __format_env(self, value: str, data: str) -> str:  # noqa: ARG002, PLR6301\n        if not data:\n            message = \"The `env` context formatting field requires a modifier\"\n            raise ValueError(message)\n\n        env_var, separator, default = data.partition(\":\")\n        if env_var in os.environ:\n            return os.environ[env_var]\n\n        if not separator:\n            message = f\"Nonexistent environment variable must set a default: {env_var}\"\n            raise ValueError(message)\n\n        return default\n\n\nclass Context:\n    def __init__(self, root: str) -> None:\n        self.__root = str(root)\n\n        # Allow callers to define their own formatters with precedence\n        self.__formatters: ChainMap = ChainMap()\n        self.__configured_contexts: set[str] = set()\n        self.__formatter = ContextStringFormatter(self.__formatters)\n\n        self.add_context(DefaultContextFormatter(self.__root))\n\n    def format(self, *args: Any, **kwargs: Any) -> str:\n        return self.__formatter.format(*args, **kwargs)\n\n    def add_context(self, context: DefaultContextFormatter) -> None:\n        if context.CONTEXT_NAME in self.__configured_contexts:\n            return\n\n        self.__add_formatters(context.get_formatters())\n        self.__configured_contexts.add(context.CONTEXT_NAME)\n\n    @contextmanager\n    def apply_context(self, context: DefaultContextFormatter) -> Iterator:\n        self.__add_formatters(context.get_formatters())\n        try:\n            yield\n        finally:\n            self.__remove_formatters()\n\n    def __add_formatters(self, formatters: MutableMapping) -> None:\n        return self.__formatters.maps.insert(0, formatters)\n\n    def __remove_formatters(self) -> None:\n        if len(self.__formatters.maps) > 1:\n            self.__formatters.maps.pop(0)\n\n\nclass ContextStringFormatter(string.Formatter):\n    def __init__(self, formatters: ChainMap) -> None:\n        super().__init__()\n\n        self.__formatters = formatters\n\n    def vformat(self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]) -> str:\n        # We override to increase the recursion limit from 2 to 10\n        #\n        # TODO: remove type ignore after https://github.com/python/typeshed/pull/9228\n        used_args = set()  # type: ignore[var-annotated]\n        result, _ = self._vformat(format_string, args, kwargs, used_args, 10)\n        self.check_unused_args(used_args, args, kwargs)\n        return result\n\n    def get_value(self, key: int | str, args: Sequence[Any], kwargs: Mapping[str, Any]) -> Any:\n        if key in self.__formatters:\n            # Avoid hard look-up and rely on `None` to indicate that the field is undefined\n            return kwargs.get(str(key))\n\n        try:\n            return super().get_value(key, args, kwargs)\n        except KeyError:\n            message = f\"Unknown context field `{key}`\"\n            raise ValueError(message) from None\n\n    def format_field(self, value: Any, format_spec: str) -> Any:\n        formatter, _, data = format_spec.partition(\":\")\n        if formatter in self.__formatters:\n            return self.__formatters[formatter](value, data)\n\n        return super().format_field(value, format_spec)\n\n    def parse(self, format_string: str) -> Iterable:\n        for literal_text, field_name, format_spec, conversion in super().parse(format_string):\n            if field_name in self.__formatters:\n                yield literal_text, field_name, f\"{field_name}:{format_spec}\", conversion\n            else:\n                yield literal_text, field_name, format_spec, conversion\n"
  },
  {
    "path": "backend/src/hatchling/utils/fs.py",
    "content": "from __future__ import annotations\n\nimport os\n\n\ndef locate_file(root: str, file_name: str, *, boundary: str | None = None) -> str | None:\n    while True:\n        file_path = os.path.join(root, file_name)\n        if os.path.isfile(file_path):\n            return file_path\n\n        if boundary is not None and os.path.exists(os.path.join(root, boundary)):\n            return None\n\n        new_root = os.path.dirname(root)\n        if new_root == root:\n            return None\n\n        root = new_root\n\n\ndef path_to_uri(path: str) -> str:\n    if os.sep == \"/\":\n        return f\"file://{os.path.abspath(path).replace(' ', '%20')}\"\n\n    return f\"file:///{os.path.abspath(path).replace(' ', '%20').replace(os.sep, '/')}\"\n"
  },
  {
    "path": "backend/src/hatchling/version/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/version/core.py",
    "content": "from __future__ import annotations\n\nimport os\nimport re\n\nDEFAULT_PATTERN = r'(?i)^(__version__|VERSION) *= *([\\'\"])v?(?P<version>.+?)\\2'\nDEFAULT_TEMPLATE = \"\"\"\\\n# This file is auto-generated by Hatchling. As such, do not:\n#   - modify\n#   - track in version control e.g. be sure to add to .gitignore\n__version__ = VERSION = {version!r}\n\"\"\"\n\n\nclass VersionFile:\n    def __init__(self, root: str, relative_path: str) -> None:\n        self.__relative_path = relative_path\n        self.__path = os.path.normpath(os.path.join(root, relative_path))\n        self.__cached_read_data: tuple | None = None\n\n    def read(self, *, pattern: str | bool) -> str:\n        if not os.path.isfile(self.__path):\n            message = f\"file does not exist: {self.__relative_path}\"\n            raise OSError(message)\n\n        with open(self.__path, encoding=\"utf-8\") as f:\n            contents = f.read()\n\n        if not pattern or pattern is True:\n            pattern = DEFAULT_PATTERN\n\n        match = re.search(pattern, contents, flags=re.MULTILINE)\n        if not match:\n            message = f\"unable to parse the version from the file: {self.__relative_path}\"\n            raise ValueError(message)\n\n        groups = match.groupdict()\n        if \"version\" not in groups:\n            message = \"no group named `version` was defined in the pattern\"\n            raise ValueError(message)\n\n        self.__cached_read_data = groups[\"version\"], contents, match.span(\"version\")\n        return self.__cached_read_data[0]\n\n    def set_version(self, version: str) -> None:\n        _old_version, file_contents, (start, end) = self.__cached_read_data  # type: ignore[misc]\n        with open(self.__path, \"w\", encoding=\"utf-8\") as f:\n            f.write(f\"{file_contents[:start]}{version}{file_contents[end:]}\")\n\n    def write(self, version: str, template: str = DEFAULT_TEMPLATE) -> None:\n        template = template or DEFAULT_TEMPLATE\n\n        parent_dir = os.path.dirname(self.__path)\n        if not os.path.isdir(parent_dir):\n            os.makedirs(parent_dir)\n\n        with open(self.__path, \"w\", encoding=\"utf-8\") as f:\n            f.write(template.format(version=version))\n"
  },
  {
    "path": "backend/src/hatchling/version/scheme/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/version/scheme/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/version/scheme/plugin/hooks.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom hatchling.plugin import hookimpl\nfrom hatchling.version.scheme.standard import StandardScheme\n\nif TYPE_CHECKING:\n    from hatchling.version.scheme.plugin.interface import VersionSchemeInterface\n\n\n@hookimpl\ndef hatch_register_version_scheme() -> type[VersionSchemeInterface]:\n    return StandardScheme\n"
  },
  {
    "path": "backend/src/hatchling/version/scheme/plugin/interface.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom abc import ABC, abstractmethod\nfrom functools import cached_property\n\n\nclass VersionSchemeInterface(ABC):  # no cov\n    \"\"\"\n    Example usage:\n\n    ```python tab=\"plugin.py\"\n    from hatchling.version.scheme.plugin.interface import VersionSchemeInterface\n\n\n    class SpecialVersionScheme(VersionSchemeInterface):\n        PLUGIN_NAME = \"special\"\n        ...\n    ```\n\n    ```python tab=\"hooks.py\"\n    from hatchling.plugin import hookimpl\n\n    from .plugin import SpecialVersionScheme\n\n\n    @hookimpl\n    def hatch_register_version_scheme():\n        return SpecialVersionScheme\n    ```\n    \"\"\"\n\n    PLUGIN_NAME = \"\"\n    \"\"\"The name used for selection.\"\"\"\n\n    def __init__(self, root: str, config: dict) -> None:\n        self.__root = root\n        self.__config = config\n\n    @property\n    def root(self) -> str:\n        \"\"\"\n        The root of the project tree as a string.\n        \"\"\"\n        return self.__root\n\n    @property\n    def config(self) -> dict:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.version]\n        ```\n        \"\"\"\n        return self.__config\n\n    @cached_property\n    def validate_bump(self) -> bool:\n        \"\"\"\n        This is the value of the `validate-bump` option, with the `HATCH_VERSION_VALIDATE_BUMP`\n        environment variable taking precedence. Validation is enabled by default.\n\n        ```toml config-example\n        [tool.hatch.version]\n        validate-bump = true\n        ```\n        \"\"\"\n        from hatchling.utils.constants import VersionEnvVars\n\n        if VersionEnvVars.VALIDATE_BUMP in os.environ:\n            return os.environ[VersionEnvVars.VALIDATE_BUMP] not in {\"false\", \"0\"}\n\n        validate_bump = self.config.get(\"validate-bump\", True)\n        if not isinstance(validate_bump, bool):\n            message = \"option `validate-bump` must be a boolean\"\n            raise TypeError(message)\n\n        return validate_bump\n\n    @abstractmethod\n    def update(self, desired_version: str, original_version: str, version_data: dict) -> str:\n        \"\"\"\n        This should return a normalized form of the desired version. If the\n        [validate_bump](reference.md#hatchling.version.scheme.plugin.interface.VersionSchemeInterface.validate_bump)\n        property is `True`, this method should also verify that the version is higher than the original version.\n        \"\"\"\n"
  },
  {
    "path": "backend/src/hatchling/version/scheme/standard.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, Literal, cast\n\nfrom hatchling.version.scheme.plugin.interface import VersionSchemeInterface\n\nif TYPE_CHECKING:\n    from packaging.version import Version\n\n\nclass StandardScheme(VersionSchemeInterface):\n    \"\"\"\n    See https://peps.python.org/pep-0440/\n    \"\"\"\n\n    PLUGIN_NAME = \"standard\"\n\n    def update(\n        self,\n        desired_version: str,\n        original_version: str,\n        version_data: dict,  # noqa: ARG002\n    ) -> str:\n        from packaging.version import Version\n\n        original = Version(original_version)\n        versions = desired_version.split(\",\")\n\n        for version in versions:\n            if version == \"release\":\n                original = reset_version_parts(original, release=original.release)\n            elif version == \"major\":\n                original = reset_version_parts(original, release=update_release(original, [original.major + 1]))\n            elif version == \"minor\":\n                original = reset_version_parts(\n                    original, release=update_release(original, [original.major, original.minor + 1])\n                )\n            elif version in {\"micro\", \"patch\", \"fix\"}:\n                original = reset_version_parts(\n                    original, release=update_release(original, [original.major, original.minor, original.micro + 1])\n                )\n            elif version in {\"a\", \"b\", \"c\", \"rc\", \"alpha\", \"beta\", \"pre\", \"preview\"}:\n                phase, number = parse_letter_version(version, 0)\n                if original.pre:\n                    current_phase, current_number = parse_letter_version(*original.pre)\n                    if phase == current_phase:\n                        number = current_number + 1\n\n                original = reset_version_parts(original, pre=(phase, number))\n            elif version in {\"post\", \"rev\", \"r\"}:\n                number = 0 if original.post is None else original.post + 1\n                original = reset_version_parts(original, post=number)\n            elif version == \"dev\":\n                number = 0 if original.dev is None else original.dev + 1\n                original = reset_version_parts(original, dev=number)\n            else:\n                if len(versions) > 1:\n                    message = \"Cannot specify multiple update operations with an explicit version\"\n                    raise ValueError(message)\n\n                next_version = Version(version)\n                if self.validate_bump and next_version <= original:\n                    message = f\"Version `{version}` is not higher than the original version `{original_version}`\"\n                    raise ValueError(message)\n\n                return str(next_version)\n\n        return str(original)\n\n\ndef reset_version_parts(version: Version, **kwargs: Any) -> Version:\n    \"\"\"\n    Update version parts and clear all subsequent parts in the sequence.\n\n    When __replace__ is available (packaging 26.0+), returns a new Version instance.\n    Otherwise mutates version via private ._version and returns the same instance.\n    \"\"\"\n    parts: dict[str, Any] = {}\n    ordered_part_names = (\"epoch\", \"release\", \"pre\", \"post\", \"dev\", \"local\")\n\n    reset = False\n    for part_name in ordered_part_names:\n        if reset:\n            parts[part_name] = kwargs.get(part_name)\n        elif part_name in kwargs:\n            parts[part_name] = kwargs[part_name]\n            reset = True\n        else:\n            parts[part_name] = getattr(version, part_name)\n\n    # Use __replace__ if available for efficiency\n    if hasattr(version, \"__replace__\"):\n        return version.__replace__(**parts)\n\n    # Reference: https://github.com/pypa/packaging/blob/20.9/packaging/version.py#L301-L310\n    internal_version = version._version  # noqa: SLF001\n    version._version = type(internal_version)(**parts)  # noqa: SLF001\n    return version\n\n\ndef update_release(original_version: Version, new_release_parts: list[int]) -> tuple[int, ...]:\n    # Retain release length\n    new_release_parts.extend(0 for _ in range(len(original_version.release) - len(new_release_parts)))\n\n    return tuple(new_release_parts)\n\n\ndef parse_letter_version(*args: Any, **kwargs: Any) -> tuple[Literal[\"a\", \"b\", \"rc\"], int]:\n    from packaging.version import _parse_letter_version  # noqa: PLC2701\n\n    return cast(tuple[Literal[\"a\", \"b\", \"rc\"], int], _parse_letter_version(*args, **kwargs))\n"
  },
  {
    "path": "backend/src/hatchling/version/source/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/version/source/code.py",
    "content": "from __future__ import annotations\n\nimport os\n\nfrom hatchling.version.source.plugin.interface import VersionSourceInterface\n\n\nclass CodeSource(VersionSourceInterface):\n    PLUGIN_NAME = \"code\"\n\n    def get_version_data(self) -> dict:\n        import sys\n        from importlib.util import module_from_spec, spec_from_file_location\n\n        relative_path = self.config.get(\"path\")\n        if not relative_path:\n            message = \"option `path` must be specified\"\n            raise ValueError(message)\n\n        if not isinstance(relative_path, str):\n            message = \"option `path` must be a string\"\n            raise TypeError(message)\n\n        path = os.path.normpath(os.path.join(self.root, relative_path))\n        if not os.path.isfile(path):\n            message = f\"file does not exist: {relative_path}\"\n            raise OSError(message)\n\n        expression = self.config.get(\"expression\") or \"__version__\"\n        if not isinstance(expression, str):\n            message = \"option `expression` must be a string\"\n            raise TypeError(message)\n\n        search_paths = self.config.get(\"search-paths\", [])\n        if not isinstance(search_paths, list):\n            message = \"option `search-paths` must be an array\"\n            raise TypeError(message)\n\n        absolute_search_paths = []\n        for i, search_path in enumerate(search_paths, 1):\n            if not isinstance(search_path, str):\n                message = f\"entry #{i} of option `search-paths` must be a string\"\n                raise TypeError(message)\n\n            absolute_search_paths.append(os.path.normpath(os.path.join(self.root, search_path)))\n\n        spec = spec_from_file_location(os.path.splitext(path)[0], path)\n        module = module_from_spec(spec)  # type: ignore[arg-type]\n\n        old_search_paths = list(sys.path)\n        try:\n            sys.path[:] = [*absolute_search_paths, *old_search_paths]\n            spec.loader.exec_module(module)  # type: ignore[union-attr]\n        finally:\n            sys.path[:] = old_search_paths\n\n        # Execute the expression to determine the version\n        version = eval(expression, vars(module))  # noqa: S307\n\n        return {\"version\": version}\n\n    def set_version(self, version: str, version_data: dict) -> None:\n        message = \"Cannot rewrite loaded code\"\n        raise NotImplementedError(message)\n"
  },
  {
    "path": "backend/src/hatchling/version/source/env.py",
    "content": "from __future__ import annotations\n\nimport os\n\nfrom hatchling.version.source.plugin.interface import VersionSourceInterface\n\n\nclass EnvSource(VersionSourceInterface):\n    PLUGIN_NAME = \"env\"\n\n    def get_version_data(self) -> dict:\n        variable = self.config.get(\"variable\", \"\")\n        if not variable:\n            message = \"option `variable` must be specified\"\n            raise ValueError(message)\n\n        if not isinstance(variable, str):\n            message = \"option `variable` must be a string\"\n            raise TypeError(message)\n\n        if variable not in os.environ:\n            message = f\"environment variable `{variable}` is not set\"\n            raise RuntimeError(message)\n\n        return {\"version\": os.environ[variable]}\n\n    def set_version(self, version: str, version_data: dict) -> None:\n        message = \"Cannot set environment variables\"\n        raise NotImplementedError(message)\n"
  },
  {
    "path": "backend/src/hatchling/version/source/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "backend/src/hatchling/version/source/plugin/hooks.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom hatchling.plugin import hookimpl\nfrom hatchling.version.source.code import CodeSource\nfrom hatchling.version.source.env import EnvSource\nfrom hatchling.version.source.regex import RegexSource\n\nif TYPE_CHECKING:\n    from hatchling.version.source.plugin.interface import VersionSourceInterface\n\n\n@hookimpl\ndef hatch_register_version_source() -> list[type[VersionSourceInterface]]:\n    return [CodeSource, EnvSource, RegexSource]\n"
  },
  {
    "path": "backend/src/hatchling/version/source/plugin/interface.py",
    "content": "from __future__ import annotations\n\nfrom abc import ABC, abstractmethod\n\n\nclass VersionSourceInterface(ABC):  # no cov\n    \"\"\"\n    Example usage:\n\n    ```python tab=\"plugin.py\"\n    from hatchling.version.source.plugin.interface import VersionSourceInterface\n\n\n    class SpecialVersionSource(VersionSourceInterface):\n        PLUGIN_NAME = \"special\"\n        ...\n    ```\n\n    ```python tab=\"hooks.py\"\n    from hatchling.plugin import hookimpl\n\n    from .plugin import SpecialVersionSource\n\n\n    @hookimpl\n    def hatch_register_version_source():\n        return SpecialVersionSource\n    ```\n    \"\"\"\n\n    PLUGIN_NAME = \"\"\n    \"\"\"The name used for selection.\"\"\"\n\n    def __init__(self, root: str, config: dict) -> None:\n        self.__root = root\n        self.__config = config\n\n    @property\n    def root(self) -> str:\n        \"\"\"\n        The root of the project tree as a string.\n        \"\"\"\n        return self.__root\n\n    @property\n    def config(self) -> dict:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.version]\n        ```\n        \"\"\"\n        return self.__config\n\n    @abstractmethod\n    def get_version_data(self) -> dict:\n        \"\"\"\n        This should return a mapping with a `version` key representing the current version of the project and will be\n        displayed when invoking the [`version`](../../cli/reference.md#hatch-version) command without any arguments.\n\n        The mapping can contain anything else and will be passed to\n        [set_version](reference.md#hatchling.version.source.plugin.interface.VersionSourceInterface.set_version)\n        when updating the version.\n        \"\"\"\n\n    def set_version(self, version: str, version_data: dict) -> None:\n        \"\"\"\n        This should update the version to the first argument with the data provided during retrieval.\n        \"\"\"\n        raise NotImplementedError\n"
  },
  {
    "path": "backend/src/hatchling/version/source/regex.py",
    "content": "from hatchling.version.core import VersionFile\nfrom hatchling.version.source.plugin.interface import VersionSourceInterface\n\n\nclass RegexSource(VersionSourceInterface):\n    PLUGIN_NAME = \"regex\"\n\n    def get_version_data(self) -> dict:\n        relative_path = self.config.get(\"path\", \"\")\n        if not relative_path:\n            message = \"option `path` must be specified\"\n            raise ValueError(message)\n\n        if not isinstance(relative_path, str):\n            message = \"option `path` must be a string\"\n            raise TypeError(message)\n\n        pattern = self.config.get(\"pattern\", \"\")\n        if not isinstance(pattern, str):\n            message = \"option `pattern` must be a string\"\n            raise TypeError(message)\n\n        version_file = VersionFile(self.root, relative_path)\n        version = version_file.read(pattern=pattern)\n\n        return {\"version\": version, \"version_file\": version_file}\n\n    def set_version(self, version: str, version_data: dict) -> None:  # noqa: PLR6301\n        version_data[\"version_file\"].set_version(version)\n"
  },
  {
    "path": "backend/tests/__init__.py",
    "content": ""
  },
  {
    "path": "backend/tests/downstream/datadogpy/data.json",
    "content": "{\n  \"repo_url\": \"https://github.com/DataDog/datadogpy\",\n  \"statements\": [\n    \"from datadog import initialize, api\"\n  ]\n}\n"
  },
  {
    "path": "backend/tests/downstream/datadogpy/pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"datadog\"\ndescription = \"The Datadog Python library\"\nreadme = \"README.md\"\nlicense = \"BSD-3-Clause\"\nkeywords = [\n  \"datadog\",\n]\nauthors = [\n  { name = \"Datadog, Inc.\", email = \"dev@datadoghq.com\" },\n]\nclassifiers = [\n  \"Operating System :: OS Independent\",\n  \"Programming Language :: Python :: 2.7\",\n  \"Programming Language :: Python :: 3.4\",\n  \"Programming Language :: Python :: 3.5\",\n  \"Programming Language :: Python :: 3.6\",\n  \"Programming Language :: Python :: 3.7\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  'Programming Language :: Python :: Implementation :: CPython',\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = [\n  \"requests>=2.6.0\",\n  \"typing; python_version<'3.5'\",\n  \"configparser<5; python_version<'3.0'\",\n]\ndynamic = [\"version\"]\n\n[project.urls]\n\"Bug Tracker\" = \"https://github.com/DataDog/datadogpy/issues\"\nDocumentation = \"https://datadogpy.readthedocs.io/en/latest/\"\n\"Source Code\" = \"https://github.com/DataDog/datadogpy\"\n\n[project.scripts]\ndog = \"datadog.dogshell:main\"\ndogwrap = \"datadog.dogshell.wrap:main\"\ndogshell = \"datadog.dogshell:main\"\ndogshellwrap = \"datadog.dogshell.wrap:main\"\n\n[tool.hatch.version]\npath = \"datadog/version.py\"\n\n[tool.hatch.build]\npackages = [\"datadog\"]\n\n[tool.hatch.build.targets.sdist]\ninclude = [\n  \"/LICENSE\",\n  \"/tests\",\n]\n\n[tool.hatch.build.targets.wheel]\n"
  },
  {
    "path": "backend/tests/downstream/hatch-showcase/data.json",
    "content": "{\n  \"repo_url\": \"https://github.com/ofek/hatch-showcase\",\n  \"statements\": [\n    \"from hatch_showcase.fib import fibonacci; assert fibonacci(32) == 2178309\"\n  ],\n  \"env_vars\": {\n    \"HATCH_BUILD_HOOKS_ENABLE\": \"true\"\n  }\n}\n"
  },
  {
    "path": "backend/tests/downstream/integrate.py",
    "content": "import errno\nimport json\nimport os\nimport platform\nimport shutil\nimport stat\nimport subprocess\nimport sys\nimport tempfile\nfrom contextlib import contextmanager\nfrom zipfile import ZipFile\n\nimport requests\nimport tomli\nfrom packaging.requirements import Requirement\nfrom packaging.specifiers import SpecifierSet\nfrom virtualenv import cli_run\n\nHERE = os.path.dirname(os.path.abspath(__file__))\nON_WINDOWS = platform.system() == \"Windows\"\n\n\ndef handle_remove_readonly(func, path, exc):  # no cov\n    # PermissionError: [WinError 5] Access is denied: '...\\\\.git\\\\...'\n    if func in {os.rmdir, os.remove, os.unlink} and exc[1].errno == errno.EACCES:\n        os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)\n        func(path)\n    else:\n        raise exc\n\n\nclass EnvVars(dict):\n    def __init__(self, env_vars=None, ignore=None):\n        super().__init__(os.environ)\n        self.old_env = dict(self)\n\n        if env_vars is not None:\n            self.update(env_vars)\n\n        if ignore is not None:\n            for env_var in ignore:\n                self.pop(env_var, None)\n\n    def __enter__(self):\n        os.environ.clear()\n        os.environ.update(self)\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        os.environ.clear()\n        os.environ.update(self.old_env)\n\n\ndef python_version_supported(project_config):\n    requires_python = project_config[\"project\"].get(\"requires-python\", \"\")\n    if requires_python:\n        python_constraint = SpecifierSet(requires_python)\n        if not python_constraint.contains(str(\".\".join(map(str, sys.version_info[:2])))):\n            return False\n\n    return True\n\n\ndef download_file(url, file_name):\n    response = requests.get(url, stream=True, timeout=20)\n    with open(file_name, \"wb\") as f:\n        for chunk in response.iter_content(16384):\n            f.write(chunk)\n\n\n@contextmanager\ndef temp_dir():\n    d = tempfile.mkdtemp()\n\n    try:\n        d = os.path.realpath(d)\n        yield d\n    finally:\n        shutil.rmtree(d, ignore_errors=False, onerror=handle_remove_readonly)\n\n\ndef get_venv_exe_dir(venv_dir):\n    exe_dir = os.path.join(venv_dir, \"Scripts\" if ON_WINDOWS else \"bin\")\n    if os.path.isdir(exe_dir):\n        return exe_dir\n\n    # PyPy\n    if ON_WINDOWS:\n        exe_dir = os.path.join(venv_dir, \"bin\")\n        if os.path.isdir(exe_dir):\n            return exe_dir\n\n        message = f\"Unable to locate executables directory within: {venv_dir}\"\n        raise OSError(message)\n\n    # Debian\n    if os.path.isdir(os.path.join(venv_dir, \"local\")):\n        exe_dir = os.path.join(venv_dir, \"local\", \"bin\")\n        if os.path.isdir(exe_dir):\n            return exe_dir\n\n        message = f\"Unable to locate executables directory within: {venv_dir}\"\n        raise OSError(message)\n\n    message = f\"Unable to locate executables directory within: {venv_dir}\"\n    raise OSError(message)\n\n\ndef main():\n    original_backend_path = os.path.dirname(os.path.dirname(HERE))\n    with temp_dir() as links_dir, temp_dir() as build_dir:\n        print(\"<<<<< Copying backend >>>>>\")\n        backend_path = os.path.join(build_dir, \"backend\")\n        shutil.copytree(original_backend_path, backend_path)\n\n        # Increment the minor version\n        version_file = os.path.join(backend_path, \"src\", \"hatchling\", \"__about__.py\")\n        with open(version_file, encoding=\"utf-8\") as f:\n            lines = f.readlines()\n\n        for i, line in enumerate(lines):\n            if line.startswith(\"__version__\"):\n                version = line.strip().split(\" = \")[1].strip(\"'\\\"\")\n                version_parts = version.split(\".\")\n                version_parts[1] = str(int(version_parts[1]) + 1)\n                lines[i] = line.replace(version, \".\".join(version_parts))\n                break\n        else:\n            message = \"No version found\"\n            raise ValueError(message)\n\n        with open(version_file, \"w\", encoding=\"utf-8\") as f:\n            f.writelines(lines)\n\n        print(\"<<<<< Building backend >>>>>\")\n        subprocess.check_call([sys.executable, \"-m\", \"build\", \"--wheel\", \"-o\", links_dir, backend_path])\n        subprocess.check_call([\n            sys.executable,\n            \"-m\",\n            \"pip\",\n            \"download\",\n            \"-q\",\n            \"--disable-pip-version-check\",\n            \"-d\",\n            links_dir,\n            os.path.join(links_dir, os.listdir(links_dir)[0]),\n        ])\n\n        constraints = []\n        constraints_file = os.path.join(build_dir, \"constraints.txt\")\n        with open(constraints_file, \"w\", encoding=\"utf-8\") as f:\n            f.write(\"\\n\".join(constraints))\n\n        for project in os.listdir(HERE):\n            project_dir = os.path.join(HERE, project)\n            if not os.path.isdir(project_dir):\n                continue\n\n            print(f\"<<<<< Project: {project} >>>>>\")\n            project_config = {}\n            potential_project_file = os.path.join(project_dir, \"pyproject.toml\")\n\n            # Not yet ported\n            if os.path.isfile(potential_project_file):\n                with open(potential_project_file, encoding=\"utf-8\") as f:\n                    project_config.update(tomli.loads(f.read()))\n\n                if not python_version_supported(project_config):\n                    print(\"--> Unsupported version of Python, skipping\")\n                    continue\n\n            with open(os.path.join(project_dir, \"data.json\"), encoding=\"utf-8\") as f:\n                test_data = json.loads(f.read())\n\n            with temp_dir() as d:\n                if \"repo_url\" in test_data:\n                    print(\"--> Cloning repository\")\n                    repo_dir = os.path.join(d, \"repo\")\n                    subprocess.check_call([\"git\", \"clone\", \"-q\", \"--depth\", \"1\", test_data[\"repo_url\"], repo_dir])\n                else:\n                    archive_name = f\"{project}.zip\"\n                    archive_path = os.path.join(d, archive_name)\n\n                    print(\"--> Downloading archive\")\n                    download_file(test_data[\"archive_url\"], archive_path)\n                    with ZipFile(archive_path) as zip_file:\n                        zip_file.extractall(d)\n\n                    entries = os.listdir(d)\n                    entries.remove(archive_name)\n                    repo_dir = os.path.join(d, entries[0])\n\n                project_file = os.path.join(repo_dir, \"pyproject.toml\")\n                if project_config:\n                    shutil.copyfile(potential_project_file, project_file)\n                else:\n                    if not os.path.isfile(project_file):\n                        sys.exit(\"--> Missing file: pyproject.toml\")\n\n                    with open(project_file, encoding=\"utf-8\") as f:\n                        project_config.update(tomli.loads(f.read()))\n\n                    for requirement in project_config.get(\"build-system\", {}).get(\"requires\", []):\n                        if Requirement(requirement).name == \"hatchling\":\n                            break\n                    else:\n                        sys.exit(\"--> Field `build-system.requires` must specify `hatchling` as a requirement\")\n\n                    if not python_version_supported(project_config):\n                        print(\"--> Unsupported version of Python, skipping\")\n                        continue\n\n                for file_name in (\"MANIFEST.in\", \"setup.cfg\", \"setup.py\"):\n                    possible_path = os.path.join(repo_dir, file_name)\n                    if os.path.isfile(possible_path):\n                        os.remove(possible_path)\n\n                venv_dir = os.path.join(d, \".venv\")\n                print(\"--> Creating virtual environment\")\n                cli_run([venv_dir, \"--no-download\", \"--no-periodic-update\"])\n\n                env_vars = dict(test_data.get(\"env_vars\", {}))\n                env_vars[\"VIRTUAL_ENV\"] = venv_dir\n                env_vars[\"PATH\"] = f\"{get_venv_exe_dir(venv_dir)}{os.pathsep}{os.environ['PATH']}\"\n                env_vars[\"PIP_CONSTRAINT\"] = constraints_file\n                with EnvVars(env_vars, ignore=(\"__PYVENV_LAUNCHER__\", \"PYTHONHOME\")):\n                    print(\"--> Installing project\")\n                    subprocess.check_call([\n                        shutil.which(\"pip\"),\n                        \"install\",\n                        \"-q\",\n                        \"--disable-pip-version-check\",\n                        \"--find-links\",\n                        links_dir,\n                        \"--no-deps\",\n                        repo_dir,\n                    ])\n\n                    print(\"--> Installing dependencies\")\n                    subprocess.check_call([\n                        shutil.which(\"pip\"),\n                        \"install\",\n                        \"-q\",\n                        \"--disable-pip-version-check\",\n                        repo_dir,\n                    ])\n\n                    print(\"--> Testing package\")\n                    for statement in test_data[\"statements\"]:\n                        subprocess.check_call([shutil.which(\"python\"), \"-c\", statement])\n\n                    scripts = project_config[\"project\"].get(\"scripts\", {})\n                    if scripts:\n                        print(\"--> Testing scripts\")\n                        for script in scripts:\n                            if not shutil.which(script):\n                                sys.exit(f\"--> Could not locate script: {script}\")\n\n                    print(\"--> Success!\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "backend/tests/downstream/requirements.txt",
    "content": "build\npackaging\nrequests\ntomli\nvirtualenv>=21\n"
  },
  {
    "path": "docs/.hooks/expand_blocks.py",
    "content": "import re\nimport textwrap\n\nfrom markdown.preprocessors import Preprocessor\n\n_code_tab_regex = re.compile(\n    r'^( *)((`{3,})[^ ].*) tab=\"(.+)\"\\n([\\s\\S]+?)\\n\\1\\3$',\n    re.MULTILINE,\n)\n_config_example_regex = re.compile(\n    r\"^( *)((`{3,})toml\\b.*) config-example\\n([\\s\\S]+?)\\n\\1\\3$\",\n    re.MULTILINE,\n)\n\n\ndef _code_tab_replace(m):\n    indent, fence_start, fence_end, title, content = m.groups()\n    return f\"\"\"\\\n{indent}=== \":octicons-file-code-16: {title}\"\n{indent}    {fence_start}\n{textwrap.indent(content, \"    \")}\n{indent}    {fence_end}\n\"\"\"\n\n\ndef _config_example_replace(m):\n    indent, fence_start, fence_end, content = m.groups()\n    content_without = re.sub(r\" *\\[tool.hatch\\]\\n\", \"\", content.replace(\"[tool.hatch.\", \"[\"))\n    return f\"\"\"\\\n{indent}=== \":octicons-file-code-16: pyproject.toml\"\n{indent}    {fence_start}\n{textwrap.indent(content, \"    \")}\n{indent}    {fence_end}\n\n{indent}=== \":octicons-file-code-16: hatch.toml\"\n{indent}    {fence_start}\n{textwrap.indent(content_without, \"    \")}\n{indent}    {fence_end}\n\"\"\"\n\n\nclass ExpandedBlocksPreprocessor(Preprocessor):\n    def run(self, lines):  # noqa: PLR6301\n        markdown = \"\\n\".join(lines)\n        markdown = _config_example_regex.sub(_config_example_replace, markdown)\n        markdown = _code_tab_regex.sub(_code_tab_replace, markdown)\n        return markdown.splitlines()\n"
  },
  {
    "path": "docs/.hooks/inject_version.py",
    "content": "import os\nimport subprocess\nfrom functools import cache\n\nfrom markdown.preprocessors import Preprocessor\n\nMARKER = \"<HATCH_LATEST_VERSION>\"\nSEMVER_PARTS = 3\n\n\n@cache\ndef get_latest_version():\n    env = dict(os.environ)\n    # Ignore the current documentation environment so that the version\n    # command can execute as usual in the default build environment\n    env.pop(\"HATCH_ENV_ACTIVE\", None)\n\n    output = subprocess.check_output([\"hatch\", \"--no-color\", \"version\"], env=env).decode(\"utf-8\").strip()  # noqa: S607\n\n    version = output.replace(\"dev\", \"\")\n    parts = list(map(int, version.split(\".\")))\n    major, minor, patch = parts[:SEMVER_PARTS]\n    if len(parts) > SEMVER_PARTS:\n        patch -= 1\n\n    return f\"{major}.{minor}.{patch}\"\n\n\nclass VersionInjectionPreprocessor(Preprocessor):\n    def run(self, lines):  # noqa: PLR6301\n        for i, line in enumerate(lines):\n            lines[i] = line.replace(MARKER, get_latest_version())\n\n        return lines\n"
  },
  {
    "path": "docs/.hooks/plugin_register.py",
    "content": "import os\nimport sys\n\nfrom markdown.extensions import Extension\n\nHERE = os.path.dirname(__file__)\n\n\ndef on_config(\n    config,\n    **kwargs,  # noqa: ARG001\n):\n    config.markdown_extensions.append(GlobalExtension())\n\n\nclass GlobalExtension(Extension):\n    def extendMarkdown(self, md):  # noqa: N802, PLR6301\n        sys.path.insert(0, HERE)\n\n        from expand_blocks import ExpandedBlocksPreprocessor\n        from inject_version import VersionInjectionPreprocessor\n        from render_default_test_env import TestEnvDefaultsPreprocessor\n        from render_ruff_defaults import RuffDefaultsPreprocessor\n\n        md.preprocessors.register(ExpandedBlocksPreprocessor(), ExpandedBlocksPreprocessor.__name__, 100)\n        md.preprocessors.register(VersionInjectionPreprocessor(), VersionInjectionPreprocessor.__name__, 101)\n        md.preprocessors.register(RuffDefaultsPreprocessor(), RuffDefaultsPreprocessor.__name__, 102)\n        md.preprocessors.register(TestEnvDefaultsPreprocessor(), TestEnvDefaultsPreprocessor.__name__, 103)\n\n        sys.path.pop(0)\n"
  },
  {
    "path": "docs/.hooks/render_default_test_env.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom ast import literal_eval\nfrom functools import cache\n\nimport tomlkit\nfrom markdown.preprocessors import Preprocessor\n\nMARKER_DEPENDENCIES = \"<HATCH_TEST_ENV_DEPENDENCIES>\"\nMARKER_MATRIX = \"<HATCH_TEST_ENV_MATRIX>\"\nMARKER_SCRIPTS = \"<HATCH_TEST_ENV_SCRIPTS>\"\n\n\n@cache\ndef test_env_config():\n    path = os.path.join(os.getcwd(), \"src\", \"hatch\", \"env\", \"internal\", \"test.py\")\n    with open(path, encoding=\"utf-8\") as f:\n        contents = f.read()\n\n    value = \"\".join(contents.split(\" return \")[1].strip().splitlines())\n    return literal_eval(value)\n\n\n@cache\ndef get_dependencies_toml():\n    env_config = {\"dependencies\": test_env_config()[\"dependencies\"]}\n    content = tomlkit.dumps({\"tool\": {\"hatch\": {\"envs\": {\"hatch-test\": env_config}}}}).strip()\n\n    # Reload to fix the long array\n    config = tomlkit.loads(content)\n    config[\"tool\"][\"hatch\"][\"envs\"][\"hatch-test\"][\"dependencies\"].multiline(True)\n\n    # Reduce indentation\n    content = tomlkit.dumps(config).strip()\n    return content.replace('    \"', '  \"')\n\n\n@cache\ndef get_matrix_toml():\n    env_config = {\"matrix\": test_env_config()[\"matrix\"]}\n    return tomlkit.dumps({\"tool\": {\"hatch\": {\"envs\": {\"hatch-test\": env_config}}}}).strip()\n\n\n@cache\ndef get_scripts_toml():\n    env_config = {\"scripts\": test_env_config()[\"scripts\"]}\n    return tomlkit.dumps({\"tool\": {\"hatch\": {\"envs\": {\"hatch-test\": env_config}}}}).strip()\n\n\nclass TestEnvDefaultsPreprocessor(Preprocessor):\n    def run(self, lines):  # noqa: PLR6301\n        return (\n            \"\\n\".join(lines)\n            .replace(MARKER_DEPENDENCIES, get_dependencies_toml())\n            .replace(MARKER_MATRIX, get_matrix_toml())\n            .replace(MARKER_SCRIPTS, get_scripts_toml())\n            .splitlines()\n        )\n"
  },
  {
    "path": "docs/.hooks/render_ruff_defaults.py",
    "content": "from __future__ import annotations\n\nimport os\nimport re\nfrom collections import defaultdict\nfrom functools import cache\nfrom typing import Any\n\nfrom markdown.preprocessors import Preprocessor\n\nMARKER_VERSION = \"<HATCH_RUFF_VERSION>\"\nMARKER_SELECTED_RULES = \"<HATCH_RUFF_SELECTED_RULES>\"\nMARKER_UNSELECTED_RULES = \"<HATCH_RUFF_UNSELECTED_RULES>\"\nMARKER_STABLE_RULES_COUNT = \"<HATCH_RUFF_STABLE_RULES_COUNT>\"\nMARKER_PREVIEW_RULES_COUNT = \"<HATCH_RUFF_PREVIEW_RULES_COUNT>\"\nMARKER_UNSELECTED_RULES_COUNT = \"<HATCH_RUFF_UNSELECTED_RULES_COUNT>\"\nMARKER_PER_FILE_IGNORED_RULES = \"<HATCH_RUFF_PER_FILE_IGNORED_RULES>\"\nRULE_URLS = {\"S\": \"https://docs.astral.sh/ruff/rules/#flake8-bandit-s\"}\n\n\ndef read_constants(path: str, start: str) -> dict[str, Any]:\n    with open(path, encoding=\"utf-8\") as f:\n        lines = f.read().splitlines()\n\n    for i, line in enumerate(lines):\n        if line.startswith(start):\n            block_start = i\n            break\n    else:\n        message = f\"Could not find {start} in {path}\"\n        raise RuntimeError(message)\n\n    data = {}\n    exec(\"\\n\".join(lines[block_start:]), None, data)  # noqa: S102\n    return data\n\n\ndef parse_rules(rules: tuple[str, ...]) -> defaultdict[str, list[str]]:\n    selected_rules: defaultdict[str, list[str]] = defaultdict(list)\n    separator = re.compile(r\"^(\\D+)(\\d+)$\")\n\n    for rule in rules:\n        match = separator.search(rule)\n        if match is None:\n            message = f\"Could not parse rule {rule}\"\n            raise RuntimeError(message)\n\n        group, number = match.groups()\n        selected_rules[group].append(number)\n\n    return selected_rules\n\n\ndef construct_collapsed_markdown_rule_list(text: str, rules: defaultdict[str, list[str]]) -> str:\n    preview_rule_set = set(ruff_data()[\"PREVIEW_RULES\"])\n\n    lines = [f'??? \"{text}\"']\n    for group, numbers in sorted(rules.items()):\n        numbers.sort(key=lambda x: int(x[0]))\n\n        parts = []\n        for number in numbers:\n            rule = f\"{group}{number}\"\n            part = f\"[{rule}](https://docs.astral.sh/ruff/rules/{rule})\"\n            if f\"{group}{number}\" in preview_rule_set:\n                part += \"^P^\"\n            parts.append(part)\n\n        lines.append(f\"    - {', '.join(parts)}\")\n\n    return \"\\n\".join(lines)\n\n\n@cache\ndef ruff_data():\n    root = os.getcwd()\n    data = {}\n    for path, start in (\n        (os.path.join(root, \"src\", \"hatch\", \"cli\", \"fmt\", \"core.py\"), \"STABLE_RULES\"),\n        (os.path.join(root, \"src\", \"hatch\", \"env\", \"internal\", \"static_analysis.py\"), \"RUFF_DEFAULT_VERSION\"),\n    ):\n        data.update(read_constants(path, start))\n\n    return data\n\n\n@cache\ndef get_ruff_version():\n    return ruff_data()[\"RUFF_DEFAULT_VERSION\"]\n\n\n@cache\ndef get_stable_rules_count():\n    return str(len(ruff_data()[\"STABLE_RULES\"]))\n\n\n@cache\ndef get_preview_rules_count():\n    return str(len(ruff_data()[\"PREVIEW_RULES\"]))\n\n\n@cache\ndef get_unselected_rules_count():\n    return str(len(UNSELECTED_RULES))\n\n\n@cache\ndef get_selected_rules():\n    data = ruff_data()\n    rules = parse_rules(data[\"STABLE_RULES\"])\n    for group, numbers in parse_rules(data[\"PREVIEW_RULES\"]).items():\n        rules[group].extend(numbers)\n\n    return construct_collapsed_markdown_rule_list(\"Selected rules\", rules)\n\n\n@cache\ndef get_unselected_rules():\n    return construct_collapsed_markdown_rule_list(\"Unselected rules\", parse_rules(UNSELECTED_RULES))\n\n\n@cache\ndef get_per_file_ignored_rules():\n    lines = []\n    for glob, rules in sorted(ruff_data()[\"PER_FILE_IGNORED_RULES\"].items()):\n        parts = []\n        for rule in rules:\n            url = RULE_URLS.get(rule) or f\"https://docs.astral.sh/ruff/rules/{rule}\"\n            parts.append(f\"[{rule}]({url})\")\n\n        lines.append(f\"- `{glob}`: {', '.join(parts)}\")\n\n    return \"\\n\".join(lines)\n\n\nclass RuffDefaultsPreprocessor(Preprocessor):\n    def run(self, lines):  # noqa: PLR6301\n        return (\n            \"\\n\".join(lines)\n            .replace(MARKER_VERSION, get_ruff_version())\n            .replace(MARKER_STABLE_RULES_COUNT, get_stable_rules_count())\n            .replace(MARKER_PREVIEW_RULES_COUNT, get_preview_rules_count())\n            .replace(MARKER_UNSELECTED_RULES_COUNT, get_unselected_rules_count())\n            .replace(MARKER_SELECTED_RULES, get_selected_rules())\n            .replace(MARKER_UNSELECTED_RULES, get_unselected_rules())\n            .replace(MARKER_PER_FILE_IGNORED_RULES, get_per_file_ignored_rules())\n            .splitlines()\n        )\n\n\nUNSELECTED_RULES: tuple[str, ...] = (\n    \"AIR001\",\n    \"AIR002\",\n    \"AIR301\",\n    \"AIR302\",\n    \"AIR311\",\n    \"AIR312\",\n    \"ANN001\",\n    \"ANN002\",\n    \"ANN003\",\n    \"ANN101\",\n    \"ANN102\",\n    \"ANN201\",\n    \"ANN202\",\n    \"ANN204\",\n    \"ANN205\",\n    \"ANN206\",\n    \"ANN401\",\n    \"B027\",\n    \"C901\",\n    \"COM812\",\n    \"COM819\",\n    \"CPY001\",\n    \"D100\",\n    \"D101\",\n    \"D102\",\n    \"D103\",\n    \"D104\",\n    \"D105\",\n    \"D106\",\n    \"D107\",\n    \"D200\",\n    \"D201\",\n    \"D202\",\n    \"D203\",\n    \"D204\",\n    \"D205\",\n    \"D206\",\n    \"D207\",\n    \"D208\",\n    \"D209\",\n    \"D210\",\n    \"D211\",\n    \"D212\",\n    \"D213\",\n    \"D214\",\n    \"D215\",\n    \"D300\",\n    \"D301\",\n    \"D400\",\n    \"D401\",\n    \"D402\",\n    \"D403\",\n    \"D404\",\n    \"D405\",\n    \"D406\",\n    \"D407\",\n    \"D408\",\n    \"D409\",\n    \"D410\",\n    \"D411\",\n    \"D412\",\n    \"D413\",\n    \"D414\",\n    \"D415\",\n    \"D416\",\n    \"D417\",\n    \"D418\",\n    \"D419\",\n    \"DJ001\",\n    \"DJ003\",\n    \"DJ006\",\n    \"DJ007\",\n    \"DJ008\",\n    \"DJ012\",\n    \"DJ013\",\n    \"E111\",\n    \"E114\",\n    \"E117\",\n    \"E301\",\n    \"E302\",\n    \"E303\",\n    \"E304\",\n    \"E305\",\n    \"E306\",\n    \"E501\",\n    \"E999\",\n    \"ERA001\",\n    \"FBT003\",\n    \"FIX001\",\n    \"FIX002\",\n    \"FIX003\",\n    \"FIX004\",\n    \"FURB101\",\n    \"FURB103\",\n    \"FURB140\",\n    \"ISC001\",\n    \"ISC002\",\n    \"NPY001\",\n    \"NPY002\",\n    \"NPY003\",\n    \"NPY201\",\n    \"PD002\",\n    \"PD003\",\n    \"PD004\",\n    \"PD007\",\n    \"PD008\",\n    \"PD009\",\n    \"PD010\",\n    \"PD011\",\n    \"PD012\",\n    \"PD013\",\n    \"PD015\",\n    \"PD101\",\n    \"PD901\",\n    \"PERF203\",\n    \"PGH001\",\n    \"PGH002\",\n    \"PGH003\",\n    \"PGH004\",\n    \"PLR0904\",\n    \"PLR0911\",\n    \"PLR0912\",\n    \"PLR0913\",\n    \"PLR0914\",\n    \"PLR0915\",\n    \"PLR0916\",\n    \"PLR0917\",\n    \"PLR1701\",\n    \"PLR1702\",\n    \"PLR1706\",\n    \"PT004\",\n    \"PT005\",\n    \"PTH100\",\n    \"PTH101\",\n    \"PTH102\",\n    \"PTH103\",\n    \"PTH104\",\n    \"PTH105\",\n    \"PTH106\",\n    \"PTH107\",\n    \"PTH108\",\n    \"PTH109\",\n    \"PTH110\",\n    \"PTH111\",\n    \"PTH112\",\n    \"PTH113\",\n    \"PTH114\",\n    \"PTH115\",\n    \"PTH116\",\n    \"PTH117\",\n    \"PTH118\",\n    \"PTH119\",\n    \"PTH120\",\n    \"PTH121\",\n    \"PTH122\",\n    \"PTH123\",\n    \"PTH124\",\n    \"PTH201\",\n    \"PTH202\",\n    \"PTH203\",\n    \"PTH204\",\n    \"PTH205\",\n    \"PTH206\",\n    \"PTH207\",\n    \"PTH208\",\n    \"PTH210\",\n    \"PTH211\",\n    \"Q000\",\n    \"Q001\",\n    \"Q002\",\n    \"Q003\",\n    \"Q004\",\n    \"RET501\",\n    \"RET502\",\n    \"RUF011\",\n    \"RUF035\",\n    \"RUF200\",\n    \"S320\",\n    \"S404\",\n    \"S410\",\n    \"S603\",\n    \"SIM401\",\n    \"TD001\",\n    \"TD002\",\n    \"TD003\",\n    \"TRY200\",\n    \"UP027\",\n    \"UP038\",\n    \"W191\",\n)\n"
  },
  {
    "path": "docs/.hooks/title_from_content.py",
    "content": "def on_page_markdown(\n    markdown,\n    page,\n    **kwargs,  # noqa: ARG001\n):\n    if \"title\" in page.meta:\n        return\n\n    first_line = markdown.strip().splitlines()[0]\n    if first_line.startswith(\"# \"):\n        title = first_line[2:].split(\" # {:\", maxsplit=1)[0].strip()\n        page.meta[\"title\"] = title\n        page.meta[\"social\"] = {\"cards_layout_options\": {\"title\": title}}\n"
  },
  {
    "path": "docs/.overrides/partials/copyright.html",
    "content": "<div class=\"md-copyright\">\n  {% if config.copyright %}\n    <div class=\"md-copyright__highlight\">\n      {{ config.copyright }}\n    </div>\n    <div>\n      Logo by\n      <a\n        href=\"https://openai.com/dall-e-2/\"\n        target=\"_blank\" rel=\"noopener\"\n      >\n        DALL·E\n      </a>\n      and\n      <a\n        href=\"https://boriscrowther.com/\"\n        target=\"_blank\" rel=\"noopener\"\n      >\n        Boris Crowther\n      </a>\n    </div>\n  {% endif %}\n  {% if not config.extra.generator == false %}\n    Made with\n    <a\n      href=\"https://squidfunk.github.io/mkdocs-material/\"\n      target=\"_blank\" rel=\"noopener\"\n    >\n      Material for MkDocs\n    </a>\n  {% endif %}\n</div>\n"
  },
  {
    "path": "docs/.snippets/abbrs.txt",
    "content": "*[PyPI]: Python Package Index\n"
  },
  {
    "path": "docs/.snippets/links.txt",
    "content": "[PEP 440 version specifiers]: https://peps.python.org/pep-0440/#version-specifiers\n[PEP 508]: https://peps.python.org/pep-0508/\n[PEP 517]: https://peps.python.org/pep-0517/\n[PEP 639]: https://peps.python.org/pep-0639/\n[PEP 660]: https://peps.python.org/pep-0660/\n[PEP 665]: https://peps.python.org/pep-0665/\n[project metadata standard]: https://packaging.python.org/en/latest/specifications/pyproject-toml/#declaring-project-metadata-the-project-table\n"
  },
  {
    "path": "docs/assets/badge/v0.json",
    "content": "{\"schemaVersion\":1,\"label\":\"\",\"message\":\"Hatch\",\"labelColor\":\"grey\",\"color\":\"#4051b5\",\"logoSvg\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" fill=\\\"none\\\" viewBox=\\\"0 0 1024 1024\\\"><path fill=\\\"url(#a)\\\" d=\\\"m544 406 5-37-57 24-64 29 17 12 63 6z\\\"/><path fill=\\\"#e9d77a\\\" stroke=\\\"#000\\\" d=\\\"m557 299 52 34-18 18-34 13-26 14-40 16-51 22-49 12 9-37-83-13-23-20-8-59 14-13 42-35 24-16 31-4v31l33-8 63 19h17l13 37z\\\"/><path fill=\\\"#fdf7dd\\\" stroke=\\\"#000\\\" d=\\\"m586 353 23-18 10 13 10 30v31l-12 32-22 31-37 30-22 31-10 27-22 8-67 31-46 49 6-45 25-67 35-43 45-44 39-48 7-36z\\\"/><path fill=\\\"#19a2f4\\\" stroke=\\\"#000\\\" d=\\\"m637 274-77 23 50 34 22 53-19 66-34 32-41 53-12 21 34 30 10-30 59-61 31-52 8-80z\\\"/><path fill=\\\"url(#b)\\\" d=\\\"m658 447 79-13-20-54-48-12z\\\"/><path fill=\\\"url(#c)\\\" d=\\\"m231 186 68-60 53 29-27 27 33 36h27l10 7-30 8-47 38-47-20v-18l-13-29-37-4z\\\"/><path fill=\\\"url(#d)\\\" d=\\\"m200 461 97 14 17 24 41-38 25 43 44-41 31 24-39 69-29 91-53-56v-24l-57-11-50-52-7 17-25-22z\\\"/><path fill=\\\"url(#e)\\\" d=\\\"m554 582 84-94 18-6 32 23 28-17 16 11h21l16-22 22-11 34 13v9l-100 31-12 28h-50l-22 53h-65z\\\"/><path fill=\\\"url(#f)\\\" d=\\\"m849 567-19-82-102 34-15 26h-51l-19 55h-66l-50-41-91 41-50 49-48-57-3-25-57-11-52-57-7 19-29-19-9 46c-1 10-18 109-11 192 6 83 101 174 136 188 34 15 168 60 340 26 138-27 192-171 203-239z\\\"/><path fill=\\\"url(#g)\\\" d=\\\"M571 77c-107-39-230 7-277 45l59 29-28 31 33 35h38v42l67-14 23 25 23 6 14 34 120-40 33 95 38 18 25 49 15 6c23-73 16-47 23-102-3-63-13-102-32-125s-40-85-174-134\\\"/><path stroke=\\\"#000\\\" stroke-width=\\\"16\\\" d=\\\"M510 440c-17 0-60-2-89-15\\\"/><path stroke=\\\"#000\\\" stroke-width=\\\"10\\\" d=\\\"M547 377c16 14 56 36 82 18m-104 35c2 17 17 49 62 47m-116 0c4 15 25 45 72 47m-125 32c10 10 38 29 71 20\\\"/><path stroke=\\\"#000\\\" stroke-width=\\\"10\\\" d=\\\"M553 299c58 18 84 66 76 107-17 82-66 65-104 152\\\"/><path fill=\\\"#e356e9\\\" d=\\\"M168 416c113 54 173 3 198-27l-33-8c-22 47-119 43-165 35\\\"/><path fill=\\\"#000\\\" d=\\\"m366 389 4 3 5-6-8-2zm-198 27 1-5a5 5 0 0 0-3 10zm165-35 1-5-4-1-2 4zm29 5c-12 14-32 34-63 43s-73 9-129-17l-4 9c57 27 102 28 136 18 33-10 55-32 68-47zm-195 35c24 4 60 7 93 3q26-3 46-12 22-9 31-29l-9-4q-8 15-26 24t-43 11c-32 4-67 1-90-3zm200-37-33-8-3 10 34 8z\\\"/><path stroke=\\\"#000\\\" stroke-width=\\\"16\\\" d=\\\"M388 645c5-37 29-119 71-158 53-48 88-76 91-119-16 2-42 15-65 29-40 23-91 32-96 32 2-10 9-32 7-38-3-7-75-1-102-29l-8-59c0-4 5-15 10-17 3-2 29-16 41-29 24-24 38-26 59-26\\\"/><path fill=\\\"#000\\\" d=\\\"M446 375c-25 14-46 5-58 5l12 25c9 0 31-1 45-8 17-9 43-39 53-47l30-19-17 1c-5 1-27 19-30 22-2 2-22 15-35 21\\\"/><path stroke=\\\"#000\\\" stroke-width=\\\"10\\\" d=\\\"m299 128 54 25-29 28 34 38h39v43l66-15 22 24 24 5 13 33 119-38 35 102 39 8 24 55\\\"/><path stroke=\\\"#000\\\" stroke-width=\\\"16\\\" d=\\\"m314 274-4-7-11 1-3-12-24-1v-27l-16-24c-5-5-31-2-43 0 38-48 138-150 289-136 145 13 337 136 252 370l-19-4-18 7-10-7-46 7m-28 43 25-3 28 24 31-20 13 14h26l11-23 24-9 34 9s161 511-334 490C11 946 205 467 205 467l26-7 70 16 13 23 42-39 26 45 42-40 31 24\\\"/><path stroke=\\\"#000\\\" stroke-width=\\\"10\\\" d=\\\"m827 485-102 36-12 25h-51l-20 54h-66l-47-42-93 42-51 52-49-61v-24l-59-9-50-58-7 21-20-21\\\"/><path stroke=\\\"#000\\\" stroke-width=\\\"16\\\" d=\\\"M631 276c23 28 56 113 23 176-41 78-84 94-98 131\\\"/><path fill=\\\"#000\\\" d=\\\"M327 301v-15c-7-1-15 5-18 8s0 12 2 15q4 2 7 0zm26 2 15-6c3 6 0 16-1 20-2 4-11 5-15 4q-3-4-2-6z\\\"/><path stroke=\\\"#000\\\" stroke-width=\\\"10\\\" d=\\\"M552 364c19-2 47-14 55-28\\\"/><path fill=\\\"#000\\\" d=\\\"M443 349c24 7 49-10 57-37q5-22-4-39l-10-2-24-23-18 4q-23 10-32 36c-7 28 7 55 31 61\\\"/><path fill=\\\"#fecd10\\\" d=\\\"M458 269q5-4 11-2 9 3 15 11 4 11 3 24c-1 9-6 31-24 32-18 2-19-16-18-31 1-21 9-30 13-34\\\"/><path fill=\\\"#fff\\\" d=\\\"m447 272-3-4-5-1q-4 0-5 2-4 3-1 11 4 8 10 2 4-6 4-10\\\"/><mask id=\\\"i\\\" width=\\\"43\\\" height=\\\"69\\\" x=\\\"444\\\" y=\\\"266\\\" maskUnits=\\\"userSpaceOnUse\\\" style=\\\"mask-type:alpha\\\"><path fill=\\\"#fe9f10\\\" d=\\\"M458 269q5-4 11-2 9 3 15 11 4 11 3 24c-1 9-6 31-24 32-18 2-19-16-18-31 1-21 9-30 13-34\\\"/></mask><g filter=\\\"url(#h)\\\" mask=\\\"url(#i)\\\"><path fill=\\\"#fe9f10\\\" d=\\\"m492 310-3-34-12-11-9-1-8 3c24 8 23 43 16 62z\\\"/></g><defs><linearGradient id=\\\"a\\\" x1=\\\"488.5\\\" x2=\\\"488.5\\\" y1=\\\"369.2\\\" y2=\\\"440.2\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#714d29\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#c1a180\\\"/></linearGradient><linearGradient id=\\\"b\\\" x1=\\\"697\\\" x2=\\\"697\\\" y1=\\\"367.7\\\" y2=\\\"446.7\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#714d29\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#c1a180\\\"/></linearGradient><linearGradient id=\\\"c\\\" x1=\\\"307.8\\\" x2=\\\"307.8\\\" y1=\\\"126.2\\\" y2=\\\"270.7\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#714d29\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#c1a180\\\"/></linearGradient><linearGradient id=\\\"d\\\" x1=\\\"324.8\\\" x2=\\\"324.8\\\" y1=\\\"461.2\\\" y2=\\\"647.2\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#c1a180\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#714d29\\\"/></linearGradient><linearGradient id=\\\"e\\\" x1=\\\"689.3\\\" x2=\\\"689.3\\\" y1=\\\"466.2\\\" y2=\\\"599.7\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#c1a180\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#714d29\\\"/></linearGradient><radialGradient id=\\\"f\\\" cx=\\\"0\\\" cy=\\\"0\\\" r=\\\"1\\\" gradientTransform=\\\"matrix(489 154 -218 695 284 643)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#fff\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#bbb\\\"/></radialGradient><radialGradient id=\\\"g\\\" cx=\\\"0\\\" cy=\\\"0\\\" r=\\\"1\\\" gradientTransform=\\\"rotate(31 31 849)scale(364 351)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#fff\\\"/><stop offset=\\\"1\\\" stop-color=\\\"silver\\\"/></radialGradient><filter id=\\\"h\\\" width=\\\"50\\\" height=\\\"82.5\\\" x=\\\"451\\\" y=\\\"255\\\" color-interpolation-filters=\\\"sRGB\\\" filterUnits=\\\"userSpaceOnUse\\\"><feFlood flood-opacity=\\\"0\\\" result=\\\"BackgroundImageFix\\\"/><feBlend in=\\\"SourceGraphic\\\" in2=\\\"BackgroundImageFix\\\" result=\\\"shape\\\"/><feGaussianBlur result=\\\"effect1_foregroundBlur_5_2\\\" stdDeviation=\\\"4.5\\\"/></filter></defs></svg>\"}"
  },
  {
    "path": "docs/assets/css/custom.css",
    "content": ":root > * {\n  /* Use font but disable ligatures, see https://github.com/pypa/hatch/issues/104 */\n  font-variant-ligatures: none;\n}\n\n/* Brighter links for dark mode */\n[data-md-color-scheme=slate] {\n  /* https://github.com/squidfunk/mkdocs-material/blob/9.1.2/src/assets/stylesheets/main/_colors.scss#L91-L92 */\n  --md-typeset-a-color: var(--md-primary-fg-color--light);\n}\n\n/* FiraCode https://github.com/tonsky/FiraCode */\ncode { font-family: 'Fira Code', monospace; }\n@supports (font-variation-settings: normal) {\n  code { font-family: 'Fira Code VF', monospace; }\n}\n\n/* https://github.com/squidfunk/mkdocs-material/issues/1522 */\n.md-typeset h5 {\n  color: var(--md-default-fg-color);\n  text-transform: none;\n}\n"
  },
  {
    "path": "docs/blog/.authors.yml",
    "content": "authors:\n  ofek:\n    name: Ofek Lev\n    description: Creator\n    avatar: https://avatars.githubusercontent.com/u/9677399\n  flying-sheep:\n    name: Phil A.\n    description: Contributor\n    avatar: https://avatars.githubusercontent.com/u/291575\n  cjames23:\n    name: Cary Hawkins\n    description: Co-maintainer\n    avatar: https://avatars.githubusercontent.com/u/8369962?v=4\n"
  },
  {
    "path": "docs/blog/index.md",
    "content": "# Blog\n"
  },
  {
    "path": "docs/blog/posts/release-hatch-1100.md",
    "content": "---\ndate: 2024-05-02\nauthors: [ofek,flying-sheep]\ndescription: >-\n  Hatch v1.10.0 brings a test command, support for UV, and a Python script runner.\ncategories:\n  - Release\n---\n\n# Hatch v1.10.0\n\nHatch [v1.10.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.10.0) brings a test command, support for UV, and a Python script runner.\n\n<!-- more -->\n\n## Test command\n\nThe new [`test`](../../cli/reference.md#hatch-test) command allows you to easily run tests for your project on multiple versions of Python. The default behavior follows best practices, using [pytest](https://github.com/pytest-dev/pytest) with select plugins for test execution and [coverage.py](https://github.com/nedbat/coveragepy) for code coverage measurement.\n\nThe command is designed to be both simple to use while also satisfying the needs of most projects. For example, the following shows Hatch running tests for [Jinja](https://github.com/pallets/jinja) in all environments in the [default matrix](../../config/internal/testing.md#matrix):\n\n<figure markdown>\n  ![Testing Jinja example](release-hatch-1100/testing-jinja.gif){ loading=lazy role=\"img\" }\n</figure>\n\nHere is us testing [Rich](https://github.com/Textualize/rich), with a bit of configuration:\n\n<figure markdown>\n  ![Testing Rich example](release-hatch-1100/testing-rich.gif){ loading=lazy role=\"img\" }\n</figure>\n\nSee the [tutorial](../../tutorials/testing/overview.md) for a detailed walk-through and the [config reference](../../config/internal/testing.md) for options.\n\n## UV\n\nThe package installer [UV](https://github.com/astral-sh/uv), brought to you by the same folks behind [Ruff](https://github.com/astral-sh/ruff), is now supported. In any environment, you can set the `installer` option to `uv` to use UV in place of [virtualenv](https://github.com/pypa/virtualenv) & [pip](https://github.com/pypa/pip) for virtual environment creation and dependency management, respectively. This often results in a significant performance benefit.\n\nFor example, if you wanted to enable this functionality for the [default](../../config/environment/overview.md#inheritance) environment, you could set the following:\n\n```toml config-example\n[tool.hatch.envs.default]\ninstaller = \"uv\"\n```\n\nSemi-internal environments like those used for [testing](../../config/internal/testing.md) and [static analysis](../../config/internal/static-analysis.md) have this enabled by default.\n\nSee the [how-to guide](../../how-to/environment/select-installer.md) for more information about switching the installer.\n\n## Python script runner\n\nThe [`run`](../../cli/reference.md#hatch-run) command now supports executing Python scripts with [inline metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/) as standardized by [PEP 723](https://peps.python.org/pep-0723/).\n\nAs an example, consider the following script:\n\n```python tab=\"script.py\"\n# /// script\n# requires-python = \">=3.11\"\n# dependencies = [\n#   \"httpx\",\n#   \"rich\",\n# ]\n# ///\n\nimport httpx\nfrom rich.pretty import pprint\n\nresp = httpx.get(\"https://peps.python.org/api/peps.json\")\ndata = resp.json()\npprint([(k, v[\"title\"]) for k, v in data.items()][:10])\n```\n\nIf you run the script for the first time as follows:\n\n```\nhatch run script.py\n```\n\nHatch will create a dedicated environment for that script using a version of Python greater than or equal to 3.11 with dependencies `httpx` and `rich`.\n\n<figure markdown>\n  ![Script running example](release-hatch-1100/run-script.gif){ loading=lazy role=\"img\" }\n</figure>\n\nSee the [how-to guide](../../how-to/run/python-scripts.md) for more information.\n\n## Static analysis\n\nThe environment used for static analysis is now [completely configurable](../../config/internal/static-analysis.md#customize-behavior) such that you can fully alter the underlying behavior of the [`fmt`](../../cli/reference.md#hatch-fmt) command (see the [how-to](../../how-to/static-analysis/behavior.md)).\n\nAdditionally, Ruff has been updated to version 1.4.0 and the rules selected by default have been updated accordingly. Check out their [blog post](https://astral.sh/blog/ruff-v0.4.0) about how the new hand-written parser has made it twice as fast!\n\n## Community highlights\n\n### Visual Studio Code\n\nVisual Studio Code [announced support](https://code.visualstudio.com/updates/v1_88#_hatch-environment-discovery) for Hatch environments in their latest release. This means that you can now easily discover and select Hatch environments for your projects directly from the editor.\n\nSee the [how-to guide](../../how-to/integrate/vscode.md) for detailed instructions.\n\n### CMake build plugin\n\nA [new release](https://github.com/scikit-build/scikit-build-core/releases/tag/v0.9.0) of the extension module builder [scikit-build-core](https://github.com/scikit-build/scikit-build-core) has introduced a [build plugin](https://scikit-build-core.readthedocs.io/en/stable/plugins/hatchling.html) for Hatchling. This means that you can use Hatchling as your build backend while also shipping extension modules built with CMake.\n\nTo get started, add the dependency to your [build requirements](../../config/build.md#build-system):\n\n```toml tab=\"pyproject.toml\"\n[build-system]\nrequires = [\"hatchling>=1.24.2\", \"scikit-build-core~=0.9.3\"]\nbuild-backend = \"hatchling.build\"\n```\n\nThen explicitly enable the `experimental` option (acknowledging that the plugin will move to a dedicated package in the future):\n\n```toml config-example\n[tool.hatch.build.targets.wheel.hooks.scikit-build]\nexperimental = true\n```\n\nAt this point, you can create your `CMakeLists.txt` file as usual and start building your extension modules with CMake! Check out the dedicated [example project](https://github.com/scikit-build/scikit-build-sample-projects/tree/main/projects/hatchling-pybind11-hello) for a complete demonstration.\n\n## Meta\n\n### Docs\n\nThe efforts toward documentation improvements have increased substantially and the priorities have [shifted](https://github.com/pypa/hatch/issues/1245). From now on expect to see far more tutorials and how-to guides rather than just reference material.\n\n### Future\n\nUpcoming features include:\n\n- workspaces functionality [similar to Cargo](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html)\n- commands to manage dependencies\n- Windows release artifacts signed by the PSF (just like macOS)\n- performance improvements for both the CLI and the Hatchling build system\n\n### Support\n\nIf you or your organization finds value in what Hatch provides, consider a [sponsorship](https://github.com/sponsors/ofek) to assist with maintenance and more rapid development!\n"
  },
  {
    "path": "docs/blog/posts/release-hatch-1160.md",
    "content": "---\ndate: 2025-11-24\nauthors: [cjames23]\ndescription: >-\n  Hatch v1.16.0 brings workspace support, dependency-groups, and sbom support.\ncategories:\n  - Release\n---\n\n\n# Hatch v1.16.0\n\nHatch [v1.16.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.0) brings support for workspaces, dependency groups, and SBOMs.\n\n<!-- more -->\n\n## Workspaces\n\nWorkspaces allow repositories with several related packages (e.g. monorepos) to be installed and tested in lockstep. Our design is inspired by [Cargo workspaces](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html).\n\nWorkspace members are defined by filters of relative or absolute paths, with support for glob patterns. Each member may also select which of their [features](../../config/environment/overview.md/#features) to enable.\n\nOne design choice that users will find different from a tool like uv is that workspaces are configured per environment. Adhering to Hatch's environment-first [philosophy](../../why.md/#environment-management) allows full compatibility with the environment feature set, such as matrices of workspaces with different configuration.\n\n### Example\n\n```toml config-example\n[tool.hatch.envs.default]\nworkspace.members = [\n  \"packages/core\",\n  \"packages/utils\",\n  \"packages/cli\"\n]\n```\n\nFor more information on usage, see the [workspace docs](../../how-to/environment/workspace.md).\n\n## Dependency Groups\n\nEnvironments now support [dependency groups](../../config/environment/overview.md#dependency-groups) as defined by [PEP-735](https://peps.python.org/pep-0735/). You can think of them as [features](../../config/environment/overview.md#features), but for non-runtime dependencies, never being included in user-facing package metadata.\n\n## Software Bill of Materials (SBOM)\n\nSupport for [PEP-770](https://peps.python.org/pep-0770/) has been added. This enables adding sbom files to wheels with hatchling. This support does not add sbom generation, only the ability to have already created sbom files added to a wheel during wheel builds.\n\nFor more information on usage, see [Wheel Options](../../plugins/builder/wheel.md#options).\n\n## Meta\n\n### Changes with maintainership\n\nSome may have noticed already during PR interactions, but I wanted to take some time to introduce myself as the new co-maintainer of hatch along with Ofek. I was browsing through the PyPA Discord, about to ask a question about workspace support for hatch, as I had created one version of it for the needs of my organization. That led to some discussions with Ofek and me taking on the contributions of finishing workspace support from where development had stopped. It made sense for me to join the efforts of maintainership with hatch and take some stress off of Ofek. I am excited to be here and to see what amazing things we can make hatch do in the future. \n\nA little about me as a developer and person. I have been writing Python code for 12 years now, and work at AWS as a Python Ecologist. My role is to provide tools to builders to be able to be more productive. In the past, I have made contributions to Airflow and a Poetry plugin for proxy setups. In my spare time, I enjoy hanging out with my dog Jacques, hiking, and rock climbing. And of course, I also enjoy giving back to the community in Python. \n\n### Future\n\nUpcoming features:\n\n- Typing default command like `hatch fmt` for linting.\n- Documentation improvements including contributor guidelines. \n- Performance improvements for both the CLI and the Hatchling build system.\n\n### Support\n\nIf you or your organization finds value in what Hatch provides, consider a [sponsorship](https://github.com/sponsors/ofek) to assist with maintenance and more rapid development!"
  },
  {
    "path": "docs/blog/posts/release-hatch-160.md",
    "content": "---\ndate: 2022-10-08\nauthors: [ofek]\ndescription: >-\n  Hatch v1.6.0 brings improvements to build environments, better handling\n  of dynamic metadata, and support for tools like Visual Studio Code.\ncategories:\n  - Release\n---\n\n# Hatch v1.6.0\n\nHatch [v1.6.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.6.0) brings improvements to build environments, better handling of dynamic metadata, and support for tools like Visual Studio Code.\n\n<!-- more -->\n\n## Build environments\n\nOriginally, the environment interface method for providing builder sub-environments was intended to be used in conjunction with some cleanup logic in order to provide a fresh setup every time. However, this is unnecessary in practice because build dependencies rarely change.\n\nWithout caching, repeat build environment use is slow which affects the following scenarios:\n\n- the [`build`](../../cli/reference.md#hatch-build) command\n- commands that read project metadata, like [`dep hash`](../../cli/reference.md#hatch-dep-hash), if any fields are [set dynamically](../../config/metadata.md#dynamic)\n\nNow a new environment interface method `build_environment_exists` is used by Hatch to determine whether or not it has already been created, for implementations that have a caching mechanism.\n\nThe [`virtual`](../../plugins/environment/virtual.md) environment type now uses this method to cache build environments.\n\n## Project metadata\n\nDynamically defined metadata is now supported everywhere, thanks to the new caching of `virtual` build environments.\n\nA [`project metadata`](../../cli/reference.md#hatch-project-metadata) command is introduced that displays the fully resolved [metadata](../../config/metadata.md). The output format is JSON unless a field is specified as an argument.\n\nFor example, if you checkout a project that is built by Hatch, like [FastAPI](https://github.com/tiangolo/fastapi), and run:\n\n```\nhatch project metadata readme\n```\n\nonly the `readme` text will be displayed. If the content is in Markdown, then [Rich](https://github.com/Textualize/rich) will render it directly in your terminal:\n\n![FastAPI readme](release-hatch-160/rich-readme.png)\n\n## Virtual environment location\n\nThe [`virtual`](../../plugins/environment/virtual.md) environment type now uses a flat layout for storage in the configured `virtual` [environment directory](../../config/hatch.md#environments) if the directory resides somewhere within the project root or if it is set to a `.virtualenvs` directory within the user's home directory.\n\nFor example, if you define the following Hatch configuration:\n\n```toml tab=\"config.toml\"\n[dirs.env]\nvirtual = \".hatch\"\n```\n\nand the following [matrix](../../config/environment/advanced.md#matrix):\n\n```toml config-example\n[[tool.hatch.envs.test.matrix]]\npython = [\"3.7\", \"3.8\", \"3.9\", \"3.10\", \"3.11\"]\n```\n\nthen [locating](../../cli/reference.md#hatch-env-find) environments with the following command:\n\n```\nhatch env find test\n```\n\nwill show that the general directory structure is:\n\n```\n.hatch\n├── test.py3.7\n├── test.py3.8\n├── test.py3.9\n├── test.py3.10\n└── test.py3.11\n```\n\nThis flat structure is required for detection of virtual environments by tools like Visual Studio Code and PyCharm.\n\nAdditionally, the `virtual` environment type now supports a `path` option to specify an explicit path that all [inherited](../../config/environment/overview.md#inheritance) environments will share, such as the common `.venv`.\n\n## Migration script improvements\n\nThe [script](https://github.com/pypa/hatch/blob/hatch-v1.6.0/src/hatch/cli/new/migrate.py) used to migrate [existing projects](../../intro.md#existing-project) from `setuptools` has been improved to handle more edge cases that were encountered in the wild and now no longer modifies the formatting of existing `pyproject.toml` configuration.\n\n## Hatchling\n\nHatch now depends on Hatchling [v1.11.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.11.0), which was also just released.\n\n### Environment version source\n\nA new [`env` version source](../../plugins/version-source/env.md) is available that allows for the project version to be defined by an environment variable.\n\n### Relaxed version bumping\n\nThe [`standard` version scheme](../../plugins/version-scheme/standard.md) now supports a `validate-bump` option that when set to `false` will forego the check when [updating the version](../../version.md#updating) that the desired version is higher than the current version.\n\nThis use case comes from [Project Jupyter](https://jupyter.org):\n\n> A common pattern we use in Jupyter is to bump to a `.dev0` minor version bump after making a release.  If we have a  bug fix that needs to go out in the interim, we'd rather not be forced to create a branch every time.\n"
  },
  {
    "path": "docs/blog/posts/release-hatch-180.md",
    "content": "---\ndate: 2023-12-11\nauthors: [ofek]\ndescription: >-\n  Hatch v1.8.0 brings Python distribution management, static analysis and\n  formatting backed by Ruff, and binaries for every platform.\ncategories:\n  - Release\n---\n\n# Hatch v1.8.0\n\nHatch [v1.8.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.8.0) brings Python distribution management, static analysis and formatting backed by Ruff, and binaries for every platform.\n\n<!-- more -->\n\n## Installation made easy\n\nOne thing that has been a perpetual problem for Hatch and other Python applications is that Python itself is a dependency. You, and more importantly your users, need to in some way get Python before your software can even be used. The recommended way to go about that is platform-dependent and even differs based on your target audience. I viewed this as a central UX problem for Hatch and so severe that I took a bit of a hiatus to solve it.\n\nLuckily, I have to my satisfaction solved this problem in the form of [PyApp](https://github.com/ofek/pyapp). It is a runtime installer for Python projects written in Rust. Apps are distributed as standalone executables as users have come to expect and bootstrapping occurs upon the first invocation. Here is an example of what you would see the first time you run a binary from this release:\n\n<figure markdown>\n  ![Installation example](release-hatch-180/install-demo.gif){ loading=lazy role=\"img\" }\n</figure>\n\nNow that we have binaries, creating installers for different platforms becomes trivial. Starting with this release not only are binaries available for every platform but also we have installers for Windows and macOS. The installer for macOS is signed using a certificate from the same account used to sign the official distributions from https://www.python.org, so users will not get any security pop-ups. Shout out to @ewdurbin for their extreme generosity in setting up multiple certificates in their free time!\n\nThese installers and binaries are now the [recommended way](../../install.md) to install and update Hatch. These binaries have built-in management so you can update to the latest version by running `hatch self update`.\n\n!!! note \"Windows signing\"\n    In future we will sign the installers for Windows but I did not have time to look into how that works. macOS signing took way longer than I anticipated :sweat_smile:\n\n## Python management\n\nFor a long time I and other users have desired that Hatch gain the ability to manage Python distributions. In my mind this was always blocked on a better installation experience because there was sort of a chicken-or-egg problem where you want a Python manager but you first need Python. No longer is that the case!\n\nThe new [`python`](../../cli/reference.md#hatch-python) command group allows for easy installation of various distributions to arbitrary locations which are then added to your PATH by default. Hatch supports CPython and PyPy distributions:\n\n<figure markdown>\n  ![Available Python distributions](release-hatch-180/available-pythons.png){ loading=lazy width=\"200\" role=\"img\" }\n</figure>\n\n## Virtual environment Python resolution\n\nThe `virtual` environment type is now far more intelligent when resolving the parent distribution to use and guarantees that, when no specific version is requested, the resolved distribution will always be [compatible](../../config/metadata.md#python-support) with the project.\n\nAdditionally, when a requested version cannot be found on PATH it will [automatically](../../plugins/environment/virtual.md#python-resolution) be downloaded and managed internally.\n\n## Static analysis\n\nThere is a new [`fmt`](../../cli/reference.md#hatch-fmt) command, backed entirely by [Ruff](https://github.com/astral-sh/ruff), that checks and fixes your code for formatting and linting issues.\n\nStarting with this release, Hatch maintains [default settings](../../config/internal/static-analysis.md#default-settings) that are guaranteed to be up-to-date and represent best practices for programming in modern Python. The idea is to provide defaults that are so broadly applicable that the majority of users will maintain little if any of their own [overrides](../../config/internal/static-analysis.md#extending-config).\n\nThe default behavior is internal management of settings to provide an OOTB experience that works. It is recommended however that you [persist](../../config/internal/static-analysis.md#persistent-config) the default config file in version control so that other tools like IDEs can utilize your full configuration.\n\nSince Ruff is now provided as a built-in feature, new project templates no longer have such configuration and are much less verbose.\n\n## Build improvements\n\n[Building](../../cli/reference.md#hatch-build) projects that do not use Hatchling as a backend is now supported and such builds are managed with the standard [build](https://github.com/pypa/build) tool.\n\nThe bridge between Hatch and the Hatchling CLI has been removed. Previously, the builder would send serialized messages to Hatch that would contain the desired content and style for each line of output. This was done in an effort to allow builder and build hook plugins to output pretty messages without actually requiring a dependency like [Rich](https://github.com/Textualize/rich). A problem that arises with this is that builders that invoke subprocesses will not display ANSI codes as one might expect and will lose out on the interactive experience of such invocations, like the built-in [binary builder plugin](../../plugins/builder/binary.md) calling `cargo build`. So now everything is simpler at the expense of no colored output without manual logic, or adding a dependency if you're a third-party plugin.\n\n## Faster environment usage\n\n[Spawning a shell](../../environment.md#entering-environments) or [running commands](../../environment.md#command-execution) within environments always first checks that your project's dependencies are satisfied and if not synchronizes the environment with what is defined. Previously, this had the potential to be quite slow for projects that have many dependencies.\n\nNow the set of dependency definitions is [hashed](../../plugins/environment/reference.md#hatch.env.plugin.interface.EnvironmentInterface.dependency_hash) and no check is performed if the hash is the same as before, significantly speeding up environment usage in most cases.\n\n## Hatchling\n\nHatch now depends on Hatchling [v1.19.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.19.0), which was also just released.\n\n### Better defaults\n\nHatchling is all about providing the best possible defaults, even at the expense of backward compatibility. In this release, there are two breaking changes that provide a much better user experience and were in fact requested by users.\n\n- Both the [`force-include`](../../config/build.md#forced-inclusion) option and the [`force_include_editable`](../../plugins/builder/wheel.md#build-data) wheel build data setting now raise errors if source paths do not exist.\n- The `wheel` build target now raises an error when no file inclusion options have been defined and none of its [heuristics](../../plugins/builder/wheel.md#default-file-selection) to determine what to ship are satisfied.\n\n### Binary build target\n\nA new [`binary`](../../plugins/builder/binary.md) build target is now stable that allows for the building of standalone binaries for projects. This is what Hatch itself uses for its binaries.\n\n## Meta\n\n### Why Hatch?\n\nA [new page](../../why.md) has been introduced that discusses the value proposition of Hatch and Hatchling in comparison to alternatives. Currently, it only addresses a few features but in future this page will become more comprehensive.\n\n### Future\n\nUpcoming features include a `test` command, commands to manage dependencies, and workspaces functionality [similar to Cargo](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html) that will make managing monorepos far easier.\n\nNext year there will be two large efforts that you should expect to see:\n\n1. A significant amount of my free time (and some at work) will be devoted to introducing lock file functionality in Hatch and trying to get whatever that happens to be standardized.\n\n    I met with @brettcannon about his thoughts post-[PEP 665](https://peps.python.org/pep-0665/) and about [mousebender](https://github.com/brettcannon/mousebender). I also met with the [prefix.dev](https://github.com/prefix-dev) team about [rip](https://github.com/prefix-dev/rip) and was fortunate enough to be shown a demo before its official announcement.\n\n    At the moment, the two options I see are to either go all in and contribute to mousebender or rely on the Prefix folks and use rip. The latter has the benefit of _potentially_ supporting Conda as a side effect with the downside of being quite new with the spec firmly out of our control. The former has the benefit of being able to easily gain institutional support from the Python packaging team and each of our employers with the downside being a significant amount of work needing to be done.\n\n1. When @henryiii is able to get some free time away from teaching I plan to work with him once again and push very hard for the Python build ecosystem to adopt the [extensionlib](https://github.com/ofek/extensionlib) approach.\n\n    I am of the opinion that the Python community has not fully completed the expressed outcome of [PEP 517][] in that build backends are still (for the most part) reliant on setuptools for building non-Python code bases.\n\n    Basically, there are components that interact with compilers to produce extension modules and components that pack files into an archive which we call a build backend. These are two distinct pieces of functionality and my view is that there should be an API that allows backends to consume extension module builders to find out where things got created and where they should be shipped inside archives.\n\n    In this hypothetical future any build backend would be able to trigger the building of extension modules based on user configuration.\n\n### Support\n\nIf you or your organization finds value in what Hatch provides, consider a [sponsorship](https://github.com/sponsors/ofek) to assist with maintenance and more rapid development!\n"
  },
  {
    "path": "docs/blog/posts/release-hatch-190.md",
    "content": "---\ndate: 2023-12-18\nauthors: [ofek]\ndescription: >-\n  Hatch v1.9.0 brings improvements to static analysis and important bug fixes.\ncategories:\n  - Release\n---\n\n# Hatch v1.9.0\n\nHatch [v1.9.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.9.0) brings improvements to static analysis and important bug fixes.\n\n<!-- more -->\n\n## Static analysis\n\nThe default version of Ruff has been increased to [v0.1.8](https://astral.sh/blog/ruff-v0.1.8). This release brings formatting capabilities to docstrings and Hatch enables this by default with line length set to 80. This length was chosen as the default because it plays nicely with the rendering of the most popular themes for Python documentation, such as [Material for MkDocs](https://github.com/squidfunk/mkdocs-material) and [Furo](https://github.com/pradyunsg/furo).\n\nAdditionally, it is now possible for projects to [pin](../../config/internal/static-analysis.md#dependencies) to specific versions of Ruff for upgrading at a later time:\n\n```toml config-example\n[tool.hatch.envs.hatch-static-analysis]\ndependencies = [\"ruff==X.Y.Z\"]\n```\n\n## Notable fixes\n\n- Python resolution for environments that do not install the project is no longer bound by the project's [Python requirement](../../config/metadata.md#python-support).\n- Fixed an edge case for out-of-the-box static analysis when there was existing configuration.\n- Compatibility checks for environments no longer occur if the environment is already created. This significantly increases the responsiveness of environment usage.\n"
  },
  {
    "path": "docs/build.md",
    "content": "# Builds\n\n-----\n\n## Configuration\n\nBuilds are [configured](config/build.md) using the `tool.hatch.build` table. Every [target](config/build.md#build-targets) is defined by a section within `tool.hatch.build.targets`, for example:\n\n```toml config-example\n[tool.hatch.build.targets.sdist]\nexclude = [\n  \"/.github\",\n  \"/docs\",\n]\n\n[tool.hatch.build.targets.wheel]\npackages = [\"src/foo\"]\n```\n\n## Building\n\nInvoking the [`build`](cli/reference.md#hatch-build) command without any arguments will build the [sdist](plugins/builder/sdist.md) and [wheel](plugins/builder/wheel.md) targets:\n\n```console\n$ hatch build\n[sdist]\ndist/hatch_demo-1rc0.tar.gz\n\n[wheel]\ndist/hatch_demo-1rc0-py3-none-any.whl\n```\n\nTo only build specific targets, use the `-t`/`--target` option:\n\n```console\n$ hatch build -t wheel\n[wheel]\ndist/hatch_demo-1rc0-py3-none-any.whl\n```\n\nIf the target supports multiple [versions](config/build.md#versions), you can specify the exact versions to build by appending a colon followed by the desired versions separated by commas:\n\n```console\n$ hatch -v build -t wheel:standard\n[wheel]\nBuilding `wheel` version `standard`\ndist/hatch_demo-1rc0-py3-none-any.whl\n```\n\n## Packaging ecosystem\n\nHatch [complies](config/build.md#build-system) with modern Python packaging specs and therefore your projects can be used by other tools with Hatch serving as just the build backend.\n\nSo you could use [tox](https://github.com/tox-dev/tox) as an alternative to Hatch's [environment management](environment.md), or [cibuildwheel](https://github.com/pypa/cibuildwheel) to distribute packages for every platform, and they both will transparently use Hatch without any extra modification.\n"
  },
  {
    "path": "docs/cli/about.md",
    "content": "# CLI usage\n\n-----\n\n## Verbosity\n\nThe amount of displayed output is controlled solely by the `-v`/`--verbose` (environment variable `HATCH_VERBOSE`) and  `-q`/`--quiet` (environment variable `HATCH_QUIET`) [root options](reference.md#hatch).\n\nThe levels are documented [here](../config/hatch.md#terminal).\n\n## Project awareness\n\nNo matter the [mode](../config/hatch.md#mode), Hatch will always change to the project's root directory for [entering](../environment.md#entering-environments) or [running commands](../environment.md#command-execution) in environments.\n\n## Tab completion\n\nCompletion is achieved by saving a script and then executing it as a part of your shell's startup sequence.\n\nAfterward, you'll need to start a new shell in order for the changes to take effect.\n\n=== \"Bash\"\n    Save the script somewhere:\n\n    ```console\n    _HATCH_COMPLETE=bash_source hatch > ~/.hatch-complete.bash\n    ```\n\n    Source the file in `~/.bashrc` (or `~/.bash_profile` if on macOS):\n\n    ```bash\n    . ~/.hatch-complete.bash\n    ```\n\n=== \"Z shell\"\n    Save the script somewhere:\n\n    ```console\n    _HATCH_COMPLETE=zsh_source hatch > ~/.hatch-complete.zsh\n    ```\n\n    Source the file in `~/.zshrc`:\n\n    ```zsh\n    . ~/.hatch-complete.zsh\n    ```\n\n=== \"fish\"\n    Save the script in `~/.config/fish/completions`:\n\n    ```console\n    _HATCH_COMPLETE=fish_source hatch > ~/.config/fish/completions/hatch.fish\n    ```\n"
  },
  {
    "path": "docs/cli/reference.md",
    "content": "::: mkdocs-click\n    :module: hatch.cli\n    :command: hatch\n    :depth: 0\n    :style: table\n    :remove_ascii_art: true\n"
  },
  {
    "path": "docs/community/contributing.md",
    "content": "# Contributing\n\nThe usual process to make a contribution is to:\n\n1. Check for existing related issues\n2. Fork the repository and create a new branch\n3. Make your changes\n4. Make sure formatting, linting and tests passes.\n5. Add tests if possible to cover the lines you added.\n6. Commit, and send a Pull Request.\n\n## Clone the repository\n\nClone the `hatch` repository, `cd` into it, and create a new branch for your contribution:\n\n```bash\ncd hatch\ngit switch -c add-my-contribution\n```\n\n## Run the tests\n\nRun the test suite while developing:\n\n```bash\nhatch test\n```\n\nRun the test suite with coverage report:\n\n```bash\nhatch test --cover\n```\n\nRun the extended test suite with coverage:\n\n```bash\nhatch test --cover --all\n```\n\n## Lint\n\nRun automated formatting:\n\n```bash\nhatch fmt\n```\n\nRun full linting and type checking:\n\n```bash\nhatch fmt --check\nhatch run types:check\n```\n\n## Docs\n\nStart the documentation in development:\n\n```bash\nhatch run docs:serve\n```\n\nBuild and validate the documentation website:\n\n```bash\nhatch run docs:build-check\n```"
  },
  {
    "path": "docs/community/highlights.md",
    "content": "# Community highlights\n\n-----\n\n## Integration\n\n- Project Jupyter - https://blog.jupyter.org/packaging-for-jupyter-in-2022-c7be64c38926\n- Visual Studio Code - https://code.visualstudio.com/updates/v1_88#_hatch-environment-discovery\n\n## Adoption\n\n- Black - https://ichard26.github.io/blog/2022/10/black-22.10.0/#goodbye-python-36-and-hello-hatchling\n- \"Switching to Hatch\" - https://andrich.me/2023/08/switching-to-hatch/\n"
  },
  {
    "path": "docs/community/users.md",
    "content": "# Users\n\n-----\n\nThe following is not intended to be a complete enumeration. Be sure to view the [development version](/dev/community/users/) of this page for an up-to-date listing.\n\n## Projects\n\n[aiogram](https://github.com/aiogram/aiogram/blob/a2e5f9a8b8c994ad65bce05cde9c744760f47c4c/pyproject.toml#L1-L3)\n| [Apache Airflow](https://github.com/apache/airflow/blob/ba2ba7f49395b528ea67611c423ddd71b64b8ede/pyproject.toml#L18-L39)\n| [argon2-cffi](https://github.com/hynek/argon2-cffi/blob/59c7470af1a65b3b71e18fbf9abeca2cca3d707a/pyproject.toml#L3-L5)\n| [attrs](https://github.com/python-attrs/attrs/blob/01413df3db8e64437547f7fa6439a646fa116a98/pyproject.toml#L3-L5)\n| [Black](https://github.com/psf/black/blob/f22273a72b3f1c15085f2d4a43e8d785bf48c822/pyproject.toml#L28-L30)\n| [coffea](https://github.com/CoffeaTeam/coffea/blob/bab41f66869293f8ba630556f21ac093828788b7/pyproject.toml#L1-L3)\n| [Colorama](https://github.com/tartley/colorama/blob/cd653d75be52f4d8c3953eb6942fe597375f8b97/pyproject.toml#L1-L5)\n| [Django Anymail](https://github.com/anymail/django-anymail/blob/63e355084c057d60bcce41afa1de315b163b6235/pyproject.toml#L1-L3)\n| [Django Debug Toolbar](https://github.com/jazzband/django-debug-toolbar/blob/d04b9d1a666fd6427604c92f86f91380597eae14/pyproject.toml#L1-L5)\n| [Django NYT](https://github.com/django-wiki/django-nyt/blob/b87107f5fadc2a77941bb15e7dfb95dba3d7f40d/pyproject.toml#L1-L3)\n| [Django OTP](https://github.com/django-otp/django-otp/blob/1cb288fceaab66e7921f80c27f40df475c056811/pyproject.toml#L135-L137)\n| [Django OTP Agents](https://github.com/django-otp/django-otp-agents/blob/b9cd473bef9153c05c8768f72208229f2a25951d/pyproject.toml#L118-L120)\n| [Django OTP Twilio](https://github.com/django-otp/django-otp-twilio/blob/a0c68a829cbffe373605df03f62e093b3f9d4170/pyproject.toml#L118-L120)\n| [Django OTP YubiKey](https://github.com/django-otp/django-otp-yubikey/blob/fbd121dfb0f4890745df10ce2fb129e2b588da24/pyproject.toml#L118-L120)\n| [Django Places](https://github.com/oscarmcm/django-places/blob/76630ccc1a45380d40cca1262fa4f9a269cf5112/pyproject.toml#L1-L3)\n| [Django Wiki](https://github.com/django-wiki/django-wiki/blob/1b03661c3fe7260b0eb82565cc3812b96de6b674/pyproject.toml#L1-L3)\n| [FastAPI](https://github.com/tiangolo/fastapi/blob/1073062c7f2c48bcc28bcedbdc009c18c171f6fb/pyproject.toml#L1-L3)\n| [filelock](https://github.com/tox-dev/filelock/blob/c06aa983616804c349007c7a536c361d0e1a8cff/pyproject.toml#L1-L6)\n| [Fluentd](https://github.com/fluent/fluent-logger-python/blob/1e58a7e8b62b435d42f80f7b8ca264012925edce/pyproject.toml#L1-L3)\n| [github3.py](https://github.com/sigmavirus24/github3.py/blob/94541f8adee67e39f3061c6b29db3e39cef5ce05/pyproject.toml#L1-L3)\n| [Gradio](https://github.com/gradio-app/gradio/blob/f43481c18ac6468fbf30bf9a80981b7eab453961/pyproject.toml#L1-L3)\n| [HTTPX](https://github.com/encode/httpx/blob/45b7cfaad3a8987ea35fa5bf092bbdda485444fd/pyproject.toml#L1-L3)\n| [iCalendar for Humans](https://github.com/ics-py/ics-py/blob/133a0955f6efbb83ff0eae45ad0bbe6902a8f2f1/pyproject.toml#L61-L63)\n| [LinkChecker](https://github.com/linkchecker/linkchecker/blob/de40321b57a2271e90e696b5320c0409faaa895d/pyproject.toml#L29-L34)\n| [Litestar](https://github.com/litestar-org/litestar/blob/f9e3f727e8ae71e4b58a518240fb6c66e83c10de/pyproject.toml#L181-L183)\n| [Material for MkDocs](https://github.com/squidfunk/mkdocs-material/blob/7ca1c1d623b4750d4aaa0cfd673b0ed2c6050c2b/pyproject.toml#L21-L23)\n| [MicroPython](https://github.com/micropython/micropython/blob/30a9ccf4caa72c62cb8656a1572518fef34b08a4/tools/mpremote/pyproject.toml#L1-L7)\n| [MkDocs](https://github.com/mkdocs/mkdocs/blob/65c24c21f0057ec4717d20d14d5fb7af22fe8caf/pyproject.toml#L1-L3)\n| [openSUSE](https://github.com/openSUSE/py2pack/blob/25be8cdb53ee6966213474e3399fe451f33993f6/pyproject.toml#L1-L3)\n| [Nox](https://github.com/wntrblm/nox/blob/cc710bde9d6a8781833144bac02a5f4581d9eca7/pyproject.toml#L1-L5)\n| [Packit](https://github.com/packit/packit/blob/6e286a7b4d0f79cd2a8213a8ae978788be5219c5/pyproject.toml#L1-L3)\n| [pipx](https://github.com/pypa/pipx/blob/bc7dd03c4d872c443257685109a650ec3d524814/pyproject.toml#L1-L3)\n| [platformdirs](https://github.com/platformdirs/platformdirs/blob/382e961c436f9974e56dc69ce105b6fd8945c343/pyproject.toml#L1-L3)\n| [Pydantic](https://github.com/pydantic/pydantic/blob/f341049b9e5538a125751d75b4e44c1609b53df6/pyproject.toml#L1-L3)\n| [Pygments](https://github.com/pygments/pygments/blob/0f3ddb3a6e3ed99957fe20aab695446f85835387/pyproject.toml#L1-L3)\n| [PyHamcrest](https://github.com/hamcrest/PyHamcrest/blob/07a787207619a7f7d51088d36051a632432a0144/pyproject.toml#L1-L3)\n| [PyMdown Extensions](https://github.com/facelessuser/pymdown-extensions/blob/72390ce2d0b40df638e31b75f1f02f45659724de/pyproject.toml#L1-L5)\n| [Python JSON Schema](https://github.com/python-jsonschema/jsonschema/blob/afc22f09e74d696ab00be8a711bbc5c2a15327b7/pyproject.toml#L1-L3)\n| [Rye](https://github.com/mitsuhiko/rye/blob/92b571bfd42e5748d2e535174d78fc7311a889a3/pyproject.toml#L20-L22)\n| [SALib](https://github.com/SALib/SALib/blob/7490a686e959b436f7db9bc9cf6fa4b2e7bfa3fc/pyproject.toml#L1-L3)\n| [Spack](https://github.com/spack/spack/blob/7a5e527cab5980cb4732bb3504fab77d75286a19/pyproject.toml#L36-L38)\n| [Starlette](https://github.com/encode/starlette/blob/31164e346b9bd1ce17d968e1301c3bb2c23bb418/pyproject.toml#L1-L3)\n| [structlog](https://github.com/hynek/structlog/blob/6e2e8c6025fb90484c5e6c5ff2fd3e96a61854cf/pyproject.toml#L3-L5)\n| [tox](https://github.com/tox-dev/tox/blob/f2b4a4a6f5e8bbc8f9f0cff3dd5d17c50e874172/pyproject.toml#L1-L3)\n| [Twisted](https://github.com/twisted/twisted/blob/960e26bb1f4c67b3f7819553d0c45b25e6db4aae/pyproject.toml#L1-L7)\n| [urllib3](https://github.com/urllib3/urllib3/blob/8dda1974ae51839304f8517ab7993006c0d9db2e/pyproject.toml#L3-L5)\n| [Uvicorn](https://github.com/encode/uvicorn/blob/ccd1aae48e49dd8c9365600fd79e886efe88be1d/pyproject.toml#L1-L3)\n| [virtualenv](https://github.com/pypa/virtualenv/blob/69664d522d98899c21dcf0e88a0af3efcb0c71e7/pyproject.toml#L1-L6)\n| [Voilà](https://github.com/voila-dashboards/voila/blob/71292e4124b1f4a6f91c8b4e16ea9ad6b5ef500b/pyproject.toml#L1-L7)\n| [XGBoost](https://github.com/dmlc/xgboost/blob/62571b79eb08398a031873c3704da4e9cfd2c301/python-package/pyproject.toml#L1-L6)\n| [Ypy](https://github.com/y-crdt/ypy/tree/b9241a9e7ca248b6c44b62707d719b1ef20eef74#using-hatch)\n| [yt-dlp](https://github.com/yt-dlp/yt-dlp/blob/111b61ddef305584d45a48e7b7c73ffcedf062a2/pyproject.toml#L1-L3)\n\n## Industry\n\n- [Anaconda](https://www.anaconda.com) <sup>\\[[1](https://github.com/ContinuumIO/dask-awkward/blob/105275b1937cce9a80a352af0b200d4e264f27f7/pyproject.toml#L1-L3)|[2](https://github.com/conda-incubator/ensureconda/blob/b20dbcf7166009ff4e9270f35ed75da7afc3db60/pyproject.toml#L1-L3)|[3](https://github.com/conda-incubator/conda-lock/blob/9187487698f9afbb08e131cd585a17bba82ce9f2/pyproject.toml#L1-L3)|[4](https://github.com/conda-incubator/conda-auth/blob/437ca609ea8bf4b8bd91d32dd427abe8294f6a3b/pyproject.toml#L1-L3)|[5](https://github.com/conda/conda-content-trust/blob/f72a50b04126177f37b965c25d02564223b7acf8/pyproject.toml#L1-L6)|[6](https://github.com/conda/conda-build/blob/37ab8d3de084d32b907b726ba2ad4570e91d326b/pyproject.toml#L1-L6)|[7](https://github.com/conda/conda/blob/0c38f5660f7eca66434827af910beddf9f7e462d/pyproject.toml#L1-L6)\\]</sup>\n- [Airbnb](https://www.airbnb.com) <sup>\\[[1](https://github.com/airbnb/omniduct/blob/98c66e10b493c83d42f69bc6b97fab7a8c91eab1/pyproject.toml#L1-L3)\\]</sup>\n- [Astronomer](https://www.astronomer.io) <sup>\\[[1](https://github.com/astronomer/astronomer-cosmos/blob/29886492a46cf1dccd4c17a1643010975cb8094a/pyproject.toml#L1-L3)|[2](https://github.com/astronomer/astro-provider-databricks/blob/3e1ca039a024a98f9079d178478aa24702e15453/pyproject.toml#L1-L3)|[3](https://github.com/astronomer/astro-providers-template/blob/5be542eb5763f3d9accc7d6d7bc35c9214d15904/pyproject.toml#L1-L3)\\]</sup>\n- [Bitwarden](https://bitwarden.com) <sup>\\[[1](https://github.com/bitwarden/gh-actions/blob/c3bc6a192283618c6ae92f33bde7c2f28e198539/lint-workflow-v2/pyproject.toml#L1-L3)\\]</sup>\n- [Bloomberg](https://www.bloomberg.com) <sup>\\[[1](https://github.com/bloomberg/ipydatagrid/blob/04b73fe67bf33d054e69036fe2794ac72057b105/pyproject.toml#L1-L6)|[2](https://github.com/bloomberg/pytest-memray/blob/4ea6a7608adb0de4572d35768fbd370aee016627/pyproject.toml#L1-L3)\\]</sup>\n- [Blue Robotics](https://bluerobotics.com) <sup>\\[[1](https://github.com/bluerobotics/navigator-lib/blob/1d8afadb0804ffbbf32147232b1c627e92786c07/pyproject.toml#L26-L38)\\]</sup>\n- [Cars.com](https://www.cars.com) <sup>\\[[1](https://github.com/carsdotcom/cars-forge/blob/ba14db991a5c7cb3c5adc3a4a364121e43f6aa0e/pyproject.toml#L63-L65)\\]</sup>\n- [Cisco](https://www.cisco.com) <sup>\\[[1](https://github.com/CiscoDevNet/sastre/blob/76da836c9df01f1d3d40df5475c0d2caff4db566/pyproject.toml#L1-L3)|[2](https://github.com/CiscoDevNet/sdwan-devops/blob/bb6dde778af881be257fab722b12196599f63ddf/sdwan_config_builder/pyproject.toml#L1-L3)\\]</sup>\n- [Databricks](https://www.databricks.com) <sup>\\[[1](https://github.com/databrickslabs/ucx/blob/80145a4f2b6dccf65c1ad048fdb4d1e2622afa09/pyproject.toml#L1-L3)|[2](https://github.com/databricks-industry-solutions/many-model-forecasting/blob/a9e347b0444354bf836a8f528e4deb547e7bdd05/pyproject.toml#L35-L37)|[3](https://github.com/databrickslabs/pylint-plugin/blob/3b33c79dea74bdaac011488e16ad0121db4150b1/pyproject.toml#L34-L36)\\]</sup>\n- [Datadog](https://www.datadoghq.com) <sup>\\[[1](https://github.com/DataDog/datadogpy/blob/63d0c01b5bbcb8158cf3ddab153639951ab44945/pyproject.toml#L1-L3)|[2](https://github.com/DataDog/integrations-core/pulls?q=is%3Apr+author%3Aofek+in%3Atitle+Add+pyproject.toml+file)|[3](https://github.com/DataDog/integrations-extras/pulls?q=is%3Apr+author%3Aofek+in%3Atitle+Add+pyproject.toml+file)|[4](https://github.com/DataDog/mkdocs-click/blob/434925323f3bb187595d4c7f6a2c80b790015109/pyproject.toml#L1-L3)\\]</sup>\n- [deepset](https://www.deepset.ai) <sup>\\[[1](https://github.com/deepset-ai/haystack/blob/728383a14968111b0a032480ac276d6e3313332b/pyproject.toml#L1-L5)|[2](https://github.com/deepset-ai/deepset-cloud-sdk/blob/18c76d4b7a3863040fac0d9e6f47c765f266d7fa/pyproject.toml#L1-L3)\\]</sup>\n- [Elastic](https://www.elastic.co) <sup>\\[[1](https://github.com/elastic/rally/blob/8ba7980bb25b85f25fe20f3fd5dd8e12b9b1214b/pyproject.toml#L1-L3)|[2](https://github.com/elastic/rally-tracks/blob/33840005cd3e2a6191d73a567e5c2c0858169270/pyproject.toml#L1-L3)|[3](https://github.com/elastic/curator/blob/b41743a061ad790820affe7acee5f71abe819357/pyproject.toml#L1-L3)\\]</sup>\n- [Google](https://about.google) <sup>\\[[1](https://github.com/google/latexify_py/blob/9307e6e70df0d0a5f7d524833a85e2c25ffe66ef/pyproject.toml#L1-L5)|[2](https://github.com/google/gcp_scanner/blob/93dc594a6d920d1aff9bc8fef780a32056c12e27/pyproject.toml#L1-L3)|[3](https://github.com/GoogleCloudPlatform/cloud-build-samples/blob/a66407bc412a2726781f30063923a49bb6789064/python-example-noncontainer-artifacts/pyproject.toml#L1-L3)|[4](https://github.com/google/visualblocks/blob/3809f598253cdad2d93ed82b1e2623c10b4a5a0b/python/pyproject.toml#L1-L3)|[5](https://github.com/google/jaxtyping/blob/1acc0d7153f3881870b0376496d8efa27689cb3b/pyproject.toml#L29-L31)|[6](https://github.com/GoogleCloudPlatform/database-assessment/blob/d14d587cb2cab55cc0b1b92d79d0b30f12807b42/pyproject.toml#L126-L128)\\]</sup>\n- [IBM](https://www.ibm.com) <sup>\\[[1](https://github.com/IBM/python-log-router/blob/b0fc624cde262c6faadd5cb2e780e1ed7847f6c2/pyproject.toml#L1-L3)\\]</sup>\n- [JPMorgan Chase](https://www.jpmorganchase.com) <sup>\\[[1](https://github.com/jpmorganchase/jupyter-fs/blob/e7ea3ced16e8f7f1297ac8bed3f028b641558256/pyproject.toml#L1-L7)\\]</sup>\n- [Intel Corporation](https://www.intel.com) <sup>\\[[1](https://github.com/intel/neural-compressor/blob/5f6f38b96d45d0253b8de239df51c09b2471a8fb/neural_coder/extensions/neurl_compressor_ext_lab_alibaba/pyproject.toml#L1-L3)|[2](https://github.com/intel/tdx-tools/blob/ba4ba1796f21388d15cb14ecf673747c303ea0ae/utils/ovmfkeyenroll/pyproject.toml#L1-L3)|[3](https://github.com/intel/open-domain-question-and-answer/blob/6d8e90acb738ea3fe33d400c549c45ee05461afc/pyproject.toml#L1-L5)\\]</sup>\n- [McKinsey](https://www.mckinsey.com) <sup>\\[[1](https://github.com/mckinsey/vizro/blob/a7e88f19b7f50df19f9e0981ae19b36ccd83bc52/vizro-core/pyproject.toml#L1-L3)|[2](https://github.com/mckinsey/vizro/blob/a7e88f19b7f50df19f9e0981ae19b36ccd83bc52/vizro-ai/pyproject.toml#L1-L3)\\]</sup>\n- [Meta](https://about.facebook.com) <sup>\\[[1](https://github.com/facebook/usort/blob/b3d1dc49abac0c06ac29f1ceb332d2b86a50e850/pyproject.toml#L1-L3)|[2](https://github.com/Instagram/Fixit/blob/c95b0ef9f8c02adfd6a541b55f22f0bd6a922706/pyproject.toml#L1-L3)|[3](https://github.com/meta-llama/llama-recipes/blob/44b66374bec23ad77c00af4348197e6641a8d2e3/pyproject.toml#L1-L3)\\]</sup>\n- [Microsoft](https://www.microsoft.com) <sup>\\[[1](https://github.com/microsoft/qsharp/blob/2ef271eea86f6cc4dff3c79526aaa79422489fcd/jupyterlab/pyproject.toml#L1-L3)|[2](https://github.com/microsoft/responsible-ai-toolbox-tracker/blob/4e37f81726ba7ccf76d0539a5edc3ba6a988c3a5/pyproject.toml#L1-L7)|[3](https://github.com/microsoft/CoML/blob/9a4d670c3f7ff7710556b8d75e502824f74664ce/pyproject.toml#L1-L3)|[4](https://github.com/microsoft/microxcaling/blob/142efb98622df68e4a4c01ca77d2fc02dfdec261/pyproject.toml#L18-L20)|[5](https://github.com/microsoft/sca-fuzzer/blob/c0d42786e06115daf8281e40e5475e8e69f6b10e/pyproject.toml#L1-L3)|[6](https://github.com/microsoft/TypeChat/blob/f53b971179d0136424a75d67287903a2421af98b/python/pyproject.toml#L1-L3)\\]</sup>\n- [OpenAI](https://openai.com) <sup>\\[[1](https://github.com/openai/openai-python/blob/e36956673d9049713c91bca6ce7aebe58638f483/pyproject.toml#L88-L90)\\]</sup>\n- [Oracle](https://www.oracle.com) <sup>\\[[1](https://github.com/oracle/graalpython/blob/9b41424fd80727614878b5903f9d8ae0447bfd4e/graalpy_virtualenv/pyproject.toml#L40-L42)\\]</sup>\n- [Palo Alto Networks](https://www.paloaltonetworks.com) <sup>\\[[1](https://github.com/PaloAltoNetworks/pc-python-integration/blob/a3e29d71c6704dfb07cf85d592dec15a9ea575b7/pyproject.toml#L1-L3)\\]</sup>\n- [Quansight](https://quansight.com) <sup>\\[[1](https://github.com/Quansight-Labs/jupyter-a11y-testing/blob/f36bf5b2e8cb87613c637fc5aa03401c92ec58d0/pyproject.toml#L3-L6)\\]</sup>\n- [Red Hat](https://www.redhat.com) <sup>\\[[1](https://github.com/RedHatQE/wrapanapi/blob/036f85a7fa97b86eee732804f61cfe574c571a6e/pyproject.toml#L1-L3)|[2](https://github.com/RedHatQE/widgetastic.core/blob/c40d7f50f3e55c9ac9f0da1b91a56f89949bbe0c/pyproject.toml#L52-L54)|[3](https://github.com/RedHatQE/widgetastic.patternfly4/blob/5b19fcdc123732639edc8cf715dbe5fc64f3bd28/pyproject.toml#L38-L40)|[4](https://github.com/redhat-developer/devspaces-images/blob/db8de2f54466e37986ce64d96436b566c75b0677/devspaces-udi/build/python/requirements-build.in#L12)|[5](https://github.com/RedHatQE/Sentaku/blob/19dc91c00b70cb2054e0c28d69906e894fa8c104/pyproject.toml#L1-L6)\\]</sup>\n- [Salesforce](https://www.salesforce.com) <sup>\\[[1](https://github.com/SalesforceAIResearch/uni2ts/blob/ce27c2f9a0c6ee9119997e8ef0026388f143dcd6/pyproject.toml#L1-L3)\\]</sup>\n- [Snowflake](https://www.snowflake.com) <sup>\\[[1](https://github.com/Snowflake-Labs/snowcli/blob/a8cafe80ef81969655a4391425b0f45c2874d1a4/pyproject.toml#L1-L3)\\]</sup>\n- [Splunk](https://www.splunk.com) <sup>\\[[1](https://github.com/splunk/splunk-mltk-container-docker/blob/e13ae55a4a16ea459092ee9c1e9ba9772cbe6bf2/package-dsdlsupport/pyproject.toml#L1-L3)\\]</sup>\n- [The Westervelt Company](https://westervelt.com) <sup>\\[[1](https://github.com/westerveltco/django-twc-project/blob/f20768d4d42761ec0ce44f3f2283b66e47f2c8f8/pyproject.toml#L1-L3)|[2](https://github.com/westerveltco/django-email-relay/blob/e576c0561408f3c27babc9035b7284fd580a69c2/pyproject.toml#L1-L3)|[3](https://github.com/westerveltco/django-simple-nav/blob/4c0dfd5ee4bfa28fd6696e1394e6bbe2e119bfcc/pyproject.toml#L1-L3)|[4](https://github.com/westerveltco/django-q-registry/blob/59ae52978a8d900b05a50465f40e2834a16f4303/pyproject.toml#L1-L3)|[5](https://github.com/westerveltco/wagtail-heroicons/blob/a8b8985ec3994156b85c07a440a30c8ad2f21263/pyproject.toml#L1-L3)|[6](https://github.com/westerveltco/django-opfield/blob/7818ce3cdc56d25807cd5bc8f613eb12de2c6177/pyproject.toml#L1-L3)\\]</sup>\n- [Virtru](https://www.virtru.com) <sup>\\[[1](https://github.com/virtru/access-pdp/blob/46089e8a2ef691b80f92bbd6777bdfbcff1c1671/clients/python/accesspdp/pyproject.toml#L24-L26)|[2](https://github.com/virtru/access-pdp/blob/46089e8a2ef691b80f92bbd6777bdfbcff1c1671/clients/python/attributes/pyproject.toml#L21-L23)\\]</sup>\n- [VMware](https://www.vmware.com) <sup>\\[[1](https://github.com/vmware/versatile-data-kit/blob/f77faec3e9ccd840b6dc6fdc95af8a434e822e71/projects/vdk-plugins/vdk-jupyter/vdk-jupyterlab-extension/pyproject.toml#L1-L3)|[2](https://github.com/vmware/repository-service-tuf-cli/blob/374f1ac0c2a4ada6d7a7c26fba55e811f2998be8/pyproject.toml#L1-L4)|[3](https://github.com/vmware/vhpc-toolkit/blob/b8429bc4753caa302a4fc8bb160cca89e84cfd45/pyproject.toml#L20-L22)\\]</sup>\n- [Volvo Group](https://www.volvogroup.com) <sup>\\[[1](https://github.com/VolvoGroup/dymoval/blob/75261b85635dce594719b01c5fc33ad951ce55b0/pyproject.toml#L1-L3)\\]</sup>\n\n## Organizations\n\n- [Free Ebook Foundation](https://ebookfoundation.org) <sup>\\[[1](https://github.com/EbookFoundation/alt-text/blob/00433b1a971309a441ef4822322cc6ea6347d9b2/pyproject.toml#L1-L3)\\]</sup>\n- [Greater Paris University Hospitals (AP-HP)](https://www.aphp.fr) <sup>\\[[1](https://github.com/aphp/edspdf/blob/ec083ed7fedddbdbb398c6feee530e05273f7dbb/pyproject.toml#L195-L197)\\]</sup>\n- [Massachusetts General Hospital](https://www.massgeneral.org) <sup>\\[[1](https://github.com/pinellolab/DNA-Diffusion/blob/6530de4ae4e0ff95f6e0852cd0d77ee763fb8833/pyproject.toml#L1-L3)\\]</sup>\n- [Let's Encrypt](https://letsencrypt.org) <sup>\\[[1](https://github.com/letsencrypt/mariadb-sequential-partition-manager-py/blob/666de864bcd3e17001513cd14f8919b01be7dd58/pyproject.toml#L1-L3)\\]</sup>\n- [Max Planck Society](https://www.mpg.de/en) <sup>\\[[1](https://github.com/center-for-humans-and-machines/transformer-heads/blob/0a362a6654a9a0e357d759700c08991017b39fec/pyproject.toml#L1-L3)\\]</sup>\n- [OpenTelemetry](https://opentelemetry.io) <sup>\\[[1](https://github.com/open-telemetry/opentelemetry-python/issues/2884#issuecomment-1229539511)|[2](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1259#issuecomment-1235028860)\\]</sup>\n- [Smithsonian Institution](https://www.si.edu) <sup>\\[[1](https://github.com/Smithsonian/ngehtutil/blob/02921f3a2ce11eb3f1555a0b9d3b177592d2be37/pyproject.toml#L1-L3)\\]</sup>\n- [The New York Public Library](https://www.nypl.org) <sup>\\[[1](https://github.com/NYPL/python-utils/blob/79b6d1b98d35b318af23c2af2f4f25e2c8162b15/pyproject.toml#L1-L3)\\]</sup>\n\n## Government\n\n- [European Molecular Biology Laboratory](https://www.embl.org)\n    - [European Bioinformatics Institute](https://www.ebi.ac.uk) <sup>\\[[1](https://github.com/MarioniLab/oor_benchmark/blob/9117c354bb780b3cb5a73a30e68aa26fc68efdb5/pyproject.toml#L1-L3)\\]</sup>\n- [Germany](https://en.wikipedia.org/wiki/Germany)\n    - [Berlin Institute of Health](https://www.bihealth.org/en/) <sup>\\[[1](https://github.com/BIH-CEI/napkon-string-matching/blob/48d0d0ade9f1f173df9a2881a71412bbe73a006b/pyproject.toml#L25-L27)\\]</sup>\n    - [Helmholtz Munich](https://www.helmholtz-munich.de/en) <sup>\\[[1](https://github.com/theislab/moscot/blob/545d8ac7c6a648931699cddaa757ea47b63d9b5e/pyproject.toml#L1-L3)|[2](https://github.com/theislab/multigrate/blob/1974d5901d2894573acd823c3d4d3c4ba23aba7a/pyproject.toml#L1-L3)\\]</sup>\n- [Norway](https://en.wikipedia.org/wiki/Norway)\n    - [Statistics Norway](https://www.ssb.no/en/) <sup>\\[[1](https://github.com/statisticsnorway/dapla-hurtigstart-jupyter-extension/blob/96ac7441c46ed92684a8850df5cc72be15446289/pyproject.toml#L1-L3)\\]</sup>\n- [United Kingdom](https://en.wikipedia.org/wiki/United_Kingdom)\n    - [The Alan Turing Institute](https://www.turing.ac.uk) <sup>\\[[1](https://github.com/alan-turing-institute/bureau/blob/6ed1882eaeb2410814549c4ffc2c1860c1acf7ca/build/pyproject.toml#L1-L3)\\]</sup>\n    - [Department for Business and Trade](https://www.gov.uk/government/organisations/department-for-business-and-trade) <sup>\\[[1](https://github.com/uktrade/mirror-git-to-s3/blob/ce38c7c689f5dba1f3c9de4e10b8889afc8e44b7/pyproject.toml#L1-L3)\\]</sup>\n    - [The National Archives](https://www.nationalarchives.gov.uk) <sup>\\[[1](https://github.com/nationalarchives/da-ayr-webapp/blob/1e62d38c0fe14f7d391835c704ba715241affcdb/pyproject.toml#L1-L3)\\]</sup>\n- [United States](https://en.wikipedia.org/wiki/United_States)\n    - [NASA](https://www.nasa.gov) <sup>\\[[1](https://github.com/spacetelescope/hstaxe/blob/c6a73c8211c3eac71f0aa6eb4125f5be227ae7c4/pyproject.toml#L1-L3)\\]</sup>\n    - [National Institute of Standards and Technology](https://www.nist.gov) <sup>\\[[1](https://github.com/usnistgov/thermoextrap/blob/536bb94b5c08814171dccfe9569d16854a5404bc/pyproject.toml#L1-L7)|[2](https://github.com/usnistgov/labbench/blob/fc5762fc155b8eb30ba32b487b5244ed6ff78739/pyproject.toml#L99-L101)|[3](https://github.com/NERSC/sfapi_client/blob/685c4988501cd10ec3cb495368e2839d3b648124/pyproject.toml#L1-L3)|[4](https://github.com/usnistgov/cmomy/blob/1689a97c65d00fc6d48221e128b789839c56b034/pyproject.toml#L1-L7)|[5](https://github.com/usnistgov/tmmc-lnpy/blob/ee6e16e21aef5824352f214042ed52ba252bd588/pyproject.toml#L1-L7)\\]</sup>\n    - [National Security Agency](https://www.nsa.gov) <sup>\\[[1](https://github.com/NationalSecurityAgency/ghidra/blob/6242fda158fed6c7dbbd6928a4a74371a212c373/Ghidra/Debug/Debugger-agent-lldb/src/main/py/pyproject.toml#L1-L3)|[2](https://github.com/NationalSecurityAgency/ghidra/blob/6242fda158fed6c7dbbd6928a4a74371a212c373/Ghidra/Debug/Debugger-agent-gdb/src/main/py/pyproject.toml#L1-L3)\\]</sup>\n    - [National Telecommunications and Information Administration](https://www.ntia.gov) <sup>\\[[1](https://github.com/NTIA/scos-tekrsa/blob/73090a737fdc0bd3a6c7c08deb170e00018d9ceb/pyproject.toml#L1-L3)|[2](https://github.com/NTIA/scos-actions/blob/a388aa46d414c7b5e67f76f8982bff2f534014f7/pyproject.toml#L1-L3)|[3](https://github.com/NTIA/tekrsa-api-wrap/blob/edce621075f053809c1640c6197c46bbc6456a10/pyproject.toml#L1-L3)|[4](https://github.com/NTIA/Preselector/pull/10)\\]</sup>\n\n## Academia\n\n- [Brown University](https://www.brown.edu)\n    - [Carney Institute for Brain Science](https://www.brown.edu/carney/) <sup>\\[[1](https://github.com/AutoResearch/sourpea/blob/f3007a58d3e5a647ccfb37fee24e44468d5ec707/pyproject.toml#L1-L3)\\]</sup>\n- [Carnegie Mellon University](https://www.cmu.edu)\n    - [Department of Chemical Engineering](https://www.cheme.engineering.cmu.edu/) <sup>\\[[1](https://github.com/FAIR-Chem/fairchem/blob/e344dc83f9e295c4be3830118302daf96e8a9b78/packages/fairchem-core/pyproject.toml#L1-L3)|[2](https://github.com/FAIR-Chem/fairchem/blob/e344dc83f9e295c4be3830118302daf96e8a9b78/packages/fairchem-data-oc/pyproject.toml#L1-L3)|[3](https://github.com/FAIR-Chem/fairchem/blob/e344dc83f9e295c4be3830118302daf96e8a9b78/packages/fairchem-data-om/pyproject.toml#L1-L3)|[4](https://github.com/FAIR-Chem/fairchem/blob/e344dc83f9e295c4be3830118302daf96e8a9b78/packages/fairchem-demo-ocpapi/pyproject.toml#L1-L3)|[5](https://github.com/FAIR-Chem/fairchem/blob/e344dc83f9e295c4be3830118302daf96e8a9b78/packages/fairchem-applications-AdsorbML/pyproject.toml#L1-L3)|[6](https://github.com/FAIR-Chem/fairchem/blob/e344dc83f9e295c4be3830118302daf96e8a9b78/packages/fairchem-applications-cattsunami/pyproject.toml#L1-L3)\\]</sup>\n- [Chinese Academy of Sciences](https://english.cas.cn)\n    - [Academy of Mathematics and Systems Science](http://english.amss.cas.cn) <sup>\\[[1](https://github.com/zhanglabtools/ConsTADs/blob/db732cf820569564f933cd290736ad83b9c99dea/pyproject.toml#L1-L3)\\]</sup>\n- [Georgia Institute of Technology](https://www.gatech.edu)\n    - [Georgia Tech Database Group](https://db.cc.gatech.edu) <sup>\\[[1](https://github.com/georgia-tech-db/sqlfuzz/blob/e85895dae1c92a223cbc13b12d4a19f297c410ab/pyproject.toml#L1-L3)\\]</sup>\n- [Harvard University](https://www.harvard.edu)\n    - [Department of Molecular and Cellular Biology](https://www.mcb.harvard.edu) <sup>\\[[1](https://github.com/Hekstra-Lab/raman-analysis/blob/4b548b5ea935e52a7bd1f0ec8f4a00c822b81ede/pyproject.toml#L2-L4)\\]</sup>\n- [Heidelberg University](https://www.uni-heidelberg.de)\n    - [Center for Molecular Biology](https://www.zmbh.uni-heidelberg.de) <sup>\\[[1](https://github.com/anders-biostat/pymetdense/blob/a1d210f2c03d2919b549f2fed1e4db986d01c8d5/pyproject.toml#L1-L3)\\]</sup>\n- [Leiden University](https://www.universiteitleiden.nl/en)\n    - [Leiden University Libraries](https://www.library.universiteitleiden.nl) <sup>\\[[1](https://github.com/LeidenUniversityLibrary/maps-tools/blob/d7a9fc683be919d4f5538f6a6c80319558064968/pyproject.toml#L3-L5)|[2](https://github.com/LeidenUniversityLibrary/archminer/blob/61465dc36924ffe593653aa5888a27617c93860e/pyproject.toml#L1-L3)\\]</sup>\n- [Maastricht University](https://www.maastrichtuniversity.nl)\n    - [Institute of Data Science](https://www.maastrichtuniversity.nl/research/institute-data-science) <sup>\\[[1](https://github.com/MaastrichtU-IDS/fair-test/blob/9c88c18cb1b0fa8d37336cdd2b7b132cb979a83a/pyproject.toml#L95-L97)|[2](https://github.com/MaastrichtU-IDS/fair-enough-metrics/blob/dad29ef1f99f5e01a76799d909e538565ae2ed4e/pyproject.toml#L50-L52)|[3](https://github.com/MaastrichtU-IDS/cookiecutter-python-package/blob/1eda79b6ca64c27b4b12407464b3c2dc2511af94/%7B%7Bcookiecutter.package_name%7D%7D/pyproject.toml#L70-L72)|[4](https://github.com/MaastrichtU-IDS/translator-openpredict/blob/b6e0f5f5100129d3038618f86e4c2c05d62d51f4/pyproject.toml#L1-L3)|[5](https://github.com/MaastrichtU-IDS/cookiecutter-trapi-predict-kit/blob/a329c6d66c1b96b53e9fd02501c762aee32a69fb/%7B%7Bcookiecutter.package_name%7D%7D/pyproject.toml#L1-L3)|[6](https://github.com/MaastrichtU-IDS/sparql-profiler/blob/ac70a9e8575f9c9769eb1caf140e2f81b136835c/pyproject.toml#L1-L3)|[7](https://github.com/MaastrichtU-IDS/knowledge-collaboratory/blob/8263d69e7b8e485b0aff7e88a3a7aed3cceaa253/backend/pyproject.toml#L1-L3)|[8](https://github.com/MaastrichtU-IDS/LUCE/blob/94c9a0dda840a3d81828a89aefcfb19fee51cd60/pyproject.toml#L1-L3)\\]</sup>\n- [Massachusetts Institute of Technology](https://www.mit.edu)\n    - [Computer Science and Artificial Intelligence Laboratory](https://www.csail.mit.edu) <sup>\\[[1](https://github.com/Learning-and-Intelligent-Systems/lisdf/blob/d49a85a3924909f1d10fef40463757b141f47f90/pyproject.toml#L1-L3)\\]</sup>\n    - [Digital Humanities](https://digitalhumanities.mit.edu) <sup>\\[[1](https://github.com/cuthbertLab/music21/blob/5417b3ce6415ab016a39564e21e29799387263e9/pyproject.toml#L1-L5)\\]</sup>\n- [Medical University of Innsbruck](https://www.i-med.ac.at/mypoint/index.xml.en)\n    - [Institute of Bioinformatics](https://icbi.i-med.ac.at) <sup>\\[[1](https://github.com/icbi-lab/infercnvpy/blob/12c103f4062860d5d91152222163eb7d22340146/pyproject.toml#L1-L3)\\]</sup>\n- [Polytechnique Montréal](https://www.polymtl.ca/en/)\n    - [Department of Computer Engineering and Software Engineering](https://www.polymtl.ca/gigl/) <sup>\\[[1](https://github.com/corail-research/seahorse/blob/e876042f92c704180c16055a6720ef828c21e0ae/pyproject.toml#L1-L3)\\]</sup>\n- [Siberian Branch of the Russian Academy of Sciences](https://www.sbras.ru/en/)\n    - [Institute of Cytology and Genetics](https://www.icgbio.ru/en/) <sup>\\[[1](https://github.com/genomech/FastContext/blob/f8ff7f4bbea9d6d3cdf2e3a361f72e9283b04f67/pyproject.toml#L1-L3)|[2](https://github.com/genomech/exoclasma-index/blob/2e0555c3e86d731f3aa8c978b23b586d3a0c492e/pyproject.toml#L1-L3)|[3](https://github.com/genomech/exoclasma-fastq/blob/80ea3eddf603d2b54bb02b5ada6d275a9436f287/pyproject.toml#L1-L3)|[4](https://github.com/genomech/exoclasma-pipe/blob/fbe365dd9301eec51879ef53b1704be66813bb8b/pyproject.toml#L1-L3)\\]</sup>\n- [Stanford University](https://www.stanford.edu)\n    - [Empirical Security Research Group](https://esrg.stanford.edu/) <sup>\\[[1](https://github.com/stanford-esrg/gps/blob/66f803bfd4726cd9d1b3e1724abfd34a36079530/pyproject.toml#L1-L3)\\]</sup>\n- [University of British Columbia](https://www.ubc.ca)\n    - [Department of Earth, Ocean and Atmospheric Sciences](https://www.eoas.ubc.ca) <sup>\\[[1](https://github.com/UBC-MOAD/cookiecutter-MOAD-pypkg/blob/75441f962a6e7b87c09bcae031fdfaec3cf75f74/%7B%7Bcookiecutter.package_name%7D%7D/pyproject.toml#L18-L20)|[2](https://github.com/SalishSeaCast/NEMO-Cmd/blob/be5425d49eaf845eaba8f1611455f2de75aa194b/pyproject.toml#L19-L21)|[3](https://github.com/SalishSeaCast/SalishSeaNowcast/blob/1a850c1368b7f3504e5804101647ab481fbe7048/pyproject.toml#L19-L21)\\]</sup>\n- [University of California, Berkeley](https://www.berkeley.edu)\n    - [Center for Computational Biology](https://ccb.berkeley.edu) <sup>\\[[1](https://github.com/YosefLab/scib-metrics/blob/4dcbf55d80e21cf141332ba718fc5c0eb012eac1/pyproject.toml#L1-L3)\\]</sup>\n- [University of California, Santa Barbara](https://www.ucsb.edu)\n    - [Department of Computer Science](https://www.cs.ucsb.edu) <sup>\\[[1](https://github.com/UCSBarchlab/PyRTL/blob/46b0f3d2ff0e334d9cf3a04ef5b090bd55fcc177/pyproject.toml#L10-L12)\\]</sup>\n- [University of Freiburg](https://uni-freiburg.de)\n    - [Freiburg Center for Data Analysis and Modeling](https://www.fdm.uni-freiburg.de) <sup>\\[[1](https://github.com/Spatial-Systems-Biology-Freiburg/FisInMa/blob/b9c5a980ae03d6f577e17242e6bce7822f665f94/pyproject.toml#L1-L3)\\]</sup>\n- [University of Illinois Urbana-Champaign](https://illinois.edu)\n    - [Grainger College of Engineering](https://grainger.illinois.edu) <sup>\\[[1](https://github.com/SPI2Py/SPI2Py/blob/feefd7bb003b42f4790982d68e7e4e5fdb6ca8ad/pyproject.toml#L1-L3)\\]</sup>\n- [University of Lausanne](https://www.unil.ch/central/en/home.html)\n    - [Department of Computational Biology](https://www.unil.ch/dbc/en/home.html) <sup>\\[[1](https://github.com/CSOgroup/cellcharter/blob/00b4cd44f13702bd8832ed6705614efda048b7b7/pyproject.toml#L1-L3)\\]</sup>\n- [University of Ljubljana](https://www.uni-lj.si/eng/)\n    - [Faculty of Mechanical Engineering](https://www.uni-lj.si/academies_and_faculties/faculties/2013071111460582/) <sup>\\[[1](https://github.com/ladisk/speckle_pattern/blob/055f45b66c7985564a9fa400d8d2f41ddd181d31/pyproject.toml#L1-L3)\\]</sup>\n- [University of Massachusetts Amherst](https://www.umass.edu)\n    - [College of Information and Computer Sciences](https://www.cics.umass.edu) <sup>\\[[1](https://github.com/plasma-umass/ChatDBG/blob/1bc32332464afe2f3932b0a9f586a88c8fb7a357/pyproject.toml#L1-L3)\\]</sup>\n- [University of Oxford](https://www.ox.ac.uk)\n    - [Oxford Research Software Engineering](https://www.rse.ox.ac.uk) <sup>\\[[1](https://github.com/OxfordRSE/oxrse_unit_conv/blob/e4cb7d15bbc8ba4ab7ff816d3bbdfb65fbda3f76/pyproject.toml#L21-L23)\\]</sup>\n- [University of Pennsylvania](https://www.upenn.edu)\n    - [Lifespan Informatics and Neuroimaging Center](https://www.pennlinc.io) <sup>\\[[1](https://github.com/PennLINC/qsiprep/blob/f0d661589cc2efd9a787b2c1b3db397a897daa98/pyproject.toml#L1-L3)|[2](https://github.com/PennLINC/xcp_d/blob/e68c802604ac9ca2c179ca2f164ceb4db7c1fe66/pyproject.toml#L1-L3)|[3](https://github.com/PennLINC/aslprep/blob/aeee1a22fce8f8f1bd922de6d822124fb7b3343f/pyproject.toml#L1-L3)|[4](https://github.com/PennLINC/CuBIDS/blob/fac73803b7c6d6ab938af142783c8159a6df6c60/pyproject.toml#L1-L3)\\]</sup>\n- [University of Regensburg](https://www.uni-regensburg.de/en)\n    - [Spang Lab](https://www.spang-lab.de) <sup>\\[[1](https://github.com/spang-lab/adadmire/blob/14f169a4d493952433224e518c4ed2484d6cc2bd/pyproject.toml#L1-L3)\\]</sup>\n- [University of Sussex](https://www.sussex.ac.uk)\n    - [Predictive Analytics Lab](https://wearepal.ai) <sup>\\[[1](https://github.com/wearepal/teext/blob/9253c9412b4ca340c42c0b9de0e8ac8f5ccdd0e3/pyproject.toml#L1-L3)\\]</sup>\n- [University of Toronto Scarborough](https://www.utsc.utoronto.ca/home/)\n    - [utsc-networking](https://github.com/utsc-networking) <sup>\\[[1](https://github.com/utsc-networking/utsc-tools/blob/02a79d48d133470a4394fced138b40c660cf111c/projects/core/pyproject.toml#L1-L3)|[2](https://github.com/utsc-networking/utsc-tools/blob/02a79d48d133470a4394fced138b40c660cf111c/projects/nautobot/pyproject.toml#L1-L3)|[3](https://github.com/utsc-networking/utsc-tools/blob/02a79d48d133470a4394fced138b40c660cf111c/projects/switchconfig/pyproject.toml#L1-L3)|[4](https://github.com/utsc-networking/utsc-tools/blob/02a79d48d133470a4394fced138b40c660cf111c/projects/scripts/pyproject.toml#L1-L3)\\]</sup>\n- [University of Washington](https://www.washington.edu)\n    - [Interactive Data Lab](https://idl.cs.washington.edu) <sup>\\[[1](https://github.com/uwdata/mosaic/blob/a3b78fef28fcc3e711bb922c97c3113aa6cf9122/packages/widget/pyproject.toml#L1-L3)\\]</sup>\n    - [Virtual Brain Lab](https://github.com/VirtualBrainLab) <sup>\\[[1](https://github.com/VirtualBrainLab/ephys-link/blob/ebdf3a1488f1010faa19f22397f10d6be4d29d6f/pyproject.toml#L1-L3)\\]</sup>\n- [University of Wisconsin-Madison](https://www.wisc.edu)\n    - [Data Science Institute](https://datascience.wisc.edu/institute/) <sup>\\[[1](https://github.com/UW-Madison-DSI/ask-xDD/blob/ae62d038303927b69dba9dadfef94b55b55731b3/pyproject.toml#L20-L22)\\]</sup>\n- [Waseda University](https://www.waseda.jp/top/en/)\n    - [Tackeuchi Laboratory](https://www.f.waseda.jp/atacke/) <sup>\\[[1](https://github.com/wasedatakeuchilab/python-project-template-hatch/blob/58949ab351d81b67f14aa45abf7c70b87394e2dc/pyproject.toml#L1-L3)|[2](https://github.com/wasedatakeuchilab/webapp-photo-luminescence/blob/864d1019650a2b057f761aa91ed9a6cbe6c1b455/pyproject.toml#L1-L3)|[3](https://github.com/wasedatakeuchilab/tlab-analysis/blob/72f0a710e35613e8996f473a80e5cb6c3f8c523e/pyproject.toml#L1-L3)|[4](https://github.com/wasedatakeuchilab/tlab-pptx/blob/ef331176906447dbbcf33e46f060b60ac3c007c5/pyproject.toml#L1-L3)|[5](https://github.com/wasedatakeuchilab/tlab-google/blob/53ae597611a146c90116b3b9277430832e1d04c9/pyproject.toml#L1-L3)\\]</sup>\n- [Wellcome Sanger Institute](https://www.sanger.ac.uk) <sup>\\[[1](https://github.com/sanger/lab-share-lib/blob/b3290b1922aabc29ac256dc034b8cfcc7b30f143/pyproject.toml#L25-L27)\\]</sup>\n\n## Research\n\n- [Clariah](https://www.clariah.nl) <sup>\\[[1](https://github.com/CLARIAH/pure3d/blob/3f93d62cb1f5223836c9ebf4c058e6f491de71b9/pyproject.toml#L1-L3)\\]</sup>\n- [CloudDrift](https://cloud-drift.github.io/clouddrift/) <sup>\\[[1](https://github.com/Cloud-Drift/clouddrift/blob/5e654569c869a027fe0a486f06917b358837d41e/pyproject.toml#L1-L3)\\]</sup>\n- [Dask](https://www.dask.org) <sup>\\[[1](https://github.com/dask/dask-ml/blob/b95ba909c6dcd37c566f5193ba0b918396edaaee/pyproject.toml#L1-L3)|[2](https://github.com/dask/dask-labextension/blob/39b69ac5b8bfdb726347aabe3da86a15cb201b77/pyproject.toml#L1-L3)\\]</sup>\n- [GAMA](https://gama-platform.org) <sup>\\[[1](https://github.com/gama-platform/Gama-client-python/blob/d9fecae0dff9050f39a011c4f4bdb02f5137b241/pyproject.toml#L1-L3)\\]</sup>\n- [IPython](https://ipython.org) <sup>\\[[1](https://github.com/ipython/ipykernel/blob/dd0a9863e07c1d49f5aaf72c0c62670acee71b55/pyproject.toml#L1-L3)|[2](https://github.com/ipython/ipyparallel/blob/06f5d3df1f6e858a83c3af29438ae6d5af801267/pyproject.toml#L1-L6)|[3](https://github.com/ipython/traitlets/blob/ac13bbb885c275fd446f85a9d2e74d8058c2b3c1/pyproject.toml#L1-L3)\\]</sup>\n- [MNE](https://mne.tools) <sup>\\[[1](https://github.com/mne-tools/mne-python/blob/8af33df490f94c3dd628cfc23beafed1a6cc6361/pyproject.toml#L1-L3)|[2](https://github.com/mne-tools/mne-bids-pipeline/blob/a6995abc39fab333ab957baa45b0026bdb12a3f9/pyproject.toml#L1-L3)|[3](https://github.com/mne-tools/mne-bids/blob/8321aef66e1c920bd4df748e326e06b0bf696e4c/pyproject.toml#L1-L3)\\]</sup>\n- [NIPY](https://nipy.org) <sup>\\[[1](https://github.com/nipy/nibabel/blob/298788070a36e8d8616df36ebed0d4339f00e43b/pyproject.toml#L1-L3)|[2](https://github.com/nipy/quickshear/blob/83b362b794d52183ff40ec5dcc98239b94c5633a/pyproject.toml#L1-L3)\\]</sup>\n- [Project Jupyter](https://jupyter.org)\n    - [Jupyter](https://github.com/jupyter) <sup>\\[[1](https://github.com/jupyter/notebook/blob/b9bab689c9a2f33eb3b2cca1383c2d99baa7a2e8/pyproject.toml#L1-L3)|[2](https://github.com/jupyter/jupyter_core/blob/2a6fb6d2b28ca712268eee15d7b907a3a73271d8/pyproject.toml#L1-L3)|[3](https://github.com/jupyter/jupyter_client/blob/e526895a29e0331a167167070b1603f20a4b2840/pyproject.toml#L1-L3)|[4](https://github.com/jupyter/nbconvert/blob/af70c9fa83bee4d0c92e06b4ede4ef5ea7c920b0/pyproject.toml#L1-L3)\\]</sup>\n    - [JupyterLab](https://github.com/jupyterlab) <sup>\\[[1](https://github.com/jupyterlab/hatch_jupyter_builder)|[2](https://github.com/jupyterlab/jupyterlab/pull/12606)|[3](https://github.com/jupyterlab/maintainer-tools/blob/0e95a837469f5325e5a840bd194fe8273087d2f6/pyproject.toml#L1-L3)|[4](https://github.com/jupyterlab/pytest-check-links/blob/b07e705d590e9fce22dc21191018f4f72ec7215b/pyproject.toml#L1-L3)|[5](https://github.com/jupyterlab/extension-cookiecutter-js/pull/41)\\]</sup>\n    - [Jupyter Server](https://github.com/jupyter-server) <sup>\\[[1](https://github.com/jupyter-server/jupyter_server/blob/061d846fbd0cf2f0be50d12c4a15feffd3214774/pyproject.toml#L1-L3)|[2](https://github.com/jupyter-server/enterprise_gateway/blob/b45a81ae70680be7f8e0d1e3daed1df3063667fa/pyproject.toml#L1-L3)|[3](https://github.com/jupyter-server/jupyter_server_terminals/blob/4b32ceb34b9b6ae9c677424cc65c9c3bfe243719/pyproject.toml#L1-L3)|[4](https://github.com/jupyter-server/synchronizer/blob/5809e9ffd188beff743874a434884662867bb573/pyproject.toml#L1-L3)\\]</sup>\n- [RAPIDS](https://rapids.ai) <sup>\\[[1](https://github.com/rapidsai/jupyterlab-nvdashboard/blob/578b58b4fd0ec31a7cc02ac6d2795622c00ef478/pyproject.toml#L3-L9)\\]\n- [Scikit-HEP](https://scikit-hep.org) <sup>\\[[1](https://github.com/scikit-hep/uproot-browser/blob/f41ce3f3887057f5ec9a6cd164c3c41d1ec3d633/pyproject.toml#L1-L3)|[2](https://github.com/scikit-hep/uhi/blob/95ad870218a6fd7f2ab02f3d2b5c421e93a1f03f/pyproject.toml#L1-L3)|[3](https://github.com/scikit-hep/repo-review/blob/007026a62c6c61914ec49e111be587104f59b8ae/pyproject.toml#L1-L3)|[4](https://github.com/scikit-hep/hist/blob/768ea7de75f20c06caa6ded72d70bd132e4c9467/pyproject.toml#L1-L3)|[5](https://github.com/scikit-hep/vector/blob/cac88a2e0f1c4bf7bceaafbea6e234b3147e3ca3/pyproject.toml#L1-L6)|[6](https://github.com/scikit-hep/uproot5/blob/f9213e0f8c29435890e5aa72e336330bb7a785fe/pyproject.toml#L1-L5)|[7](https://github.com/scikit-hep/particle/blob/723c1618c7058feb0a914a6738d8b8018a5df1bd/pyproject.toml#L1-L3)|[8](https://github.com/scikit-hep/hepunits/blob/bd1302cbb85ed486c057f8b078ad4e026d65bb1c/pyproject.toml#L1-L3)|[9](https://github.com/scikit-hep/decaylanguage/blob/eae09aee69acef2d1c19f55665c5ca8b28588e01/pyproject.toml#L1-L6)|[10](https://github.com/scikit-hep/pyhf/blob/efbf201b57345063afec66c254aace3148f1f055/pyproject.toml#L1-L3)\\]</sup>\n- [scverse](https://scverse.org) <sup>\\[[1](https://github.com/scverse/spatialdata-io/blob/15c395de859d6d06e5032016c9406acae5cac454/pyproject.toml#L1-L3)|[2](https://github.com/scverse/spatialdata-notebooks/blob/2b539a1d23b06b509a46a6bf3cb6594f1952f830/pyproject.toml#L1-L3)|[3](https://github.com/scverse/cookiecutter-scverse/blob/2892e1ddf0dd558cb9b547b47a7c2d0a156c9ef1/%7B%7Bcookiecutter.project_name%7D%7D/pyproject.toml#L1-L3)\\]</sup>\n- [Spyder](https://www.spyder-ide.org) <sup>\\[[1](https://github.com/spyder-ide/envs-manager/blob/9c487532cbb4804c94d7cf23dcec9404b2a1c7ec/pyproject.toml#L1-L3)\\]</sup>\n\n## Security\n\n- [Armory](https://github.com/twosixlabs/armory/blob/330caa23d54ce82886606810f103ce1a0eec98ce/pyproject.toml#L129-L134)\n- [in-toto](https://github.com/in-toto/in-toto/blob/2768904b8a3892529aba8f8a605461fd178d9a58/pyproject.toml#L1-L3)\n- [The Update Framework](https://github.com/theupdateframework/python-tuf/blob/72424a958b60817155fcacfed1216163790b26f7/pyproject.toml#L2-L4)\n\n## Crypto\n\n- [Ocean Protocol](https://oceanprotocol.com) <sup>\\[[1](https://github.com/oceanprotocol/pybundlr/blob/484c755d96be2da35cda83f01861745867cdb2d4/pyproject.toml#L1-L6)\\]</sup>\n"
  },
  {
    "path": "docs/config/build.md",
    "content": "# Build configuration\n\n-----\n\n[Build targets](#build-targets) are defined as sections within `tool.hatch.build.targets`:\n\n```toml config-example\n[tool.hatch.build.targets.<TARGET_NAME>]\n```\n\n!!! tip\n    Although not recommended, you may define global configuration in the `tool.hatch.build` table. Keys may then be overridden by target config.\n\n## Build system\n\nTo be compatible with the broader [Python packaging ecosystem](../build.md#packaging-ecosystem), you must define the [build system](https://peps.python.org/pep-0517/#source-trees) as follows:\n\n```toml tab=\"pyproject.toml\"\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n```\n\nThe version of `hatchling` defined here will be used to build all targets.\n\nHatchling is a standards-compliant[^1] build backend and is a dependency of Hatch itself.\n\n## File selection\n\n### VCS\n\nBy default, Hatch will respect the first `.gitignore` or `.hgignore` file found in your project's root directory or parent directories. Set `ignore-vcs` to `true` to disable this behavior:\n\n```toml config-example\n[tool.hatch.build.targets.sdist]\nignore-vcs = true\n```\n\n!!! note\n    For `.hgignore` files only glob syntax is supported.\n\n### Patterns\n\nYou can set the `include` and `exclude` options to select exactly which files will be shipped in each build, with `exclude` taking precedence. Every entry represents a [Git-style glob pattern](https://git-scm.com/docs/gitignore#_pattern_format).\n\nFor example, the following configuration:\n\n```toml config-example\n[tool.hatch.build.targets.sdist]\ninclude = [\n  \"pkg/*.py\",\n  \"/tests\",\n]\nexclude = [\n  \"*.json\",\n  \"pkg/_compat.py\",\n]\n```\n\nwill exclude every file with a `.json` extension, and will include everything under a `tests` directory located at the root and every file with a `.py` extension that is directly under a `pkg` directory located at the root except for `_compat.py`.\n\n### Artifacts\n\nIf you want to include files that are [ignored by your VCS](#vcs), such as those that might be created by [build hooks](#build-hooks), you can use the `artifacts` option. This option is semantically equivalent to `include`.\n\nNote that artifacts are not affected by the `exclude` option. Artifacts can\nbe excluded by using more explicit paths or by using the `!` negation operator.\nWhen using the `!` operator, the negated pattern(s) must come after the more\ngeneric ones.\n\n```toml config-example\n[tool.hatch.build.targets.wheel]\nartifacts = [\n  \"*.so\",\n  \"*.dll\",\n  \"!/foo/*.so\",\n]\n```\n\n### Explicit selection\n\n#### Generic\n\nYou can use the `only-include` option to prevent directory traversal starting at the project root and only select specific relative paths to directories or files. Using this option ignores any defined [`include` patterns](#patterns).\n\n```toml config-example\n[tool.hatch.build.targets.sdist]\nonly-include = [\"pkg\", \"tests/unit\"]\n```\n\n#### Packages\n\nThe `packages` option is semantically equivalent to `only-include` (which takes precedence) except that the shipped path will be collapsed to only include the final component.\n\nSo for example, if you want to ship a package `foo` that is stored in a directory `src` you would do:\n\n```toml config-example\n[tool.hatch.build.targets.wheel]\npackages = [\"src/foo\"]\n```\n\n### Forced inclusion\n\nThe `force-include` option allows you to select specific files or directories from anywhere on the file system that should be included and map them to the desired relative distribution path.\n\nFor example, if there was a directory alongside the project root named `artifacts` containing a file named `lib.so` and a file named `lib.h` in your home directory, you could ship both files in a `pkg` directory with the following configuration:\n\n```toml config-example\n[tool.hatch.build.targets.wheel.force-include]\n\"../artifacts\" = \"pkg\"\n\"~/lib.h\" = \"pkg/lib.h\"\n```\n\n!!! note\n    - Files must be mapped exactly to their desired paths, not to directories.\n    - The contents of directory sources are recursively included.\n    - To map directory contents directly to the root use `/` (a forward slash).\n    - Sources that do not exist will raise an error.\n\n!!! warning\n    Files included using this option will overwrite any file path that was already included by other file selection options.\n\n### Default file selection\n\nIf no file selection options are provided, then what gets included is determined by each [build target](#build-targets).\n\n### Excluding files outside packages\n\nIf you want to exclude non-[artifact](#artifacts) files that do not reside within a Python package, set `only-packages` to `true`:\n\n```toml config-example\n[tool.hatch.build.targets.wheel]\nonly-packages = true\n```\n\n### Rewriting paths\n\nYou can rewrite relative paths to directories with the `sources` option. For example, the following configuration:\n\n```toml config-example\n[tool.hatch.build.targets.wheel.sources]\n\"src/foo\" = \"bar\"\n```\n\nwould distribute the file `src/foo/file.ext` as `bar/file.ext`.\n\nIf you want to remove path prefixes entirely, rather than setting each to an empty string, you can define `sources` as an array:\n\n```toml config-example\n[tool.hatch.build.targets.wheel]\nsources = [\"src\"]\n```\n\nIf you want to add a prefix to paths, you can use an empty string. For example, the following configuration:\n\n```toml config-example\n[tool.hatch.build.targets.wheel.sources]\n\"\" = \"foo\"\n```\n\nwould distribute the file `bar/file.ext` as `foo/bar/file.ext`.\n\nThe [packages](#packages) option itself relies on sources. Defining `#!toml packages = [\"src/foo\"]` for the `wheel` target is equivalent to the following:\n\n```toml config-example\n[tool.hatch.build.targets.wheel]\nonly-include = [\"src/foo\"]\nsources = [\"src\"]\n```\n\n### Performance\n\nAll encountered directories are traversed by default. To skip non-[artifact](#artifacts) directories that are excluded, set `skip-excluded-dirs` to `true`:\n\n```toml config-example\n[tool.hatch.build]\nskip-excluded-dirs = true\n```\n\n!!! warning\n    This may result in not shipping desired files. For example, if you want to include the file `a/b/c.txt` but your [VCS ignores](#vcs) `a/b`, the file `c.txt` will not be seen because its parent directory will not be entered. In such cases you can use the [`force-include`](#forced-inclusion) option.\n\n## Reproducible builds\n\nBy default, [build targets](#build-targets) will build in a reproducible manner provided that they support that behavior. To disable this, set `reproducible` to `false`:\n\n```toml config-example\n[tool.hatch.build]\nreproducible = false\n```\n\nWhen enabled, the [SOURCE_DATE_EPOCH](https://reproducible-builds.org/specs/source-date-epoch/) environment variable will be used for all build timestamps. If not set, then Hatch will use an [unchanging default value](../plugins/utilities.md#hatchling.builders.utils.get_reproducible_timestamp).\n\n## Output directory\n\nWhen the output directory is not provided to the [`build`](../cli/reference.md#hatch-build) command, the `dist` directory will be used by default. You can change the default to a different directory using a relative or absolute path like so:\n\n```toml config-example\n[tool.hatch.build]\ndirectory = \"<PATH>\"\n```\n\n## Dev mode\n\nBy default for [dev mode](environment/overview.md#dev-mode) environment installations or [editable installs](https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs), the `wheel` target will determine which directories should be added to Python's search path based on the [selected files](#file-selection).\n\nIf you want to override this detection or perhaps instruct other build targets as well, you can use the `dev-mode-dirs` option:\n\n```toml config-example\n[tool.hatch.build]\ndev-mode-dirs = [\".\"]\n```\n\nIf you don't want to add entire directories to Python's search path, you can enable a more targeted mechanism with the mutually exclusive `dev-mode-exact` option:\n\n```toml config-example\n[tool.hatch.build]\ndev-mode-exact = true\n```\n\n!!! warning\n    The `dev-mode-exact` mechanism is [not supported](https://github.com/microsoft/pylance-release/issues/2114) by static analysis tools & IDEs, therefore functionality such as autocompletion is unlikely to work.\n\n## Build targets\n\nA build target can be provided by any [builder plugin](../plugins/builder/reference.md). There are three built-in build targets: [wheel](../plugins/builder/wheel.md), [sdist](../plugins/builder/sdist.md), and [custom](../plugins/builder/custom.md).\n\n### Dependencies ### {: #target-dependencies }\n\nYou can specify additional dependencies that will be installed in each build environment, such as for third party builders:\n\n```toml config-example\n[tool.hatch.build.targets.your-target-name]\ndependencies = [\n  \"your-builder-plugin\"\n]\n```\n\nYou can also declare dependence on the project's [runtime dependencies](metadata.md#required) with the `require-runtime-dependencies` option:\n\n```toml config-example\n[tool.hatch.build.targets.your-target-name]\nrequire-runtime-dependencies = true\n```\n\nAdditionally, you may declare dependence on specific [runtime features](metadata.md#optional) of the project with the `require-runtime-features` option:\n\n```toml config-example\n[tool.hatch.build.targets.your-target-name]\nrequire-runtime-features = [\n  \"feature1\",\n  \"feature2\",\n]\n```\n\n### Versions\n\nIf a build target supports multiple build strategies or if there are major changes over time, you can specify exactly which versions you want to build using the `versions` option:\n\n```toml config-example\n[tool.hatch.build.targets.<TARGET_NAME>]\nversions = [\n  \"v1\",\n  \"beta-feature\",\n]\n```\n\nSee the [wheel](../plugins/builder/wheel.md#versions) target for a real world example.\n\n## Build hooks\n\nA build hook defines code that will be executed at various stages of the build process and can be provided by any [build hook plugin](../plugins/build-hook/reference.md). There is one built-in build hook: [custom](../plugins/build-hook/custom.md).\n\nBuild hooks can be applied either globally:\n\n```toml config-example\n[tool.hatch.build.hooks.<HOOK_NAME>]\n```\n\nor to specific build targets:\n\n```toml config-example\n[tool.hatch.build.targets.<TARGET_NAME>.hooks.<HOOK_NAME>]\n```\n\n### Dependencies ### {: #hook-dependencies }\n\nYou can specify additional dependencies that will be installed in each build environment, such as for third party build hooks:\n\n```toml config-example\n[tool.hatch.build.hooks.your-hook-name]\ndependencies = [\n  \"your-build-hook-plugin\"\n]\n```\n\nYou can also declare dependence on the project's [runtime dependencies](metadata.md#required) with the `require-runtime-dependencies` option:\n\n```toml config-example\n[tool.hatch.build.hooks.your-hook-name]\nrequire-runtime-dependencies = true\n```\n\nAdditionally, you may declare dependence on specific [runtime features](metadata.md#optional) of the project with the `require-runtime-features` option:\n\n```toml config-example\n[tool.hatch.build.hooks.your-hook-name]\nrequire-runtime-features = [\n  \"feature1\",\n  \"feature2\",\n]\n```\n\n### Order of execution\n\nFor each build target, build hooks execute in the order in which they are defined, starting with global hooks.\n\nAs an example, for the following configuration:\n\n```toml config-example\n[tool.hatch.build.targets.foo.hooks.hook2]\n\n[tool.hatch.build.hooks.hook3]\n[tool.hatch.build.hooks.hook1]\n```\n\nWhen target `foo` is built, build hook `hook3` will be executed first, followed by `hook1`, and then finally `hook2`.\n\n### Conditional execution\n\nIf you want to disable a build hook by default and control its use by [environment variables](#environment-variables), you can do so by setting the `enable-by-default` option to `false`:\n\n```toml config-example\n[tool.hatch.build.hooks.<HOOK_NAME>]\nenable-by-default = false\n```\n\n## Environment variables\n\n| Variable | Default | Description |\n| --- | --- | --- |\n| `HATCH_BUILD_CLEAN` | `false` | Whether or not existing artifacts should first be removed |\n| `HATCH_BUILD_CLEAN_HOOKS_AFTER` | `false` | Whether or not build hook artifacts should be removed after each build |\n| `HATCH_BUILD_HOOKS_ONLY` | `false` | Whether or not to only execute build hooks |\n| `HATCH_BUILD_NO_HOOKS` | `false` | Whether or not to disable all build hooks; this takes precedence over other options |\n| `HATCH_BUILD_HOOKS_ENABLE` | `false` | Whether or not to enable all build hooks |\n| `HATCH_BUILD_HOOK_ENABLE_<HOOK_NAME>` | `false` | Whether or not to enable the build hook named `<HOOK_NAME>` |\n| `HATCH_BUILD_LOCATION` | `dist` | The location with which to build the targets; only used by the [`build`](../cli/reference.md#hatch-build) command |\n\n[^1]: Support for [PEP 517][] and [PEP 660][] guarantees interoperability with other build tools.\n"
  },
  {
    "path": "docs/config/context.md",
    "content": "# Context formatting\n\n-----\n\nYou can populate configuration with the values of certain supported fields using the syntax of Python's [format strings](https://docs.python.org/3/library/string.html#formatstrings). Each field interprets the modifier part after the colon differently, if at all.\n\n## Global fields\n\nAny configuration that declares support for context formatting will always support these fields.\n\n### Paths\n\n| Field | Description |\n| --- | --- |\n| `root` | The root project directory |\n| `home` | The user's home directory |\n\nAll paths support the following modifiers:\n\n| Modifier | Description |\n| --- | --- |\n| `uri` | The normalized absolute URI path prefixed by `file:` |\n| `real` | The path with all symbolic links resolved |\n| `parent` | The parent of the preceding path |\n\n!!! tip\n    The `parent` modifier can be chained and may be combined with either the `uri` or `real` modifier, with the latter placed at the end. For example:\n\n    ```toml config-example\n    [tool.hatch.envs.test]\n    dependencies = [\n        \"example-project @ {root:parent:parent:uri}/example-project\",\n    ]\n    ```\n\n### System separators\n\n| Field | Description |\n| --- | --- |\n| `/` | `\\` on Windows, `/` otherwise |\n| `;` | `;` on Windows, `:` otherwise |\n\n### Environment variables\n\nThe `env` field and its modifier allow you to select the value of an environment variable. If the environment variable is not set, you must specify a default value as an additional modifier e.g. `{env:PATH:DEFAULT}`.\n\n## Field nesting\n\nYou can insert fields within others. For example, if you wanted a [script](environment/overview.md#scripts) that displays the value of the environment variable `FOO`, with a fallback to the environment variable `BAR`, with its own fallback to the user's home directory, you could do the following:\n\n```toml config-example\n[tool.hatch.envs.test.scripts]\ndisplay = \"echo {env:FOO:{env:BAR:{home}}}\"\n```\n"
  },
  {
    "path": "docs/config/dependency.md",
    "content": "# Dependency configuration\n\n-----\n\n[Project dependencies](metadata.md#dependencies) are defined with [PEP 508][] strings using optional [PEP 440 version specifiers][].\n\n## Version specifiers\n\nA version specifier consists of a series of version clauses, separated by commas. For example:\n\n```toml tab=\"pyproject.toml\"\n[project]\n...\ndependencies = [\n  \"cryptography\",\n  \"click>=7, <9, != 8.0.0\",\n  \"python-dateutil==2.8.*\",\n  \"numpy~=1.21.4\",\n]\n```\n\nThe comma is equivalent to a logical `AND` operator: a candidate version must match all given version clauses in order to match the specifier as a whole.\n\n### Operators\n\n| Operators | Function |\n| :---: | --- |\n| `~=` | [Compatible release](#compatible-release) |\n| `==` | [Version matching](#version-matching) |\n| `!=` | [Version exclusion](#version-exclusion) |\n| `<=`, `>=` | [Inclusive ordered comparison](#ordered-comparison) |\n| `<`, `>` | [Exclusive ordered comparison](#ordered-comparison) |\n| `===` | [Arbitrary equality](#arbitrary-equality) |\n\n### Version matching\n\nA version matching clause includes the version matching operator `==` and a version identifier.\n\nBy default, the version matching operator is based on a strict equality comparison: the specified version must be exactly the same as the requested version.\n\n| Clause | Allowed versions |\n| --- | --- |\n| `==1` | `1.0.0` |\n| `==1.2` | `1.2.0` |\n\nPrefix matching may be requested instead of strict comparison, by appending a trailing `.*` to the version identifier in the version matching clause. This means that additional trailing segments will be ignored when determining whether or not a version identifier matches the clause.\n\n| Clause | Allowed versions |\n| --- | --- |\n| `==1.*` | `>=1.0.0, <2.0.0` |\n| `==1.2.*` | `>=1.2.0, <1.3.0` |\n\n### Compatible release\n\nA compatible release clause consists of the compatible release operator `~=` and a version identifier. It matches any candidate version that is expected to be compatible with the specified version.\n\nFor a given release identifier `V.N`, the compatible release clause is approximately equivalent to the following pair of comparison clauses:\n\n```\n>= V.N, == V.*\n```\n\nThis operator cannot be used with a single segment version number such as `~=1`.\n\n| Clause | Allowed versions |\n| --- | --- |\n| `~=1.2` | `>=1.2.0, <2.0.0` |\n| `~=1.2.3` | `>=1.2.3, <1.3.0` |\n\n### Version exclusion\n\nA version exclusion clause includes the version exclusion operator `!=` and a version identifier.\n\nThe allowed version identifiers and comparison semantics are the same as those of the [Version matching](#version-matching) operator, except that the sense of any match is inverted.\n\n### Ordered comparison\n\nInclusive comparisons allow for the version identifier part of clauses whereas exclusive comparisons do not. For example, `>=1.2` allows for version `1.2.0` while `>1.2` does not.\n\nUnlike the inclusive ordered comparisons `<=` and `>=`, the exclusive ordered comparisons `<` and `>` specifically exclude pre-releases, post-releases, and local versions of the specified version.\n\n### Arbitrary equality\n\nThough heavily discouraged, arbitrary equality comparisons allow for simple string matching without any version semantics, for example `===foobar`.\n\n## Environment markers\n\n[Environment markers](https://peps.python.org/pep-0508/#environment-markers) allow for dependencies to only be installed when certain conditions are met.\n\nFor example, if you need to install the latest version of `cryptography` that is available for a given Python major version you could define the following:\n\n```\ncryptography==3.3.2; python_version < \"3\"\ncryptography>=35.0; python_version > \"3\"\n```\n\nAlternatively, if you only need it on Python 3 when running on Windows you could do:\n\n```\ncryptography; python_version ~= \"3.0\" and platform_system == \"Windows\"\n```\n\nThe available environment markers are as follows.\n\n| Marker | Python equivalent | Examples |\n| --- | --- | --- |\n| `os_name` | `#!python import os`<br>`os.name` | <ul><li>posix</li><li>java</li></ul> |\n| `sys_platform` | `#!python import sys`<br>`sys.platform` | <ul><li>linux</li><li>win32</li><li>darwin</li></ul> |\n| `platform_machine` | `#!python import platform`<br>`platform.machine()` | <ul><li>x86_64</li></ul> |\n| `platform_python_implementation` | `#!python import platform`<br>`platform.python_implementation()` | <ul><li>CPython</li><li>Jython</li></ul> |\n| `platform_release` | `#!python import platform`<br>`platform.release()` | <ul><li>1.8.0_51</li><li>3.14.1-x86_64-linode39</li></ul> |\n| `platform_system` | `#!python import platform`<br>`platform.system()` | <ul><li>Linux</li><li>Windows</li><li>Darwin</li></ul> |\n| `platform_version` | `#!python import platform`<br>`platform.version()` | <ul><li>10.0.19041</li><li>\\#1 SMP Fri Apr 2 22:23:49 UTC 2021</li></ul> |\n| `python_version` | `#!python import platform`<br>`'.'.join(platform.python_version_tuple()[:2])` | <ul><li>2.7</li><li>3.10</li></ul> |\n| `python_full_version` | `#!python import platform`<br>`platform.python_version()` | <ul><li>2.7.18</li><li>3.11.0b1</li></ul> |\n| `implementation_name` | `#!python import sys`<br>`sys.implementation.name` | <ul><li>cpython</li></ul> |\n| `implementation_version` | See [here](https://peps.python.org/pep-0508/#environment-markers) | <ul><li>2.7.18</li><li>3.11.0b1</li></ul> |\n\n## Features\n\nYou can select groups of [optional dependencies](metadata.md#optional) to install using the [extras](https://peps.python.org/pep-0508/#extras) syntax. For example, if a dependency named `foo` defined the following:\n\n```toml tab=\"pyproject.toml\"\n[project.optional-dependencies]\ncrypto = [\n  \"PyJWT\",\n  \"cryptography\",\n]\nfastjson = [\n  \"orjson\",\n]\ncli = [\n  \"prompt-toolkit\",\n  \"colorama; platform_system == 'Windows'\",\n]\n```\n\nYou can select the `cli` and `crypto` features like so:\n\n```\nfoo[cli,crypto]==1.*\n```\n\nNote that the features come immediately after the package name, before any [version specifiers](#version-specifiers).\n\n### Self-referential\n\nFeature groups can self-referentially extend others. For example, for a project called `awesome-project`, the `dev` feature group in the following `pyproject.toml` file would select everything in the `crypto` feature group, plus `black`:\n\n```toml tab=\"pyproject.toml\"\n[project]\nname = \"awesome-project\"\n\n[project.optional-dependencies]\ncrypto = [\n  \"PyJWT\",\n  \"cryptography\",\n]\ndev = [\n  \"awesome-project[crypto]\",\n  \"black\",\n]\n```\n\n## Direct references\n\nInstead of using normal [version specifiers](#version-specifiers) and fetching packages from an index like PyPI, you can define exact sources using [direct references](https://peps.python.org/pep-0440/#direct-references) with an explicit [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax).\n\nDirect references are usually not meant to be used for dependencies of a published project but rather are used for defining [dependencies for an environment](environment/overview.md#dependencies).\n\nAll direct reference types are prefixed by the package name like:\n\n```\n<NAME> @ <REFERENCE>\n```\n\n### Version control systems\n\nVarious version control systems (VCS) are [supported](#supported-vcs) as long as the associated executable is available along your `PATH`.\n\nVCS direct references are defined using one of the following formats:\n\n```\n<NAME> @ <SCHEME>://<PATH>\n<NAME> @ <SCHEME>://<PATH>@<REVISION>\n```\n\nYou may also append a `#subdirectory=<PATH>` component for specifying the relative path to the Python package when it is not located at the root e.g. `#subdirectory=lib/foo`.\n\nFor more information, refer to [this](https://pip.pypa.io/en/stable/topics/vcs-support/).\n\n#### Supported VCS\n\n=== \"Git\"\n    | Executable | Schemes | Revisions | Example |\n    | --- | --- | --- | --- |\n    | `git` | <ul><li><code>git+file</code></li><li><code>git+https</code></li><li><code>git+ssh</code></li><li><code>git+http</code> :warning:</li><li><code>git+git</code> :warning:</li><li><code>git</code> :warning:</li></ul> | <ul><li>Commit hash</li><li>Tag name</li><li>Branch name</li></ul> | `proj @ git+https://github.com/org/proj.git@v1` |\n\n=== \"Mercurial\"\n    | Executable | Schemes | Revisions | Example |\n    | --- | --- | --- | --- |\n    | `hg` | <ul><li><code>hg+file</code></li><li><code>hg+https</code></li><li><code>hg+ssh</code></li><li><code>hg+http</code> :warning:</li><li><code>hg+static-http</code> :warning:</li></ul> | <ul><li>Revision hash</li><li>Revision number</li><li>Tag name</li><li>Branch name</li></ul> | `proj @ hg+file:///path/to/proj@v1` |\n\n=== \"Subversion\"\n    | Executable | Schemes | Revisions | Example |\n    | --- | --- | --- | --- |\n    | `svn` | <ul><li><code>svn+https</code></li><li><code>svn+ssh</code></li><li><code>svn+http</code> :warning:</li><li><code>svn+svn</code> :warning:</li><li><code>svn</code> :warning:</li></ul> | <ul><li>Revision number</li></ul> | `proj @ svn+file:///path/to/proj` |\n\n=== \"Bazaar\"\n    | Executable | Schemes | Revisions | Example |\n    | --- | --- | --- | --- |\n    | `bzr` | <ul><li><code>bzr+https</code></li><li><code>bzr+ssh</code></li><li><code>bzr+sftp</code></li><li><code>bzr+lp</code></li><li><code>bzr+http</code> :warning:</li><li><code>bzr+ftp</code> :warning:</li></ul> | <ul><li>Revision number</li><li>Tag name</li></ul> | `proj @ bzr+lp:proj@v1` |\n\n### Local\n\nYou can install local packages with the `file` scheme in the following format:\n\n```\n<NAME> @ file://<HOST>/<PATH>\n```\n\nThe `<HOST>` is only used on Windows systems, where it can refer to a network share. If omitted it is assumed to be `localhost` and the third slash must still be present.\n\nThe `<PATH>` can refer to a source archive, a wheel, or a directory containing a Python package.\n\n| Type | Unix | Windows |\n| --- | --- | --- |\n| Source archive | `proj @ file:///path/to/pkg.tar.gz` | `proj @ file:///c:/path/to/pkg.tar.gz` |\n| Wheel | `proj @ file:///path/to/pkg.whl` | `proj @ file:///c:/path/to/pkg.whl` |\n| Directory | `proj @ file:///path/to/pkg` | `proj @ file:///c:/path/to/pkg` |\n\n!!! tip\n    You may also specify paths relative to your project's root directory on all platforms by using [context formatting](context.md#paths):\n\n    ```\n    <NAME> @ {root:uri}/pkg_inside_project\n    <NAME> @ {root:parent:uri}/pkg_alongside_project\n    ```\n\n### Remote\n\nYou can install source archives and wheels by simply referring to a URL:\n\n```\nblack @ https://github.com/psf/black/archive/refs/tags/21.10b0.zip\npytorch @ https://download.pytorch.org/whl/cu102/torch-1.10.0%2Bcu102-cp39-cp39-linux_x86_64.whl\n```\n\nAn expected hash value may be specified by appending a `#<HASH_ALGORITHM>=<EXPECTED_HASH>` component:\n\n```\nrequests @ https://github.com/psf/requests/archive/refs/tags/v2.26.0.zip#sha256=eb729a757f01c10546ebd179ae2aec852dd0d7f8ada2328ccf4558909d859985\n```\n\nIf the hash differs from the expected hash, the installation will fail.\n\nIt is recommended that only hashes which are unconditionally provided by the latest version of the standard library's [hashlib module](https://docs.python.org/dev/library/hashlib.html) be used for hashes. As of Python 3.10, that list consists of:\n\n- `md5`\n- `sha1`\n- `sha224`\n- `sha256`\n- `sha384`\n- `sha512`\n- `blake2b`\n- `blake2s`\n\n### Complex syntax\n\nThe following is an example that uses [features](#features) and [environment markers](#environment-markers):\n\n```\npkg[feature1,feature2] @ <REFERENCE> ; python_version < \"3.7\"\n```\n\nNote that the space before the semicolon is required.\n"
  },
  {
    "path": "docs/config/environment/advanced.md",
    "content": "# Advanced environment configuration\n\n-----\n\n## Context formatting\n\nAll environments support the following extra [context formatting](../context.md) fields:\n\n| Field | Description |\n| --- | --- |\n| `env_name` | The name of the environment |\n| `env_type` | The [type](overview.md#type) of environment |\n| `matrix` | Its modifier selects the value of that matrix variable. If the environment is not part of a matrix or was not generated with the variable, you must specify a default value as an additional modifier e.g. `{matrix:version:v1.0.0}`. |\n| `verbosity` | The integer verbosity value of Hatch. A `flag` modifier is supported that will render the value as a CLI flag e.g. `-2` becomes `-qq`, `1` becomes `-v`, and `0` becomes an empty string. An additional flag integer modifier may be used to adjust the verbosity level. For example, if you wanted to make a command quiet by default, you could use `{verbosity:flag:-1}` within the command. |\n| `args` | For [executed commands](../../environment.md#command-execution) only, any extra command line arguments with an optional default modifier if none were provided |\n\n## Matrix\n\nEnvironments can define a series of matrices with the `matrix` option:\n\n```toml config-example\n[tool.hatch.envs.test]\ndependencies = [\n  \"pytest\"\n]\n\n[[tool.hatch.envs.test.matrix]]\npython = [\"3.10\", \"3.11\"]\nversion = [\"42\", \"3.14\"]\n\n[[tool.hatch.envs.test.matrix]]\npython = [\"3.11\", \"3.12\"]\nversion = [\"9000\"]\nfeature = [\"foo\", \"bar\"]\n```\n\nDoing so will result in the product of each variable combination being its own environment.\n\n### Naming\n\nThe name of the generated environments will be the variable values of each combination separated by hyphens, altogether prefixed by `<ENV_NAME>.`. For example, the following configuration:\n\n```toml config-example\n[[tool.hatch.envs.test.matrix]]\nversion = [\"42\"]\nfeature = [\"foo\", \"bar\"]\n```\n\nwould indicate the following unique environments:\n\n```\ntest.42-foo\ntest.42-bar\n```\n\nThe exceptions to this format are described below.\n\n#### Python variables\n\nIf the variables `py` or `python` are specified, then they will rank first in the product result and will be prefixed by `py` if the value is not. For example, the following configuration:\n\n```toml config-example\n[[tool.hatch.envs.test.matrix]]\nversion = [\"42\"]\npython = [\"3.9\", \"pypy3\"]\n```\n\nwould generate the following environments:\n\n```\ntest.py3.9-42\ntest.pypy3-42\n```\n\n!!! note\n    The value of this variable sets the [Python version](overview.md#python-version).\n\n#### Name formatting\n\nYou can set the `matrix-name-format` option to modify how each variable part is formatted which recognizes the placeholders `{variable}` and `{value}`. For example, the following configuration:\n\n```toml config-example\n[tool.hatch.envs.test]\nmatrix-name-format = \"{variable}_{value}\"\n\n[[tool.hatch.envs.test.matrix]]\nversion = [\"42\"]\nfeature = [\"foo\", \"bar\"]\n```\n\nwould produce the following environments:\n\n```\ntest.version_42-feature_foo\ntest.version_42-feature_bar\n```\n\nBy default this option is set to `{value}`.\n\n#### Default environment\n\nIf the `default` environment defines matrices, then the generated names will not be prefixed by the environment name. This can be useful for projects that only need a single series of matrices without any standalone environments.\n\n### Selection\n\nRather than [selecting](../../environment.md#selection) a single generated environment, you can select the root environment to target all of them. For example, if you have the following configuration:\n\n```toml config-example\n[tool.hatch.envs.test]\ndependencies = [\n  \"coverage[toml]\",\n  \"pytest\",\n  \"pytest-cov\",\n]\n\n[tool.hatch.envs.test.scripts]\ncov = 'pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=pkg --cov=tests'\n\n[[tool.hatch.envs.test.matrix]]\npython = [\"3.11\", \"3.12\"]\nversion = [\"42\", \"3.14\"]\n```\n\nyou could then run your tests consecutively in all 4 environments with:\n\n```\nhatch run test:cov\n```\n\n## Dependency Groups\n\nEnvironments can use dependency groups[^1] using the environment `dependency-groups` array:\n\n```toml config-example\n[dependency-groups]\ntest = [\n  \"pytest>=7.0.0\",\n  \"pytest-cov>=4.1.0\",\n]\nlint = [\n  \"black\",\n  \"ruff\",\n  \"mypy\",\n]\n# Groups can include other groups\ndev = [\n  {include-group = \"test\"},\n  {include-group = \"lint\"},\n  \"pre-commit\",\n]\n\n[tool.hatch.envs.test]\ndependency-groups = [\"test\"]\n\n[tool.hatch.envs.lint]\ndependency-groups = [\"lint\"]\n\n[tool.hatch.envs.dev]\ndependency-groups = [\"dev\"]\n```\n\nThe `dependency-groups` array specifies which dependency groups to include in the environment's dependencies. This is particularly useful for organizing related dependencies and including them in appropriate environments.\n\n### Combining with Other Dependencies\n\nDependency groups can be combined with other dependency mechanisms:\n\n```toml config-example\n[project]\nname = \"my-app\"\nversion = \"0.1.0\"\ndependencies = [\n  \"requests>=2.28.0\",\n]\n\n[dependency-groups]\ntest = [\"pytest>=7.0.0\"]\ndocs = [\"sphinx>=7.0.0\"]\n\n[tool.hatch.envs.test]\n# Include the test dependency group\ndependency-groups = [\"test\"]\n# Add environment-specific dependencies\ndependencies = [\n  \"coverage[toml]>=7.0.0\",\n]\n```\n\nIn this example, the test environment would include:\n1. Project dependencies (`requests>=2.28.0`)\n2. The test dependency group (`pytest>=7.0.0`)\n3. Environment-specific dependencies (`coverage[toml]>=7.0.0`)\n\n## Option overrides\n\nYou can modify options based on the conditions of different sources like [matrix variables](#matrix-variable-overrides) with the `overrides` table, using [dotted key](https://toml.io/en/v1.0.0#table) syntax for each declaration:\n\n```toml config-example\n[tool.hatch.envs.<ENV_NAME>.overrides]\n<SOURCE>.<CONDITION>.<OPTION> = <VALUE>\n```\n\nThe [type](#types) of the selected option determines the types of values.\n\n### Platform overrides\n\nOptions can be modified based on the current platform using the `platform` source.\n\n```toml config-example\n[tool.hatch.envs.test.overrides]\nplatform.windows.scripts = [\n  'run=pytest -m \"not io_uring\"',\n]\n```\n\nThe following platforms are supported:\n\n- `linux`\n- `windows`\n- `macos`\n\n### Environment variable overrides\n\nEnvironment variables can modify options using the `env` source.\n\n```toml config-example\n[tool.hatch.envs.test.overrides]\nenv.GITHUB_ACTIONS.dev-mode = { value = false, if = [\"true\"] }\n```\n\n### Matrix variable overrides\n\nThe [matrix](#matrix) variables used to generate each environment can be used to modify options within using the `matrix` source.\n\n```toml config-example\n[tool.hatch.envs.test.overrides]\nmatrix.version.env-vars = \"PRODUCT_VERSION\"\nmatrix.auth.features = [\n  { value = \"oauth\", if = [\"oauth2\"] },\n  { value = \"kerberos\", if = [\"kerberos\"] },\n]\n\n[[tool.hatch.envs.test.matrix]]\npython = [\"3.11\", \"3.12\"]\nversion = [\"legacy\", \"latest\"]\nauth = [\"oauth2\", \"kerberos\", \"noauth\"]\n```\n\n### Name overrides\n\nWhen a [matrix](#matrix) is defined, the `name` source can be used for regular expression matching on the generated name, minus the prefix for non-[default](#default-environment) environments.\n\n```toml config-example\n[tool.hatch.envs.test.overrides]\nname.\"^0\".env-vars = \"TESTING_UNSTABLE=true\"\n\n[[tool.hatch.envs.test.matrix]]\nversion = [\"0.1.0\", \"0.2.0\", \"1.0.0\"]\n```\n\n### Types\n\n- Literal types like strings for the [Python version](overview.md#python-version) or booleans for [skipping installation](overview.md#skip-install) can be set using the value itself, an inline table, or an array. For example:\n\n    ```toml config-example\n    [tool.hatch.envs.test.overrides]\n    matrix.foo.python = \"3.10\"\n    matrix.bar.skip-install = { value = true, if = [\"...\"] }\n    env.CI.dev-mode = [\n      { value = false, if = [\"...\"] },\n      true,\n    ]\n    ```\n\n    For arrays, the first allowed value will be used.\n\n- Array types like [dependencies](overview.md#dependencies) or [commands](overview.md#commands) can be appended to using an array of strings or inline tables. For example:\n\n    ```toml config-example\n    [tool.hatch.envs.test.overrides]\n    matrix.foo.dependencies = [\n      \"httpx\",\n      { value = \"cryptography\" },\n    ]\n    ```\n\n- Mapping types like [environment variables](overview.md#environment-variables) or [scripts](overview.md#scripts) can have keys set using a string, or an array of strings or inline tables. For example:\n\n    ```toml config-example\n    [tool.hatch.envs.test.overrides]\n    matrix.foo.env-vars = \"KEY=VALUE\"\n    matrix.bar.env-vars = [\n      \"KEY1=VALUE1\",\n      { key = \"KEY2\", value = \"VALUE2\" },\n    ]\n    ```\n\n    If the value is missing (no `=` for strings, no `value` key for inline tables), then the value will be set to the value of the source condition.\n\n### Overwriting\n\nRather than supplementing the values within mapping types or array types, you can overwrite the option as a whole by prefixing the name with `set-`:\n\n```toml config-example\n[tool.hatch.envs.test.overrides]\nmatrix.foo.set-platforms = [\"macos\", \"linux\"]\n```\n\nWhen overwriting entire options or keys within mappings, override sources are applied in the following order:\n\n1. [platform](#platform-overrides)\n2. [environment variables](#environment-variable-overrides)\n3. [matrix variables](#matrix-variable-overrides)\n4. [names](#name-overrides)\n\n### Conditions\n\nYou may specify certain extra keys for any inline table that will determine whether or not to apply that entry. These modifiers may be combined with others and any negative evaluation will immediately cause the entry to be skipped.\n\n#### Allowed values\n\nThe `if` key represents the allowed values for that condition. If the value of the condition is not listed, then that entry will not be applied:\n\n```toml config-example\n[tool.hatch.envs.test.overrides]\nmatrix.version.python = { value = \"pypy\", if = [\"3.14\"] }\nmatrix.version.env-vars = [\n  { key = \"KEY1\", value = \"VALUE1\", if = [\"42\"] },\n  { key = \"KEY2\", value = \"VALUE2\", if = [\"3.14\"] },\n]\n\n[[tool.hatch.envs.test.matrix]]\nversion = [\"42\", \"3.14\"]\n```\n\n#### Specific platforms\n\nThe `platform` key represents the desired platforms. If the current platform is not listed, then that entry will not be applied:\n\n```toml config-example\n[tool.hatch.envs.test.overrides]\nenv.EXPERIMENTAL.python = { value = \"pypy\", if = [\"1\"], platform = [\"macos\"] }\nmatrix.version.env-vars = [\n  { key = \"KEY1\", value = \"VALUE1\", if = [\"42\"], platform = [\"linux\"] },\n  { key = \"KEY2\", value = \"VALUE2\", if = [\"3.14\"] },\n]\n\n[[tool.hatch.envs.test.matrix]]\nversion = [\"42\", \"3.14\"]\n```\n\n#### Required environment variables\n\nThe `env` key represents the required environment variables. If any of the listed environment variables are not set or the defined value does not match, then that entry will not be applied:\n\n```toml config-example\n[tool.hatch.envs.test.overrides]\nplatform.windows.python = { value = \"pypy\", env = [\"EXPERIMENTAL\"] }\nmatrix.version.env-vars = [\n  { key = \"KEY1\", value = \"VALUE1\", if = [\"42\"], env = [\"FOO\", \"BAR=BAZ\"] },\n  { key = \"KEY2\", value = \"VALUE2\", if = [\"3.14\"] },\n]\n\n[[tool.hatch.envs.test.matrix]]\nversion = [\"42\", \"3.14\"]\n```\n[^1]: [PEP 735 – Dependency Groups in pyproject.toml](https://peps.python.org/pep-0735/)\n"
  },
  {
    "path": "docs/config/environment/overview.md",
    "content": "# Environment configuration\n\n-----\n\nAll environments are defined as sections within the `tool.hatch.envs` table.\n\n```toml config-example\n[tool.hatch.envs.<ENV_NAME>]\n```\n\nThe [storage location](../hatch.md#environments) for environments is completely configurable.\n\nUnless an environment is explicitly selected on the command line, the `default` environment will be used. The [type](#type) of this environment defaults to `virtual`.\n\n!!! info\n    Environments prefixed by `hatch-` are used for special purposes e.g. [testing](../internal/testing.md).\n\n## Inheritance\n\nAll environments inherit from the environment defined by its `template` option, which defaults to `default`.\n\nSo for the following configuration:\n\n```toml config-example\n[tool.hatch.envs.foo]\ntype = \"baz\"\nskip-install = true\n\n[tool.hatch.envs.bar]\ntemplate = \"foo\"\nskip-install = false\n```\n\nthe environment `bar` will be of type `baz` with `skip-install` set to `false`.\n\n!!! note\n    Environments do not inherit [matrices](advanced.md#matrix).\n\n### Self-referential environments\n\nYou can disable inheritance by setting `template` to the environment's own name:\n\n```toml config-example\n[tool.hatch.envs.foo]\ntemplate = \"foo\"\n```\n\n### Detached environments\n\nA common use case is standalone environments that do not require inheritance nor the installation of the project, such as for linting or sometimes building documentation. Enabling the `detached` option will make the environment [self-referential](#self-referential-environments) and will [skip project installation](#skip-install):\n\n```toml config-example\n[tool.hatch.envs.lint]\ndetached = true\n```\n\n## Dependencies\n\nYou can install [dependencies](../dependency.md) in addition to the ones defined by your [project's metadata](../metadata.md#dependencies). Entries support [context formatting](advanced.md#context-formatting).\n\n```toml config-example\n[tool.hatch.envs.test]\ndependencies = [\n  \"coverage[toml]\",\n  \"pytest\",\n  \"pytest-cov\",\n  \"pytest-mock\",\n]\n```\n\nIf you define environments with dependencies that only slightly differ from their [inherited environments](#inheritance), you can use the `extra-dependencies` option to avoid redeclaring the `dependencies` option:\n\n```toml config-example\n[tool.hatch.envs.default]\ndependencies = [\n  \"foo\",\n  \"bar\",\n]\n\n[tool.hatch.envs.experimental]\nextra-dependencies = [\n  \"baz\",\n]\n```\n\n!!! tip\n    Hatch uses [pip](https://github.com/pypa/pip) to install dependencies so any [configuration](https://pip.pypa.io/en/stable/topics/configuration/) it supports Hatch does as well. For example, if you wanted to only use a private repository you could set the `PIP_INDEX_URL` [environment variable](#environment-variables).\n\n## Installation\n\n### Features (extras) ### {: #features }\n\nIf your project defines [optional dependencies](../metadata.md#optional), you can select which groups to install using the `features` option:\n\n```toml config-example\n[tool.hatch.envs.nightly]\nfeatures = [\n  \"server\",\n  \"grpc\",\n]\n```\n\n!!! note\n    Features/optional dependencies are also known as `extras` in other tools.\n\n### Dependency Groups\n\n[Dependency groups](https://packaging.python.org/en/latest/specifications/dependency-groups/#dependency-groups) provide a uniform way to organize related development dependencies. See [advanced usage](advanced.md#dependency-groups) for more details on dependency group features like including other groups.\n\nYou can include dependency-groups in your hatch environments using the `[dependency-groups]` array:\n\n```toml config-example\n[dependency-groups]\ntest = [\n  \"pytest>=7.0.0\",\n  \"pytest-cov>=4.1.0\",\n]\n\n[tool.hatch.envs.test]\ndependency-groups = [\n  \"test\",\n]\n```\n\n### Dev mode\n\nBy default, environments will always reflect the current state of your project on disk, for example, by installing it in editable mode in a Python environment. Set `dev-mode` to `false` to disable this behavior and have your project installed only upon creation of a new environment. From then on, you need to manage your project installation manually.\n\n```toml config-example\n[tool.hatch.envs.static]\ndev-mode = false\n```\n\n### Skip install\n\nBy default, environments will install your project during creation. To ignore this step, set `skip-install` to `true`:\n\n```toml config-example\n[tool.hatch.envs.lint]\nskip-install = true\n```\n\n## Environment variables\n\n### Defined\n\nYou can define environment variables with the `env-vars` option:\n\n```toml config-example\n[tool.hatch.envs.docs]\ndependencies = [\n  \"mkdocs\"\n]\n[tool.hatch.envs.docs.env-vars]\nSOURCE_DATE_EPOCH = \"1580601600\"\n```\n\nValues support [context formatting](advanced.md#context-formatting).\n\n### Filters\n\nBy default, environments will have access to all environment variables. You can filter with wildcard patterns using the `env-include`/`env-exclude` options:\n\n```toml config-example\n[tool.hatch.envs.<ENV_NAME>]\nenv-include = [\n  \"FOO*\",\n]\nenv-exclude = [\n  \"BAR\",\n]\n```\n\nExclusion patterns take precedence but will never affect [defined](#defined) environment variables.\n\n## Scripts\n\nYou can define named scripts that may be [executed](../../environment.md#command-execution) or referenced at the beginning of other scripts. [Context formatting](advanced.md#context-formatting) is supported.\n\nFor example, in the following configuration:\n\n```toml config-example\n[tool.hatch.envs.test]\ndependencies = [\n  \"coverage[toml]\",\n  \"pytest\",\n  \"pytest-cov\",\n  \"pytest-mock\",\n]\n[tool.hatch.envs.test.scripts]\nrun-coverage = \"pytest --cov-config=pyproject.toml --cov=pkg --cov=tests\"\nrun = \"run-coverage --no-cov\"\n```\n\nthe `run` script would be expanded to:\n\n```\npytest --cov-config=pyproject.toml --cov=pkg --cov=tests --no-cov\n```\n\nScripts can also be defined as an array of strings.\n\n```toml config-example\n[tool.hatch.envs.style]\ndetached = true\ndependencies = [\n  \"flake8\",\n  \"black\",\n  \"isort\",\n]\n[tool.hatch.envs.style.scripts]\ncheck = [\n  \"flake8 .\",\n  \"black --check --diff .\",\n  \"isort --check-only --diff .\",\n]\nfmt = [\n  \"isort .\",\n  \"black .\",\n  \"check\",\n]\n```\n\nSimilar to [make](https://www.gnu.org/software/make/manual/html_node/Errors.html), you can ignore the exit code of commands that start with `-` (a hyphen). For example, the script `error` defined by the following configuration would halt after the second command with `3` as the exit code:\n\n```toml config-example\n[tool.hatch.envs.test.scripts]\nerror = [\n  \"- exit 1\",\n  \"exit 3\",\n  \"exit 0\",\n]\n```\n\n### Extra scripts\n\nIndividual scripts [inherit](#inheritance) from parent environments just like options. To guarantee that individual scripts do not override those defined by parent environments, you can use the `extra-scripts` option instead which is only capable of adding scripts that have not been defined.\n\n## Commands\n\nAll commands are able to use any defined [scripts](#scripts). Also like scripts, [context formatting](advanced.md#context-formatting) is supported and the exit code of commands that start with a hyphen will be ignored.\n\n### Pre-install\n\nYou can run commands immediately before environments [install](#skip-install) your project.\n\n```toml config-example\n[tool.hatch.envs.<ENV_NAME>]\npre-install-commands = [\n  \"...\",\n]\n```\n\n### Post-install\n\nYou can run commands immediately after environments [install](#skip-install) your project.\n\n```toml config-example\n[tool.hatch.envs.<ENV_NAME>]\npost-install-commands = [\n  \"...\",\n]\n```\n\n## Python version\n\nThe `python` option specifies which version of Python to use, or an absolute path to a Python interpreter:\n\n```toml config-example\n[tool.hatch.envs.<ENV_NAME>]\npython = \"3.10\"\n```\n\nAll [environment types](#type) should respect this option.\n\n## Supported platforms\n\nThe `platforms` option indicates the operating systems with which the environment is compatible:\n\n```toml config-example\n[tool.hatch.envs.<ENV_NAME>]\nplatforms = [\"linux\", \"windows\", \"macos\"]\n```\n\nThe following platforms are supported:\n\n- `linux`\n- `windows`\n- `macos`\n\nIf unspecified, the environment is assumed to be compatible with all platforms.\n\n## Description\n\nThe `description` option is purely informational and is displayed in the output of the [`env show`](../../cli/reference.md#hatch-env-show) command:\n\n```toml config-example\n[tool.hatch.envs.<ENV_NAME>]\ndescription = \"\"\"\nLorem ipsum ...\n\"\"\"\n```\n\n## Type\n\nAn environment's `type` determines which [environment plugin](../../plugins/environment/reference.md) will be used for management. The only built-in environment type is [`virtual`](../../plugins/environment/virtual.md), which uses virtual Python environments.\n"
  },
  {
    "path": "docs/config/hatch.md",
    "content": "# Hatch configuration\n\n-----\n\nConfiguration for Hatch itself is stored in a `config.toml` file located by default in one of the following platform-specific directories.\n\n| Platform | Path |\n| --- | --- |\n| macOS | `~/Library/Application Support/hatch` |\n| Windows | `%USERPROFILE%\\AppData\\Local\\hatch` |\n| Unix | `$XDG_CONFIG_HOME/hatch` (the [XDG_CONFIG_HOME](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables) environment variable default is `~/.config`) |\n\nYou can select a custom path to the file using the `--config` [root option](../cli/reference.md#hatch) or by setting the `HATCH_CONFIG` environment variable.\n\nThe file can be managed by the [`config`](../cli/reference.md#hatch-config) command group.\n\n## Mode\n\nThe `mode` key controls how Hatch selects the project to work on.\n\n### Local\n\n```toml tab=\"config.toml\"\nmode = \"local\"\n```\n\nBy default, Hatch will look for a `pyproject.toml` file in the current working directory\nand any parent directories. The directory storing the first found file will be considered the project root.\n\n### Project\n\n```toml tab=\"config.toml\"\nmode = \"project\"\nproject = \"proj1\"\n\n[projects]\nproj1 = \"/path/to/project1\"\nproj2 = {\"location\": \"/path/to/project2\"}\n\n[dirs]\nproject = [\"/path/to/monorepo1\", \"/path/to/monorepo2\"]\n```\n\nIn this mode, Hatch will only work on the selected `project`. The project is located using multiple heuristics:\n\n1. If the project is defined in the `projects` table then it must be a string, or an inline table with a `location` key, that is the full path to the project.\n2. If the project matches a subdirectory in any of the directories listed in `dirs.project`, then that will be used as the project root.\n\nAn error will occur if the project cannot be found.\n\nYou can use the [`config set`](../cli/reference.md#hatch-config-set) command to change the project you are working on:\n\n```console\n$ hatch config set project proj2\nNew setting:\nproject = \"proj2\"\n```\n\nThe project can be selected on a per-command basis with the `-p`/`--project` (environment variable `HATCH_PROJECT`) [root option](../cli/reference.md#hatch).\n\n### Aware\n\n```toml tab=\"config.toml\"\nmode = \"aware\"\n```\n\nThis is essentially the `local` mode with a fallback to the `project` mode.\n\n## Shell\n\nYou can control the shell used to [enter environments](../environment.md#entering-environments) with the `shell` key.\n\nIf defined as a string, it must be the name of one of the [supported shells](#supported) and be available along your `PATH`.\n\n```toml tab=\"config.toml\"\nshell = \"fish\"\n```\n\nIf the executable name of your shell differs from the supported name, you can define the `shell` as a table with `name` and `path` keys.\n\n```toml tab=\"config.toml\"\n[shell]\nname = \"bash\"\npath = \"/bin/ash\"\n```\n\nYou can change the default arguments used to spawn most shells with the `args` key. The default for such supported shells is usually `[\"-i\"]`.\n\n```toml tab=\"config.toml\"\n[shell]\nname = \"bash\"\nargs = [\"--login\"]\n```\n\n### Supported\n\n| Shell | Name | Arguments | macOS | Windows | Unix |\n| --- | --- | --- | --- | --- | --- |\n| [Almquist shell](https://en.wikipedia.org/wiki/Almquist_shell) | `ash` | `[\"-i\"]` | :white_check_mark: | | :white_check_mark: |\n| [Bash](https://www.gnu.org/software/bash/) | `bash` | `[\"-i\"]` | :white_check_mark: | :white_check_mark: | :white_check_mark: |\n| [Command Prompt](https://en.wikipedia.org/wiki/Cmd.exe) | `cmd` | | | :white_check_mark: | |\n| [C shell](https://en.wikipedia.org/wiki/C_shell) | `csh` | `[\"-i\"]` | :white_check_mark: | | :white_check_mark: |\n| [fish](https://github.com/fish-shell/fish-shell) | `fish` | `[\"-i\"]` | :white_check_mark: | | :white_check_mark: |\n| [Nushell](https://github.com/nushell/nushell) | `nu` | `[]` | :white_check_mark: | :white_check_mark: | :white_check_mark: |\n| [PowerShell](https://github.com/PowerShell/PowerShell) | `pwsh`, `powershell` | | :white_check_mark: | :white_check_mark: | :white_check_mark: |\n| [tcsh](https://en.wikipedia.org/wiki/Tcsh) | `tcsh` | `[\"-i\"]` | :white_check_mark: | | :white_check_mark: |\n| [xonsh](https://github.com/xonsh/xonsh) | `xonsh` | `[\"-i\"]` | :white_check_mark: | :white_check_mark: | :white_check_mark: |\n| [Z shell](https://en.wikipedia.org/wiki/Z_shell) | `zsh` | `[\"-i\"]` | :white_check_mark: | | :white_check_mark: |\n\n### Default\n\nHatch will attempt to use the current shell based on parent processes. If the shell cannot be determined, then on Windows systems Hatch will use the `SHELL` environment variable, if present, followed by the `COMSPEC` environment variable, defaulting to `cmd`. On all other platforms only the `SHELL` environment variable will be used, defaulting to `bash`.\n\n## Directories\n\n### Data\n\n```toml tab=\"config.toml\"\n[dirs]\ndata = \"...\"\n```\n\nThis is the directory that is used to persist data. By default it is set to one of the following platform-specific directories.\n\n| Platform | Path |\n| --- | --- |\n| macOS | `~/Library/Application Support/hatch` |\n| Windows | `%USERPROFILE%\\AppData\\Local\\hatch` |\n| Unix | `$XDG_DATA_HOME/hatch` (the [XDG_DATA_HOME](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables) environment variable default is `~/.local/share`) |\n\nYou can select a custom path to the directory using the `--data-dir` [root option](../cli/reference.md#hatch) or by setting the `HATCH_DATA_DIR` environment variable.\n\n### Cache\n\n```toml tab=\"config.toml\"\n[dirs]\ncache = \"...\"\n```\n\nThis is the directory that is used to cache data. By default it is set to one of the following platform-specific directories.\n\n| Platform | Path |\n| --- | --- |\n| macOS | `~/Library/Caches/hatch` |\n| Windows | `%USERPROFILE%\\AppData\\Local\\hatch\\Cache` |\n| Unix | `$XDG_CACHE_HOME/hatch` (the [XDG_CACHE_HOME](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables) environment variable default is `~/.cache`) |\n\nYou can select a custom path to the directory using the `--cache-dir` [root option](../cli/reference.md#hatch) or by setting the `HATCH_CACHE_DIR` environment variable.\n\n### Environments\n\n```toml tab=\"config.toml\"\n[dirs.env]\n<ENV_TYPE> = \"...\"\n```\n\nThis determines where to store environments, with every key being the [type of environment](environment/overview.md#type) and the value being the desired storage location.\n\nFor example, if you wanted to store [virtual environments](../plugins/environment/virtual.md) in a `.virtualenvs` directory within your home directory, you could specify the following:\n\n```toml tab=\"config.toml\"\n[dirs.env]\nvirtual = \"~/.virtualenvs\"\n```\n\nAny environment variables are also expanded.\n\nIf the path is not absolute, then it will be relative to the project root. So if you wanted to use a directory named `.hatch` in each project directory, you could do:\n\n```toml tab=\"config.toml\"\n[dirs.env]\nvirtual = \".hatch\"\n```\n\nAny type of environment that is not explicitly defined will default to `<DATA_DIR>/env/<ENV_TYPE>`.\n\n### Python installations\n\n```toml tab=\"config.toml\"\n[dirs]\npython = \"...\"\n```\n\nThis determines where to install specific versions of Python.\n\nThe following values have special meanings:\n\n| Value | Path |\n| --- | --- |\n| `isolated` (default) | `<DATA_DIR>/pythons` |\n\n## Terminal\n\nYou can configure how all output is displayed using the `terminal.styles` table. These settings are also applied to all plugins.\n\n```toml tab=\"config.toml\"\n[terminal.styles]\nerror = \"...\"\n...\n```\n\nCross-platform terminal capabilities are provided by [Rich](https://github.com/Textualize/rich).\n\n### Output levels\n\nThe levels of output are as follows. Note that the [verbosity](../cli/about.md) indicates the minimum level at which the output is displayed.\n\n| Level | Default | Verbosity | Description |\n| --- | --- | ---: | --- |\n| `debug` | `bold` | 1 - 3 | Messages that are not useful for most user experiences |\n| `error` | `bold red` | -2 | Messages indicating some unrecoverable error |\n| `info` | `bold` | 0 | Messages conveying basic information |\n| `success` | `bold cyan` | 0 | Messages indicating some positive outcome |\n| `waiting` | `bold magenta` | 0 | Messages shown before potentially time consuming operations |\n| `warning` | `bold yellow` | -1 | Messages conveying important information |\n\nSee the [documentation](https://rich.readthedocs.io/en/latest/style.html) and [color reference](https://rich.readthedocs.io/en/latest/appendix/colors.html) for guidance on valid values.\n\n### Spinner\n\nYou can select the [sequence](https://github.com/Textualize/rich/blob/master/rich/_spinners.py) used for waiting animations with the `spinner` option.\n\n```toml tab=\"config.toml\"\n[terminal.styles]\nspinner = \"...\"\n```\n"
  },
  {
    "path": "docs/config/internal/build.md",
    "content": "#  Build environment configuration\n\n-----\n\nYou can fully alter the behavior of the environment used by the [`build`](../../cli/reference.md#hatch-build) command.\n\n## Dependencies\n\nBuild environments will always have what is required by the [build system](../build.md#build-system), [targets](../build.md#target-dependencies), and [hooks](../build.md#hook-dependencies).\n\nYou can define [dependencies](../environment/overview.md#dependencies) that your builds may require in the environment as well:\n\n```toml config-example\n[tool.hatch.envs.hatch-build]\ndependencies = [\n  \"cython\",\n]\n```\n\n!!! warning \"caution\"\n    It's recommended to only use the standard mechanisms to define build dependencies for better compatibility with other tools.\n\n## Environment variables\n\nYou can define [environment variables](../environment/overview.md#environment-variables) that will be set during builds:\n\n```toml config-example\n[tool.hatch.envs.hatch-build.env-vars]\nSOURCE_DATE_EPOCH = \"1580601600\"\n```\n\n## Installer\n\nBy default, [UV is enabled](../../how-to/environment/select-installer.md). You may disable that behavior as follows:\n\n```toml config-example\n[tool.hatch.envs.hatch-build]\ninstaller = \"pip\"\n```\n"
  },
  {
    "path": "docs/config/internal/static-analysis.md",
    "content": "# Static analysis configuration\n\n-----\n\nStatic analysis performed by the [`fmt`](../../cli/reference.md#hatch-fmt) command is ([by default](#customize-behavior)) backed entirely by [Ruff](https://github.com/astral-sh/ruff).\n\nHatch provides [default settings](#default-settings) that user configuration can [extend](#extending-config).\n\n## Extending config\n\nWhen defining your configuration, be sure to use options that are prefixed by `extend-` such as [`extend-select`](https://docs.astral.sh/ruff/settings/#extend-select), for example:\n\n=== \":octicons-file-code-16: pyproject.toml\"\n\n    ```toml\n    [tool.ruff.format]\n    preview = true\n    quote-style = \"single\"\n\n    [tool.ruff.lint]\n    preview = true\n    extend-select = [\"C901\"]\n\n    [tool.ruff.lint.extend-per-file-ignores]\n    \"docs/.hooks/*\" = [\"INP001\", \"T201\"]\n\n    [tool.ruff.lint.isort]\n    known-first-party = [\"foo\", \"bar\"]\n    ```\n\n=== \":octicons-file-code-16: ruff.toml\"\n\n    ```toml\n    [format]\n    preview = true\n    quote-style = \"single\"\n\n    [lint]\n    preview = true\n    extend-select = [\"C901\"]\n\n    [lint.extend-per-file-ignores]\n    \"docs/.hooks/*\" = [\"INP001\", \"T201\"]\n\n    [lint.isort]\n    known-first-party = [\"foo\", \"bar\"]\n    ```\n\n!!! note\n    When not [persisting config](#persistent-config), there is no need to explicitly [extend](https://docs.astral.sh/ruff/settings/#extend) the defaults as Hatch automatically handles that.\n\n## Persistent config\n\nIf you want to store the default configuration in the project, set an explicit path like so:\n\n```toml config-example\n[tool.hatch.envs.hatch-static-analysis]\nconfig-path = \"ruff_defaults.toml\"\n```\n\nThen instruct Ruff to consider your configuration as an extension of the default file:\n\n=== \":octicons-file-code-16: pyproject.toml\"\n\n    ```toml\n    [tool.ruff]\n    extend = \"ruff_defaults.toml\"\n    ```\n\n=== \":octicons-file-code-16: ruff.toml\"\n\n    ```toml\n    extend = \"ruff_defaults.toml\"\n    ```\n\nAnytime you wish to update the defaults (such as when upgrading Hatch), you must run the [`fmt`](../../cli/reference.md#hatch-fmt) command once with the `--sync` flag e.g.:\n\n```\nhatch fmt --check --sync\n```\n\n!!! tip\n    This is the recommended approach since it allows other tools like IDEs to use the default configuration.\n\n### No config\n\nIf you don't want Hatch to use any of its default configuration and rely entirely on yours, set the path to anything and then simply don't `extend` in your Ruff config:\n\n```toml config-example\n[tool.hatch.envs.hatch-static-analysis]\nconfig-path = \"none\"\n```\n\n## Customize behavior\n\nYou can fully alter the behavior of the environment used by the [`fmt`](../../cli/reference.md#hatch-fmt) command. See the [how-to](../../how-to/static-analysis/behavior.md) for a detailed example.\n\n### Dependencies\n\nPin the particular version of Ruff by explicitly defining the environment [dependencies](../environment/overview.md#dependencies):\n\n```toml config-example\n[tool.hatch.envs.hatch-static-analysis]\ndependencies = [\"ruff==X.Y.Z\"]\n```\n\n### Scripts\n\nIf you want to change the default commands that are executed, you can override the [scripts](../environment/overview.md#scripts). The following four scripts must be defined:\n\n```toml config-example\n[tool.hatch.envs.hatch-static-analysis.scripts]\nformat-check = \"...\"\nformat-fix = \"...\"\nlint-check = \"...\"\nlint-fix = \"...\"\n```\n\nThe `format-*` scripts correspond to the `--formatter`/`-f` flag while the `lint-*` scripts correspond to the `--linter`/`-l` flag. The `*-fix` scripts run by default while the `*-check` scripts correspond to the `--check` flag.\n\n!!! note \"Reminder\"\n    If you choose to use different tools for static analysis, be sure to update the required [dependencies](#dependencies).\n\n### Installer\n\nBy default, [UV is enabled](../../how-to/environment/select-installer.md). You may disable that behavior as follows:\n\n```toml config-example\n[tool.hatch.envs.hatch-static-analysis]\ninstaller = \"pip\"\n```\n\n## Default settings\n\n### Non-rule settings\n\n- [Line length](https://docs.astral.sh/ruff/settings/#line-length) set to 120\n- [Docstring formatting](https://docs.astral.sh/ruff/formatter/#docstring-formatting) enabled with [line length](https://docs.astral.sh/ruff/settings/#format_docstring-code-line-length) set to 80\n- Only absolute imports [are allowed](https://docs.astral.sh/ruff/settings/#lint_flake8-tidy-imports_ban-relative-imports), [except for tests](#per-file-ignored-rules)\n- The normalized [project name](../metadata.md#name) is a [known first party](https://docs.astral.sh/ruff/settings/#lint_isort_known-first-party) import\n\n### Per-file ignored rules\n\n<HATCH_RUFF_PER_FILE_IGNORED_RULES>\n\n### Selected rules\n\nThe following rules are based on version <HATCH_RUFF_VERSION> of Ruff. Rules with a ^P^ are only selected when [preview](https://docs.astral.sh/ruff/preview/) mode is enabled.\n\nThere are <HATCH_RUFF_STABLE_RULES_COUNT> selected stable rules and <HATCH_RUFF_PREVIEW_RULES_COUNT> selected preview rules.\n\n<HATCH_RUFF_SELECTED_RULES>\n\n#### Unselected\n\nThere are <HATCH_RUFF_UNSELECTED_RULES_COUNT> unselected rules.\n\n<HATCH_RUFF_UNSELECTED_RULES>\n"
  },
  {
    "path": "docs/config/internal/testing.md",
    "content": "# Testing configuration\n\n-----\n\nCheck out the [testing overview tutorial](../../tutorials/testing/overview.md) for a more comprehensive walk-through.\n\n## Settings\n\nIf an option has a corresponding [`test`](../../cli/reference.md#hatch-test) command flag, the flag will always take precedence.\n\n### Default arguments\n\nYou can define default arguments for the [`test`](../../cli/reference.md#hatch-test) command by setting the `default-args` option, which must be an array of strings. The following is the default configuration:\n\n```toml config-example\n[tool.hatch.envs.hatch-test]\ndefault-args = [\"tests\"]\n```\n\n### Extra arguments\n\nYou can define extra internal arguments for test [scripts](#scripts) by setting the `extra-args` option, which must be an array of strings. For example, if you wanted to increase the verbosity of `pytest`, you could set the following:\n\n```toml config-example\n[tool.hatch.envs.hatch-test]\nextra-args = [\"-vv\"]\n```\n\n### Randomize test order\n\nYou can [randomize](https://github.com/pytest-dev/pytest-randomly) the order of tests by enabling the `randomize` option which corresponds to the `--randomize`/`-r` flag:\n\n```toml config-example\n[tool.hatch.envs.hatch-test]\nrandomize = true\n```\n\n### Parallelize test execution\n\nYou can [parallelize](https://github.com/pytest-dev/pytest-xdist) test execution by enabling the `parallel` option which corresponds to the `--parallel`/`-p` flag:\n\n```toml config-example\n[tool.hatch.envs.hatch-test]\nparallel = true\n```\n\n### Retry failed tests\n\nYou can [retry](https://github.com/pytest-dev/pytest-rerunfailures) failed tests by setting the `retries` option which corresponds to the `--retries` flag:\n\n```toml config-example\n[tool.hatch.envs.hatch-test]\nretries = 2\n```\n\nYou can also set the number of seconds to wait between retries by setting the `retry-delay` option which corresponds to the `--retry-delay` flag:\n\n```toml config-example\n[tool.hatch.envs.hatch-test]\nretry-delay = 1\n```\n\n## Customize environment\n\nYou can fully alter the behavior of the environment used by the [`test`](../../cli/reference.md#hatch-test) command.\n\n### Dependencies\n\nYou can define [extra dependencies](../environment/overview.md#dependencies) that your tests may require:\n\n```toml config-example\n[tool.hatch.envs.hatch-test]\nextra-dependencies = [\n  \"pyfakefs\",\n  \"pytest-asyncio\",\n  \"pytest-benchmark\",\n  \"pytest-memray\",\n  \"pytest-playwright\",\n  \"pytest-print\",\n]\n```\n\nThe following is the default configuration:\n\n```toml config-example\n<HATCH_TEST_ENV_DEPENDENCIES>\n```\n\n### Matrix\n\nYou can override the default series of [matrices](../environment/advanced.md#matrix):\n\n```toml config-example\n<HATCH_TEST_ENV_MATRIX>\n```\n\n### Scripts\n\nIf you want to change the default commands that are executed, you can override the [scripts](../environment/overview.md#scripts). The following default scripts must be redefined:\n\n```toml config-example\n<HATCH_TEST_ENV_SCRIPTS>\n```\n\nThe `run` script is the default behavior while the `run-cov` script is used instead when measuring code coverage. The `cov-combine` script runs after all tests complete when measuring code coverage, as well as the `cov-report` script when not using the `--cover-quiet` flag.\n\n!!! note\n    The `HATCH_TEST_ARGS` environment variable is how the [`test`](../../cli/reference.md#hatch-test) command's flags are translated and internally populated without affecting the user's arguments. This is also the way that [extra arguments](#extra-arguments) are passed.\n\n### Installer\n\nBy default, [UV is enabled](../../how-to/environment/select-installer.md). You may disable that behavior as follows:\n\n```toml config-example\n[tool.hatch.envs.hatch-test]\ninstaller = \"pip\"\n```\n"
  },
  {
    "path": "docs/config/metadata.md",
    "content": "# Configuring project metadata\n\n-----\n\nProject metadata is stored in a `pyproject.toml` file located at the root of a project's tree\nand is based entirely on [the standard][project metadata standard].\n\n## Name (*required*) ## {: #name }\n\nThe name of the project.\n\n```toml tab=\"pyproject.toml\"\n[project]\nname = \"your-app\"\n```\n\n## Version (*required*) ## {: #version }\n\n=== \":octicons-file-code-16: pyproject.toml\"\n\n    === \"Dynamic\"\n        See the dedicated [versioning](../version.md) section.\n\n        ```toml\n        [project]\n        ...\n        dynamic = [\"version\"]\n\n        [tool.hatch.version]\n        path = \"...\"\n        ```\n\n    === \"Static\"\n        ```toml\n        [project]\n        ...\n        version = \"0.0.1\"\n        ```\n\n## Description\n\nA brief summary of the project.\n\n```toml tab=\"pyproject.toml\"\n[project]\n...\ndescription = '...'\n```\n\n## Readme\n\nThe full description of the project.\n\n=== \":octicons-file-code-16: pyproject.toml\"\n\n    === \"Simple\"\n        The file extension must be `.md`, `.rst`, or `.txt`.\n\n        ```toml\n        [project]\n        ...\n        readme = \"README.md\"\n        ```\n\n    === \"Complex\"\n        The `content-type` field must be set to `text/markdown`, `text/x-rst`, or `text/plain`.\n\n        === \"File\"\n            A `charset` field may also be set to instruct which encoding to\n            use for reading the file, defaulting to `utf-8`.\n\n            ```toml\n            [project]\n            ...\n            readme = {\"file\" = \"README.md\", \"content-type\" = \"text/markdown\"}\n            ```\n\n        === \"Text\"\n            The `content-type` field must be set to `text/markdown` or `text/x-rst`.\n\n            ```toml\n            [project]\n            ...\n            readme = {\"text\" = \"...\", \"content-type\" = \"text/markdown\"}\n            ```\n\n!!! note\n    If this is defined as a file, then it will always be included in [source distributions](../plugins/builder/sdist.md) for consistent builds.\n\n## Python support\n\nThe Python version requirements of the project.\n\n```toml tab=\"pyproject.toml\"\n[project]\n...\nrequires-python = \">=3.8\"\n```\n\n## License\n\nFor more information, see [PEP 639][].\n\n=== \":octicons-file-code-16: pyproject.toml\"\n\n    === \"SPDX expression\"\n\n        ```toml\n        [project]\n        ...\n        license = \"Apache-2.0 OR MIT\"\n        ```\n\n    === \"Files\"\n\n        ```toml\n        [project]\n        ...\n        license-files = [\"LICENSES/*\"]\n        ```\n\n## Ownership\n\nThe people or organizations considered to be the `authors` or `maintainers` of the project.\nThe exact meaning is open to interpretation; it may list the original or primary authors,\ncurrent maintainers, or owners of the package. If the values are the same, prefer only the\nuse of the `authors` field.\n\n```toml tab=\"pyproject.toml\"\n[project]\n...\nauthors = [\n  { name = \"...\", email = \"...\" },\n]\nmaintainers = [\n  { name = \"...\", email = \"...\" },\n]\n```\n\n## Keywords\n\nThe keywords used to assist in the discovery of the project.\n\n```toml tab=\"pyproject.toml\"\n[project]\n...\nkeywords = [\n  \"...\",\n]\n```\n\n## Classifiers\n\nThe [trove classifiers](https://pypi.org/classifiers/) that apply to the project.\n\n```toml tab=\"pyproject.toml\"\n[project]\n...\nclassifiers = [\n  \"...\",\n]\n```\n\n## URLs\n\nA table of URLs where the key is the URL label and the value is the URL itself.\n\n```toml tab=\"pyproject.toml\"\n[project.urls]\nDocumentation = \"...\"\n\"Source code\" = \"...\"\n```\n\n## Dependencies\n\nSee the [dependency specification](dependency.md) page for more information.\n\nEntries support [context formatting](context.md) and [disallow direct references](#allowing-direct-references) by default.\n\n### Required\n\n```toml tab=\"pyproject.toml\"\n[project]\n...\ndependencies = [\n  \"...\",\n]\n```\n\n### Optional\n\n```toml tab=\"pyproject.toml\"\n[project.optional-dependencies]\noption1 = [\n  \"...\",\n]\noption2 = [\n  \"...\",\n]\n```\n\n## Entry points\n\n[Entry points](https://packaging.python.org/specifications/entry-points/) are a mechanism for\nthe project to advertise components it provides to be discovered and used by other code.\n\n### CLI\n\nAfter installing projects that define CLI scripts, each key will be available along your `PATH` as a command that will call its associated object.\n\n```toml tab=\"pyproject.toml\"\n[project.scripts]\ncli-name = \"pkg.subpkg:func\"\n```\n\nUsing the above example, running `cli-name` would essentially execute the following Python script:\n\n```python\nimport sys\n\nfrom pkg.subpkg import func\n\nsys.exit(func())\n```\n\n### GUI\n\nGUI scripts are exactly the same as CLI scripts except on Windows, where they are handled specially so that they can be started without a console.\n\n```toml tab=\"pyproject.toml\"\n[project.gui-scripts]\ngui-name = \"pkg.subpkg:func\"\n```\n\n### Plugins\n\n```toml tab=\"pyproject.toml\"\n[project.entry-points.plugin-namespace]\nplugin-name1 = \"pkg.subpkg1\"\nplugin-name2 = \"pkg.subpkg2:func\"\n```\n\n## Dynamic\n\nIf any metadata fields are set dynamically, like the [`version`](#version) may be, then they must be listed here.\n\n```toml tab=\"pyproject.toml\"\n[project]\n...\ndynamic = [\n  \"...\",\n]\n```\n\n## Metadata options\n\n### Allowing direct references\n\nBy default, [dependencies](#dependencies) are not allowed to define [direct references](https://peps.python.org/pep-0440/#direct-references). To disable this check, set `allow-direct-references` to `true`:\n\n```toml config-example\n[tool.hatch.metadata]\nallow-direct-references = true\n```\n\n### Allowing ambiguous features\n\nBy default, names of [optional dependencies](#optional) are normalized to prevent ambiguity. To disable this normalization, set `allow-ambiguous-features` to `true`:\n\n```toml config-example\n[tool.hatch.metadata]\nallow-ambiguous-features = true\n```\n\n!!! danger \"Deprecated\"\n    This option temporarily exists to provide better interoperability with tools that do not yet support [PEP 685](https://peps.python.org/pep-0685/) and will be removed in the first minor release after Jan 1, 2024.\n"
  },
  {
    "path": "docs/config/project-templates.md",
    "content": "# Project templates\n\n-----\n\nYou can control how new projects are created by the [new](../cli/reference.md#hatch-new) command using Hatch's [config file](hatch.md).\n\n## Author\n\n```toml tab=\"config.toml\"\n[template]\nname = \"...\"\nemail = \"...\"\n```\n\n## Licenses\n\n```toml tab=\"config.toml\"\n[template.licenses]\nheaders = true\ndefault = [\n  \"MIT\",\n]\n```\n\nThe list of licenses should be composed of [SPDX identifiers](https://spdx.org/licenses/). If multiple licenses are specified, then they will be placed in a [LICENSES](https://reuse.software/faq/#multi-licensing) directory.\n\n## Options\n\n### Tests\n\nThis adds a `tests` directory with environments for testing and linting.\n\n```toml tab=\"config.toml\"\n[template.plugins.default]\ntests = true\n```\n\n### CI\n\nThis adds a [GitHub Actions workflow](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#workflows) that runs tests on all platforms using modern versions of Python.\n\n```toml tab=\"config.toml\"\n[template.plugins.default]\nci = false\n```\n\n### `src` layout\n\nSee [this blog post](https://blog.ionelmc.ro/2014/05/25/python-packaging/).\n\n```toml tab=\"config.toml\"\n[template.plugins.default]\nsrc-layout = true\n```\n\n## Feature flags\n\n### Command line interface\n\nThe `--cli` flag adds a CLI backed by [Click](https://github.com/pallets/click) that can also be invoked with `python -m <PKG_NAME>`.\n"
  },
  {
    "path": "docs/environment.md",
    "content": "# Environments\n\n-----\n\n[Environments](config/environment/overview.md) are designed to allow for isolated workspaces for testing, building documentation, or anything else projects need.\n\nUnless an environment is [chosen explicitly](#selection), Hatch will use the `default` environment.\n\n!!! tip\n    For a more comprehensive walk-through, see the [Basic usage](tutorials/environment/basic-usage.md) tutorial.\n\n## Creation\n\nYou can create environments by using the [`env create`](cli/reference.md#hatch-env-create) command. Let's enter the directory of the project we created in the [setup phase](intro.md#new-project):\n\n```console\n$ hatch env create\nCreating environment: default\nInstalling project in development mode\nSyncing dependencies\n```\n\n!!! tip\n    You never need to manually create environments as [spawning a shell](#entering-environments) or [running commands](#command-execution) within one will automatically trigger creation.\n\n## Entering environments\n\nYou can spawn a [shell](config/hatch.md#shell) within an environment by using the [`shell`](cli/reference.md#hatch-shell) command.\n\n```console\n$ hatch shell\n(hatch-demo) $\n```\n\nNow confirm the project has been installed:\n\n```console\n(hatch-demo) $ pip show hatch-demo\nName: hatch-demo\nVersion: 0.0.1\n...\n```\n\nFinally, see where your environment's Python is [located](config/hatch.md#environments):\n\n```console\n(hatch-demo) $ python -c \"import sys;print(sys.executable)\"\n...\n```\n\nYou can type `exit` to leave the environment.\n\n## Command execution\n\nThe [`run`](cli/reference.md#hatch-run) command allows you to execute commands in an environment as if you had already entered it. For example, running the following command will output the same path as before:\n\n```\nhatch run python -c \"import sys;print(sys.executable)\"\n```\n\n!!! tip\n    Be sure to check out how to define [scripts](config/environment/overview.md#scripts) for your project.\n\n## Dependencies\n\nHatch ensures that environments are always compatible with the currently defined [project dependencies](config/metadata.md#dependencies) (if [installed](config/environment/overview.md#skip-install) and in [dev mode](config/environment/overview.md#dev-mode)) and [environment dependencies](config/environment/overview.md#dependencies).\n\nTo add `cowsay` as a dependency, open `pyproject.toml` and add it to the [`dependencies`](config/metadata.md#dependencies) array:\n\n```toml tab=\"pyproject.toml\"\n[project]\n...\ndependencies = [\n  \"cowsay\"\n]\n```\n\nThis dependency will be installed the next time you [spawn a shell](#entering-environments) or [run a command](#command-execution). For example:\n\n```console\n$ hatch run cowsay -t \"Hello, world!\"\nSyncing dependencies\n  _____________\n| Hello, world! |\n  =============\n             \\\n              \\\n                ^__^\n                (oo)\\_______\n                (__)\\       )\\/\\\n                    ||----w |\n                    ||     ||\n```\n\n!!! note\n    The `Syncing dependencies` status will display temporarily when Hatch updates environments in response to any dependency changes that you make.\n\n## Selection\n\nYou can select which environment to enter or run commands in by using the `-e`/`--env` [root option](cli/reference.md#hatch) or by setting the `HATCH_ENV` environment variable.\n\nThe [`run`](cli/reference.md#hatch-run) command allows for more explicit selection by prepending `<ENV_NAME>:` to commands. For example, if you had the following configuration:\n\n```toml config-example\n[tool.hatch.envs.docs]\ndependencies = [\n  \"mkdocs\"\n]\n[tool.hatch.envs.docs.scripts]\nbuild = \"mkdocs build --clean --strict\"\nserve = \"mkdocs serve --dev-addr localhost:8000\"\n```\n\nyou could then serve your documentation by running:\n\n```\nhatch run docs:serve\n```\n\n!!! tip\n    If you've already [entered](#entering-environments) an environment, commands will target it by default.\n\n## Matrix\n\nEvery environment can define its own set of [matrices](config/environment/advanced.md#matrix):\n\n```toml config-example\n[tool.hatch.envs.test]\ndependencies = [\n  \"pytest\"\n]\n\n[[tool.hatch.envs.test.matrix]]\npython = [\"3.10\", \"3.11\"]\nversion = [\"42\", \"3.14\"]\n\n[[tool.hatch.envs.test.matrix]]\npython = [\"3.11\", \"3.12\"]\nversion = [\"9000\"]\nfeature = [\"foo\", \"bar\"]\n```\n\nUsing the [`env show`](cli/reference.md#hatch-env-show) command would then display:\n\n```console\n$ hatch env show --ascii\n     Standalone\n+---------+---------+\n| Name    | Type    |\n+=========+=========+\n| default | virtual |\n+---------+---------+\n                        Matrices\n+------+---------+----------------------+--------------+\n| Name | Type    | Envs                 | Dependencies |\n+======+=========+======================+==============+\n| test | virtual | test.py3.10-42       | pytest       |\n|      |         | test.py3.10-3.14     |              |\n|      |         | test.py3.11-42       |              |\n|      |         | test.py3.11-3.14     |              |\n|      |         | test.py3.11-9000-foo |              |\n|      |         | test.py3.11-9000-bar |              |\n|      |         | test.py3.12-9000-foo |              |\n|      |         | test.py3.12-9000-bar |              |\n+------+---------+----------------------+--------------+\n```\n\n## Removal\n\nYou can remove a single environment or environment matrix by using the [`env remove`](cli/reference.md#hatch-env-remove) command or all of a project's environments by using the [`env prune`](cli/reference.md#hatch-env-prune) command.\n"
  },
  {
    "path": "docs/history/hatch.md",
    "content": "# Hatch history\n\n-----\n\nAll notable changes to Hatch will be documented in this file.\n\nThe 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).\n\n## Unreleased\n\n## [1.16.5](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.5) - 2026-02-26 ## {: #hatch-v1.16.5 }\n\n***Fixed:***\n\n- Handle a breaking change in `virtualenv` by only supporting the latest version and adding `python-discovery` as a dependency.\n\n## [1.16.4](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.4) - 2026-02-23 ## {: #hatch-v1.16.4 }\n\n***Fixed:***\n\n- Fixes hatch shell type error for keep_env.\n- SBOM documentation for including SBOM files in `sdist`\n- Fixes workspace member detection to properly handle shared path prefixes.\n\n## [1.16.3](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.3) - 2026-01-20 ## {: #hatch-v1.16.3 }\n\n***Added:***\n\n- Env var for keep-env when an exception occurs during environment creation to enable debugging.\n\n***Fixed:***\n\n- Fix issue with self-referential dependencies not being recognized.\n- Fix incomplete environments created when an exception occurs during creation.\n- Fix dependency-groups not working with when environment is not marked as builder.\n- Change Keyring to take expect repository URL instead of repository name.\n\n## [1.16.2](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.2) - 2025-12-06 ## {: #hatch-v1.16.2 }\n\n***Fixed:***\n\n- Properly send informational output to `stderr` instead of `stdout`\n- Implement documented support for `sbom-files` as build data for the `wheel` build target\n- Fix regression where environments no longer acknowledged the `project.optional-dependencies` field\n\n## [1.16.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.1) - 2025-11-27 ## {: #hatch-v1.16.1 }\n\n***Fixed:***\n\n- Handle special characters correctly in path for editable installs.\n- Fix multiple calls to install on `sync_dependencies` to become a single call.\n- Fix context variable formatting in project dependencies to prevent crashes when using variables like `{root:parent:uri}` in the `[project]` section.\n- Fix environment overrides for `dependency-groups` field to properly support matrix and conditional configurations.\n\n## [1.16.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.0) - 2025-11-26 ## {: #hatch-v1.16.0 }\n\n***Changed:***\n\n- Drop support for 3.9\n- Environment type plugins are now no longer expected to support a pseudo-build environment as any environment now may be used for building. The following methods have been removed: `build_environment`, `build_environment_exists`, `run_builder`, `construct_build_command`\n\n***Added:***\n\n- Support for workspaces inspired by Cargo Workspaces\n- Dependency group support.\n- The `version` and `project metadata` commands now support projects that do not use Hatchling as the build backend\n- The `version` command accepts a `--force` option, allowing for downgrades when an explicit version number is given.\n- Build environments can now be configured, the default build environment is `hatch-build`\n- The environment interface now has the following methods and properties in order to better support builds on remote machines: `project_root`, `sep`, `pathsep`, `fs_context`\n- Bump the minimum supported version of `packaging` to 24.2\n- Upgrade Ruff to 0.13.2\n\n***Fixed:***\n\n- All HTTP requests now set an identifiable `User-Agent` header.\n- Fix issue where terminal output would be out of sync during build.\n- Fix for Click Sentinel value when using `run` command\n\n## [1.15.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.15.1) - 2025-10-16 ## {: #hatch-v1.15.1 }\n\n***Fixed:***\n\n- Fix compatibility with cached default CPython distributions that were sourced from GitHub releases of the old owner\n\n## [1.15.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.15.0) - 2025-10-15 ## {: #hatch-v1.15.0 }\n\n***Changed:***\n\n- Drop support for Python 3.8\n\n***Added:***\n\n- Support Python 3.14\n- Upgrade default CPython distributions to 20251014\n- Upgrade default PyPy distributions to 7.3.20\n\n## [1.14.2](https://github.com/pypa/hatch/releases/tag/hatch-v1.14.2) - 2025-09-24 ## {: #hatch-v1.14.2 }\n\n***Fixed:***\n\n- Fix compatibility with recent versions of Click\n\n## [1.14.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.14.1) - 2025-04-07 ## {: #hatch-v1.14.1 }\n\n***Fixed:***\n\n- Remove uses of the deprecated `--no-python-version-warning` flag when using pip\n\n## [1.14.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.14.0) - 2024-12-16 ## {: #hatch-v1.14.0 }\n\n***Added:***\n\n- Upgrade default CPython distributions to 20241206\n- Bump the minimum supported version of Hatchling to 1.26.3\n- Update `virtualenv` dependency\n\n## [1.13.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.13.0) - 2024-10-13 ## {: #hatch-v1.13.0 }\n\n***Added:***\n\n- Support managing Python 3.13 distributions\n\n## [1.12.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.12.0) - 2024-05-28 ## {: #hatch-v1.12.0 }\n\n***Changed:***\n\n- The `run`/`env run` and `test` commands now treat inclusion variable options as an intersection rather than a union to allow for specific targeting of environments\n\n***Added:***\n\n- Add ability to control the source of Python distributions\n- Upgrade Ruff to 0.4.5\n- Upgrade PyApp to 0.22.0 for binary builds\n\n***Fixed:***\n\n- The `fmt` command no longer hides the commands that are being executed\n- Add default timeout for network requests, useful when installing Python distributions\n- Fix syntax highlighting contrast for the `config show` command\n\n## [1.11.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.11.1) - 2024-05-23 ## {: #hatch-v1.11.1 }\n\n***Added:***\n\n- Add official GitHub Action for installing Hatch\n\n***Fixed:***\n\n- Fix `terminal.styles.spinner` configuration\n- Fix entry points in the pre-built distributions that binaries use\n\n## [1.11.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.11.0) - 2024-05-14 ## {: #hatch-v1.11.0 }\n\n***Added:***\n\n- Upgrade PyApp to 0.21.1 for binary builds\n\n***Fixed:***\n\n- On Linux, install the highest compatible Python distribution variant based on CPU architecture rather than assuming recent hardware\n\n## [1.10.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.10.0) - 2024-05-02 ## {: #hatch-v1.10.0 }\n\n***Changed:***\n\n- The `run`/`env run`, `fmt` and `shell` commands now only change the current working directory to the project root if not already inside the project\n- The `shell` command now accepts a single argument to specify the environment to enter which overrides the standard choice mechanisms. The arguments determining shell options have been converted to flags.\n\n***Added:***\n\n- Add `test` command\n- The `run` command can now execute scripts that define inline metadata for dependencies and Python version constraints\n- The `virtual` environment type now supports the ability to use UV in place of pip & virtualenv\n- Add `self report` command for submitting pre-populated bug reports to GitHub\n- The reserved environment used for static analysis is now completely configurable\n- Add the following methods to the `environment` interface for complete control over output during life cycle management: `app_status_creation`, `app_status_pre_installation`, `app_status_post_installation`, `app_status_project_installation`, `app_status_dependency_state_check`, `app_status_dependency_installation_check`, `app_status_dependency_synchronization`\n- Add binaries for 32-bit versions of Windows\n- Read configuration from any `~/.pypirc` file for the `index` publisher\n- Use the Git user as the default username for new project URL metadata\n- Add `HATCH_DEBUG` environment variable that when enabled will show local variables in the case of unhandled tracebacks\n- The `env show` command now outputs data about all internal environments when using the `--json` flag\n- Upgrade default CPython distributions to 20240415\n- Upgrade default PyPy distributions to 7.3.15\n- Upgrade Ruff to 0.4.2\n- Upgrade PyApp to 0.19.0 for binary builds\n- Bump the minimum supported version of Hatchling to 1.24.2\n- Bump the minimum supported version of virtualenv to 20.26.1\n\n***Fixed:***\n\n- Maintain consistent data paths for case insensitive file systems\n- When projects derive dependencies from metadata hooks, there is now by default a status indicator for when the hooks are executed for better responsiveness\n- Properly support projects with a `pyproject.toml` file but no `project` table e.g. applications\n- Fix the `fmt` command when automatically installing plugin dependencies\n- Fix dependency inheritance for the template of the `types` environment for new projects\n- Fix warnings related to tar file extraction on Python 3.12+ when unpacking Python distributions for installation\n- De-select Ruff rule `E501` for the `fmt` command by default since it conflicts with the formatter\n- Fix colored output from build targets on the first run (build environment creation status indicator issue)\n- Set the `packaging` dependency version as `>=23.2` to avoid its URL validation which can conflict with context formatting\n- Fix the exit code when there happens to be an unhandled exception\n- No longer capture both stdout and stderr streams when parsing metadata payloads from build environments\n- Fix the `README.md` file template for new projects to avoid Markdown linting issues\n\n## [1.9.7](https://github.com/pypa/hatch/releases/tag/hatch-v1.9.7) - 2024-04-24 ## {: #hatch-v1.9.7 }\n\n***Fixed:***\n\n- Limit the maximum version of virtualenv due to a backward incompatible change\n- Upgrade PyApp to 0.12.0 for binary builds\n\n## [1.9.4](https://github.com/pypa/hatch/releases/tag/hatch-v1.9.4) - 2024-03-12 ## {: #hatch-v1.9.4 }\n\n***Fixed:***\n\n- Limit the maximum version of Hatchling in anticipation of backward incompatible changes\n\n## [1.9.3](https://github.com/pypa/hatch/releases/tag/hatch-v1.9.3) - 2024-01-25 ## {: #hatch-v1.9.3 }\n\n***Fixed:***\n\n- Fix loading of local plugins to account for newly released versions of a dependency\n\n## [1.9.2](https://github.com/pypa/hatch/releases/tag/hatch-v1.9.2) - 2024-01-21 ## {: #hatch-v1.9.2 }\n\n***Fixed:***\n\n- Fix the default token variable name for publishing to PyPI\n\n## [1.9.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.9.1) - 2023-12-25 ## {: #hatch-v1.9.1 }\n\n***Fixed:***\n\n- Ensure that the `dependency_hash` method of the `environment` interface is called after `sync_dependencies` for cases where the hash is only known at that point, such as for dependency lockers\n- Only acknowledge the `HATCH_PYTHON_VARIANT_*` environment variables for Python resolution for supported platforms and architectures\n- Fix Python resolution when there are metadata hooks with unsatisfied dependencies\n\n## [1.9.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.9.0) - 2023-12-19 ## {: #hatch-v1.9.0 }\n\n***Changed:***\n\n- Environments prefixed by `hatch-` are now considered internal and used for special purposes such as configuration for static analysis\n\n***Added:***\n\n- Enable docstring formatting by default for static analysis\n- Allow for overriding config of internal environments\n- Concretely state the expected API contract for the environment interface methods `find` and `check_compatibility`\n- Upgrade Ruff to 0.1.8\n- Bump the minimum supported version of Hatchling to 1.21.0\n\n***Fixed:***\n\n- Ignore a project's Python requirement for environments where the project is not installed\n- When not persisting config for static analysis, properly manage internal settings when Ruff's top level table already exists\n- Ignore compatibility checks when environments have already been created, significantly improving performance of environment usage\n- Properly allow overriding of the `path` option for the `virtual` environment type\n- Fix nushell activation on non-Windows systems\n\n## [1.8.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.8.1) - 2023-12-14 ## {: #hatch-v1.8.1 }\n\n***Fixed:***\n\n- Fix regression in calling subprocesses with updated PATH\n- Fix automatic installation of environment plugins when running as a standalone binary\n- Change default location of Python installations\n\n## [1.8.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.8.0) - 2023-12-11 ## {: #hatch-v1.8.0 }\n\n***Changed:***\n\n- Drop support for Python 3.7\n- The `get_build_process` method of the `environment` interface has been removed; plugins should use the new `run_builder` method instead\n- Remove `pyperclip` dependency and the `--copy` flag of the `config find` command\n- When running the `build` command all output from builders is now displayed as-is in real time without the stripping of ANSI codes\n- Version information (for Hatch itself) is now derived from Git\n\n***Added:***\n\n- Support Python 3.12\n- Add installers and standalone binaries\n- Add the ability to manage Python installations\n- Add `fmt` command\n- The `virtual` environment type can now automatically download requested versions of Python that are not installed\n- Add `dependency_hash` method to the `environment` interface\n- The state of installed dependencies for environments is saved as metadata so if dependency definitions have not changed then no checking is performed, which can be computationally expensive\n- The `build` command now supports backends other than Hatchling\n- Allow the use of `features` for environments when `skip-install` is enabled\n- The default is now `__token__` when prompting for a username for the `publish` command\n- Add a new `run_builder` method to the `environment` interface\n- Bump the minimum supported version of Hatchling to 1.19.0\n- Bump the minimum supported version of `click` to 8.0.6\n\n***Fixed:***\n\n- Fix nushell activation\n- Better handling of flat storage directory hierarchies for the `virtual` environment type\n- Display useful information when running the `version` command outside of a project rather than erroring\n- Fix the `project metadata` command by only capturing stdout from the backend\n- Properly support Google Artifact Registry\n- Fix parsing dependencies for environments when warnings are emitted\n\n## [1.7.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.7.0) - 2023-04-03 ## {: #hatch-v1.7.0 }\n\n***Changed:***\n\n- The `src-layout` project template option is now enabled by default\n- Non-critical output now goes to stderr\n\n***Added:***\n\n- Add `tool.hatch.env.requires` configuration to automatically install dependencies for environment and environment collector plugins\n- Add `custom` environment collector\n- Improve syncing of dependencies provided through Git direct references\n- Add `isolated_data_directory` attribute to the environment interface\n- Increase the timeout for and add retries to the `index` publisher\n- Expand home and environment variables in configured cache and data directories\n- Improve readability of exceptions\n- Update project templates\n- Bump the minimum supported version of Hatchling to 1.14.0\n\n***Fixed:***\n\n- Fix displaying the version with the `version` command when the version is static and build dependencies are unmet\n- Fix build environments for the `virtual` environment type when storing within a relative path\n- Work around System Integrity Protection on macOS when running commands\n- Allow setuptools metadata migration for projects without `setup.py` if `setup.cfg` is present\n- Handle additional edge cases for setuptools metadata migration\n- Support boolean values for the `config set` command\n\n## [1.6.3](https://github.com/pypa/hatch/releases/tag/hatch-v1.6.3) - 2022-10-24 ## {: #hatch-v1.6.3 }\n\n***Fixed:***\n\n- Fix `version` command when the version is dynamic and build dependencies are unmet\n\n## [1.6.2](https://github.com/pypa/hatch/releases/tag/hatch-v1.6.2) - 2022-10-20 ## {: #hatch-v1.6.2 }\n\n***Fixed:***\n\n- Fix getting dynamic metadata from hooks for environments when dependencies are not dynamic\n\n## [1.6.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.6.1) - 2022-10-16 ## {: #hatch-v1.6.1 }\n\n***Fixed:***\n\n- Computing the path to the user's home directory now gracefully falls back to `~` when it cannot be determined\n\n## [1.6.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.6.0) - 2022-10-08 ## {: #hatch-v1.6.0 }\n\n***Changed:***\n\n- The `run_shell_command` environment interface method now accepts arbitrary `subprocess.Popen` keyword arguments. This is not strictly breaking, but will be utilized in upcoming features.\n- The internal directory structure for storing `virtual` environments is now more nested. This is not breaking, but any local environments will be created anew.\n\n***Added:***\n\n- Add `project` command group to view details about the project like PEP 621 metadata\n- Better support for auto-detection of environments by tools like Visual Studio Code now that the storage directory of `virtual` environments will be flat if Hatch's configured `virtual` environment directory resides somewhere within the project root or if it is set to a `.virtualenvs` directory within the user's home directory\n- Build environments for the `virtual` environment type are now cached for improved performance\n- Add `build_environment_exists` method to the environment interface for implementations that cache the build environment\n- Add `path` option to the `virtual` environment type\n- Add `--initialize-auth` flag to the `index` publisher to allow for the saving of authentication information before publishing\n- Support Bash on Windows for the `shell` command\n- The `setuptools` migration script no longer modifies the formatting of existing `pyproject.toml` configuration\n- Bump the minimum supported version of Hatchling to 1.11.0\n\n***Fixed:***\n\n- Environments now respect dynamically defined project dependencies\n- The `dep hash` and all `dep show` commands now respect dynamically defined project dependencies\n- The `env show`, `dep hash`, and all `dep show` commands now honor context formatting\n- Fix matrix variable inclusion filtering of the `run` and `env run` commands when there are multiple possible variables\n- Build environment compatibility is now checked before use\n- Decreasing verbosity now has no affect on output that should always be displayed\n- Handle more edge cases in the `setuptools` migration script\n- Environments now respect user defined environment variables for context formatting\n- Update the scripts in the generated test environment template for new projects to reflect the documentation\n- Allow `extra-dependencies` in environment overrides\n- Depend on `packaging` explicitly rather than relying on it being a transitive dependency of Hatchling\n\n## [1.5.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.5.0) - 2022-08-28 ## {: #hatch-v1.5.0 }\n\n***Added:***\n\n- The `index` publisher now recognizes repository-specific options\n- Add the `--ignore-compat` flag to the `env run` command\n- Setting the `HATCH_PYTHON` environment variable to `self` will now force the use of the Python executable Hatch is running on for `virtual` environment creation\n\n***Fixed:***\n\n- Fix the `--force-continue` flag of the `env run` command\n- Handle more edge cases in the `setuptools` migration script\n\n## [1.4.2](https://github.com/pypa/hatch/releases/tag/hatch-v1.4.2) - 2022-08-16 ## {: #hatch-v1.4.2 }\n\n***Fixed:***\n\n- Fix check for updating static versions with the `version` command when metadata hooks are in use\n\n## [1.4.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.4.1) - 2022-08-13 ## {: #hatch-v1.4.1 }\n\n***Fixed:***\n\n- Fix non-detached inheritance disabling for environments\n\n## [1.4.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.4.0) - 2022-08-06 ## {: #hatch-v1.4.0 }\n\n***Added:***\n\n- The default Python for `virtual` environments now checks PATH before using the one Hatch is running on\n- Values for environment `env-vars` now support context formatting\n- Add `name` override for environments to allow for regular expression matching\n- The `index` publisher now better supports non-PyPI indices\n- Add certificate options to the `index` publisher\n- Display waiting text when checking dependencies and removing environments\n- Display help text the first time the `shell` command is executed\n- Update project templates with Python 3.11 and the latest versions of various GitHub Actions\n- Add support for Almquist (`ash`) shells\n- Add `hyperlink` as a dependency for better handling of package index URLs\n- Bump the minimum supported version of `virtualenv` to 20.16.2\n- Bump the minimum supported version of `tomlkit` to 0.11.1\n\n***Fixed:***\n\n- Acknowledge `extra-dependencies` for the `env show` command\n- Fix locating executables within virtual environments on Debian\n- Fix managing the terminal size inside the `shell` command\n- Fix default code coverage file omission for the `src-layout` project template option\n\n## [1.3.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.3.1) - 2022-07-11 ## {: #hatch-v1.3.1 }\n\n***Fixed:***\n\n- Support `-h`/`--help` flag for the `run` command\n\n## [1.3.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.3.0) - 2022-07-10 ## {: #hatch-v1.3.0 }\n\n***Changed:***\n\n- Rename the default publishing plugin from `pypi` to the more generic `index`\n\n***Added:***\n\n- Support the absence of `pyproject.toml` files, as is the case for apps and non-Python projects\n- Hide scripts that start with an underscore for the `env show` command by default\n- Ignoring the exit codes of commands by prefixing with hyphens now works with entire named scripts\n- Add a way to require confirmation for publishing\n- Add `--force-continue` flag to the `env run` command\n- Make tracebacks colorful and less verbose\n- When shell configuration has not been defined, attempt to use the current shell based on parent processes before resorting to the defaults\n- The shell name `pwsh` is now an alias for `powershell`\n- Remove `atomicwrites` dependency\n- Relax constraint on `userpath` dependency\n- Bump the minimum supported version of Hatchling to 1.4.1\n\n***Fixed:***\n\n- Keep environments in sync with the dependencies of the selected features\n- Use `utf-8` for all files generated for new projects\n- Escape special characters Git may return in the user name when writing generated files for new projects\n- Normalize the package name to lowercase in `setuptools` migration script\n- Fix parsing of source distributions during publishing\n\n## [1.2.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.2.1) - 2022-05-30 ## {: #hatch-v1.2.1 }\n\n***Fixed:***\n\n- Fix handling of top level `data_files` in `setuptools` migration script\n\n## [1.2.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.2.0) - 2022-05-22 ## {: #hatch-v1.2.0 }\n\n***Changed:***\n\n- The `enter_shell` environment plugin method now accepts an additional `args` parameter\n\n***Added:***\n\n- Allow context string formatting for environment dependencies\n- Add environment context string formatting fields `env_name`, `env_type`, `matrix`, `verbosity`, and `args`\n- Support overriding the default arguments used to spawn shells on non-Windows systems\n- Bump the minimum supported version of Hatchling to 1.3.0\n\n***Fixed:***\n\n- Improve `setuptools` migration script\n\n## [1.1.2](https://github.com/pypa/hatch/releases/tag/hatch-v1.1.2) - 2022-05-20 ## {: #hatch-v1.1.2 }\n\n***Fixed:***\n\n- Bump the minimum supported version of Hatchling to 1.2.0\n- Update project metadata to reflect support for Python 3.11\n\n## [1.1.1](https://github.com/pypa/hatch/releases/tag/hatch-v1.1.1) - 2022-05-12 ## {: #hatch-v1.1.1 }\n\n***Fixed:***\n\n- Fix `setuptools` migration script for non-Windows systems\n\n## [1.1.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.1.0) - 2022-05-12 ## {: #hatch-v1.1.0 }\n\n***Changed:***\n\n- In order to simplify the implementation of command execution for environment plugins, the `run_shell_commands` method has been replaced by the singular `run_shell_command`. A new `command_context` method has been added to more easily satisfy complex use cases.\n- The `finalize_command` environment plugin method has been removed in favor of the newly introduced context formatting functionality.\n\n***Added:***\n\n- Add context formatting functionality i.e. the ability to insert values into configuration like environment variables and command line arguments\n- Any verbosity for command execution will now always display headers, even for single environments\n- Every executed command is now displayed when running multiple commands or when verbosity is enabled\n- Similar to `make`, ignore the exit code of executed commands that start with `-` (a hyphen)\n- Add ability for the `--init` flag of the `new` command to automatically migrate `setuptools` configuration\n- Update project metadata to reflect the adoption by PyPA and production stability\n\n## [1.0.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.0.0) - 2022-04-28 ## {: #hatch-v1.0.0 }\n\nThis is the first stable release of Hatch v1, a complete rewrite. Enjoy!\n"
  },
  {
    "path": "docs/history/hatchling.md",
    "content": "# Hatchling history\n\n-----\n\nAll notable changes to Hatchling will be documented in this file.\n\nThe 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).\n\n## Unreleased\n\n## [1.29.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.29.0) - 2026-02-21 ## {: #hatchling-v1.29.0 }\n\n***Fixed:***\n\n- Source Date Epoch no longer fails when set to date before 1980. \n\n## [1.28.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.28.0) - 2025-11-26 ## {: #hatchling-v1.28.0 }\n\n***Changed:***\n\n- Drop support for Python 3.9\n\n***Added:***\n\n- Add `sbom-files` option and `sbom_files` build data to the `wheel` build target for including Software Bill of Materials files.\n\n## [1.27.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.27.0) - 2024-11-26 ## {: #hatchling-v1.27.0 }\n\n***Added:***\n\n- Update the default version of core metadata to 2.4\n\n## [1.26.3](https://github.com/pypa/hatch/releases/tag/hatchling-v1.26.3) - 2024-11-12 ## {: #hatchling-v1.26.3 }\n\n***Fixed:***\n\n- Support an old import path that is still used by some consumers like Hatch\n\n## [1.26.2](https://github.com/pypa/hatch/releases/tag/hatchling-v1.26.2) - 2024-11-12 ## {: #hatchling-v1.26.2 }\n\n***Fixed:***\n\n- Back-populate string `license` fields (`License-Expression`) for core metadata versions prior to 2.4\n- Remove the `License-Expression` and `License-Files` core metadata from version 2.2 that was missed in the previous minor release\n\n## [1.26.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.26.1) - 2024-11-10 ## {: #hatchling-v1.26.1 }\n\n***Fixed:***\n\n- Add backward compatibility for the old `license-files` metadata field\n- Support an old import path that is still used by some consumers like Hatch\n\n## [1.26.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.26.0) - 2024-11-10 ## {: #hatchling-v1.26.0 }\n\n***Changed:***\n\n- The `license-files` metadata field has been updated to the latest spec and is now just an array of glob patterns\n\n***Added:***\n\n- Support version 2.4 of core metadata for the `wheel` and `sdist` targets\n- Add `HATCH_METADATA_CLASSIFIERS_NO_VERIFY` environment variable to disable trove classifier verification\n- Add `.pixi` to the list of directories that cannot be traversed\n- Bump the minimum supported version of `packaging` to 24.2\n\n***Fixed:***\n\n- No longer write package metadata for license expressions and files for versions of core metadata prior to 2.4\n- Properly enable Zip64 support for the `wheel` target\n- Properly ignore parent `.gitingore` files when the project root matches one of the patterns\n\n## [1.25.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.25.0) - 2024-06-22 ## {: #hatchling-v1.25.0 }\n\n***Changed:***\n\n- The `macos-max-compat` option for the `wheel` target is now disabled by default and will be removed in a future release\n\n***Added:***\n\n- Artifacts for the `wheel` and `sdist` targets now have their permission bits normalized\n\n***Fixed:***\n\n- Ignore `manylinux`/`musllinux` tags for the `wheel` target artifact name when enabling the `infer_tag` build data\n- The `wheel` target build data `infer_tag` when enabled now respects the `MACOSX_DEPLOYMENT_TARGET` environment variable\n\n## [1.24.2](https://github.com/pypa/hatch/releases/tag/hatchling-v1.24.2) - 2024-04-22 ## {: #hatchling-v1.24.2 }\n\n***Fixed:***\n\n- Add `.venv` to the list of directories that cannot be traversed\n- Output from the core Application utility now writes to stderr\n\n## [1.24.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.24.1) - 2024-04-18 ## {: #hatchling-v1.24.1 }\n\n***Fixed:***\n\n- Maintain file permissions for `shared-scripts` option/`shared_scripts` build data of the `wheel` target\n\n## [1.24.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.24.0) - 2024-04-16 ## {: #hatchling-v1.24.0 }\n\n***Added:***\n\n- Add `shared_data` and `shared_scripts` build data for the `wheel` target\n\n## [1.23.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.23.0) - 2024-04-14 ## {: #hatchling-v1.23.0 }\n\n***Added:***\n\n- Add `shared-scripts` option for the `wheel` target\n\n***Fixed:***\n\n- Support recursive optional dependencies\n- Set the `packaging` dependency version as `>=23.2` to avoid its URL validation which can conflict with context formatting\n\n## [1.22.5](https://github.com/pypa/hatch/releases/tag/hatchling-v1.22.5) - 2024-04-04 ## {: #hatchling-v1.22.5 }\n\n***Fixed:***\n\n- Fix reading metadata from source distributions when fields are dynamic but not part of core metadata like entry points\n\n## [1.22.4](https://github.com/pypa/hatch/releases/tag/hatchling-v1.22.4) - 2024-03-23 ## {: #hatchling-v1.22.4 }\n\n***Fixed:***\n\n- Only read source distribution metadata for fields that are explicitly defined as dynamic\n\n## [1.22.3](https://github.com/pypa/hatch/releases/tag/hatchling-v1.22.3) - 2024-03-19 ## {: #hatchling-v1.22.3 }\n\n***Fixed:***\n\n- Fix the `custom` build hook when using dynamic dependencies\n\n## [1.22.2](https://github.com/pypa/hatch/releases/tag/hatchling-v1.22.2) - 2024-03-16 ## {: #hatchling-v1.22.2 }\n\n***Fixed:***\n\n- Fix regression when loading metadata from source distributions\n- Fix metadata hooks when building wheels from source distributions\n\n## [1.22.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.22.1) - 2024-03-16 ## {: #hatchling-v1.22.1 }\n\n***Fixed:***\n\n- Update the default version of core metadata to 2.3\n\n## [1.22.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.22.0) - 2024-03-16 ## {: #hatchling-v1.22.0 }\n\n***Deprecated:***\n\n- The `app` build target has been renamed to `binary` to reduce ambiguity with the name of an upcoming feature. The former name will still be usable for several minor releases.\n\n***Added:***\n\n- Metadata for the `wheel` target now defaults to the `PKG-INFO` metadata within source distributions\n- Add `dependencies` method to the build hook interface so that hooks can themselves dynamically define dependencies\n- Update the default version of core metadata to 2.2\n- Update SPDX license information to version 3.23\n- Improve error message for when the default heuristics for wheel file inclusion fail\n\n***Fixed:***\n\n- Properly support core metadata version 2.2\n- Remove `editables` as a direct dependency\n- Fix default wheel tag when the supported Python version declaration is strict\n- Load VCS ignore patterns first so that whitelisted patterns can be excluded by project configuration\n- Don't consider VCS ignore files that are outside of the VCS boundary\n- The `sdist` build target now gracefully ignores UNIX socket files\n- Begin ignoring certain files ubiquitously, like `.DS_Store` on macOS\n\n## [1.21.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.21.1) - 2024-01-25 ## {: #hatchling-v1.21.1 }\n\n***Fixed:***\n\n- Fix loading of local plugins to account for newly released versions of a dependency\n\n## [1.21.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.21.0) - 2023-12-18 ## {: #hatchling-v1.21.0 }\n\n***Added:***\n\n- Add `parent` context modifier for path fields\n\n## [1.20.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.20.0) - 2023-12-13 ## {: #hatchling-v1.20.0 }\n\n***Added:***\n\n- Add `bypass-selection` option to the `wheel` build target to allow for empty (metadata-only) wheels\n\n***Fixed:***\n\n- Fix regression in 1.19.1 that allowed `exclude` to count toward inclusion selection, thus bypassing the default inclusion selection heuristics\n- Fix writing optional dependency core metadata in situations where there are multiple environment markers\n\n## [1.19.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.19.1) - 2023-12-12 ## {: #hatchling-v1.19.1 }\n\n***Fixed:***\n\n- Add better error message when the `wheel` build target cannot determine what to ship\n- Consider forced inclusion patterns and build-time artifacts as file selection since some build hooks generate the entire wheel contents without user configuration\n\n## [1.19.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.19.0) - 2023-12-11 ## {: #hatchling-v1.19.0 }\n\n***Changed:***\n\n- An error will now be raised if a force-included path does not exist\n- An error will now be raised for the `wheel` build target if no file selection options are defined\n\n***Added:***\n\n- Officially support Python 3.12\n- Allow using an empty string for the `sources` option to add a prefix to distribution paths\n\n***Fixed:***\n\n- Properly handle non-zero version epoch for the `standard` version scheme\n- Fix the `wheel` build target for case insensitive file systems when the project metadata name does not match the directory name on disk\n- The `app` build target no longer has suppressed output\n- Prevent duplicate paths when projects require the `sources` option while build hooks overwrite included paths\n- Properly escape spaces for URI context formatting\n\n## [1.18.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.18.0) - 2023-06-12 ## {: #hatchling-v1.18.0 }\n\n***Changed:***\n\n- Drop support for Python 3.7\n\n***Added:***\n\n- Update the list of directories that are always excluded for builds\n\n## [1.17.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.17.1) - 2023-06-03 ## {: #hatchling-v1.17.1 }\n\n***Fixed:***\n\n- Fix dev mode when the project has symlinks and file inclusion is defined with the `packages` or `only-include` options\n- Change the name of generated PTH files for dev mode so they come first lexicographically and therefore load first\n\n## [1.17.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.17.0) - 2023-05-12 ## {: #hatchling-v1.17.0 }\n\n***Added:***\n\n- The `app` build target now embeds the project version in the name of binaries\n\n## [1.16.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.16.1) - 2023-05-11 ## {: #hatchling-v1.16.1 }\n\n***Fixed:***\n\n- Fix determining the built executable path for the `app` build target option when using a local copy of PyApp when there is an explicit target triple set\n\n## [1.16.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.16.0) - 2023-05-11 ## {: #hatchling-v1.16.0 }\n\n***Added:***\n\n- Add `app` build target option to build using a local copy of the PyApp repository\n\n## [1.15.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.15.0) - 2023-05-09 ## {: #hatchling-v1.15.0 }\n\n***Added:***\n\n- Add `app` build target\n\n## [1.14.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.14.1) - 2023-04-23 ## {: #hatchling-v1.14.1 }\n\n***Fixed:***\n\n- Fix internal archive root naming for the `sdist` target when `strict-naming` is disabled to match the file name in order to support the expectation of some frontends\n\n## [1.14.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.14.0) - 2023-04-02 ## {: #hatchling-v1.14.0 }\n\n***Added:***\n\n- Add `trove-classifiers` as a dependency\n\n***Fixed:***\n\n- Properly normalize metadata descriptions that contain line breaks\n\n## [1.13.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.13.0) - 2023-02-09 ## {: #hatchling-v1.13.0 }\n\n***Added:***\n\n- Update the set of known trove classifiers to version 2023.2.8\n\n## [1.12.2](https://github.com/pypa/hatch/releases/tag/hatchling-v1.12.2) - 2023-01-05 ## {: #hatchling-v1.12.2 }\n\n***Fixed:***\n\n- Add `macos-max-compat` option to the `wheel` target that is enabled by default to support the latest version 22.0 of the `packaging` library\n\n## [1.12.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.12.1) - 2022-12-31 ## {: #hatchling-v1.12.1 }\n\n***Fixed:***\n\n- Fix minor regression in the PEP 517/660 function signatures that was discovered by Fedora\n\n## [1.12.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.12.0) - 2022-12-30 ## {: #hatchling-v1.12.0 }\n\n***Added:***\n\n- Improve readability of exceptions\n- Add `extra_metadata` build data to the `wheel` target\n- Retroactively support `License-Expression` core metadata starting at version 2.1\n- Add more type hints\n- Update the set of known trove classifiers to version 2022.12.22\n- Update SPDX license information to version 3.19\n- Store Hatchling's metadata in `pyproject.toml`\n\n***Fixed:***\n\n- Acknowledge the `ARCHFLAGS` environment variable on macOS for the `wheel` target when build hooks set the `infer_tag` build data to `true`\n- Fix dependency checking when encountering broken distributions\n- Fix the `support-legacy` option for the `sdist` target when using a src-layout project structure\n- Remove unnecessary encoding declaration in the default template for the `version` build hook\n\n## [1.11.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.11.1) - 2022-10-19 ## {: #hatchling-v1.11.1 }\n\n***Fixed:***\n\n- Fix default file selection behavior of the `wheel` target when there is a single top-level module\n\n## [1.11.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.11.0) - 2022-10-08 ## {: #hatchling-v1.11.0 }\n\n***Added:***\n\n- Add `env` version source to retrieve the version from an environment variable\n- Add `validate-bump` option to the `standard` version scheme\n\n***Fixed:***\n\n- Use proper CSV formatting for the `RECORD` metadata file of the `wheel` target to avoid warnings during installation by `pip` if, for example, file names contain commas\n- Fix installations with pip for build hooks that modify runtime dependencies\n- Decreasing verbosity now has no affect on output that should always be displayed\n\n## [1.10.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.10.0) - 2022-09-18 ## {: #hatchling-v1.10.0 }\n\n***Added:***\n\n- Add the following to the list of directories that cannot be traversed: `__pypackages__`, `.hg`, `.hatch`, `.tox`, `.nox`\n- Add deprecated option to allow ambiguous features\n\n***Fixed:***\n\n- Improve tracking of dynamic metadata\n- Fix core metadata for entries in `project.optional-dependencies` that use direct references\n\n## [1.9.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.9.0) - 2022-09-09 ## {: #hatchling-v1.9.0 }\n\n***Changed:***\n\n- File pattern matching now more closely resembles Git's behavior\n\n***Added:***\n\n- Implement a minimal version of `prepare_metadata_for_build_wheel` and `prepare_metadata_for_build_editable` for non-frontend tools that only need to inspect a project's metadata\n- Add `metadata` command to view PEP 621 project metadata\n- Improve error messages for SPDX license errors\n- Retroactively support `License-File` for core metadata starting at version 2.1\n- Bump the minimum supported version of `pathspec` to 0.10.1\n\n***Fixed:***\n\n- Allow the valid non-SPDX `license` values `LicenseRef-Public-Domain` and `LicenseRef-Proprietary`\n- Show the help text of the CLI when no subcommand is selected\n\n## [1.8.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.8.1) - 2022-08-25 ## {: #hatchling-v1.8.1 }\n\n***Fixed:***\n\n- Fix default file inclusion for `wheel` build targets when both the project name and package directory name are not normalized\n\n## [1.8.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.8.0) - 2022-08-16 ## {: #hatchling-v1.8.0 }\n\n***Added:***\n\n- Add `get_known_classifiers` method to metadata hooks\n\n***Fixed:***\n\n- Fix check for updating static versions with the `version` command when metadata hooks are in use\n\n## [1.7.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.7.1) - 2022-08-13 ## {: #hatchling-v1.7.1 }\n\n***Fixed:***\n\n- Fix the value of the `relative_path` attribute of included files, that some build plugins may use, when selecting explicit paths\n\n## [1.7.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.7.0) - 2022-08-12 ## {: #hatchling-v1.7.0 }\n\n***Added:***\n\n- Add `require-runtime-features` option for builders and build hooks\n- Check for unknown trove classifiers\n- Update SPDX license information to version 3.18\n\n***Fixed:***\n\n- Add better error message for `wheel` target dev mode installations that define path rewrites with the `sources` option\n- Note the `allow-direct-references` option in the relevant error messages\n\n## [1.6.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.6.0) - 2022-07-23 ## {: #hatchling-v1.6.0 }\n\n***Changed:***\n\n- When no build targets are specified on the command line, now default to `sdist` and `wheel` targets rather than what happens to be defined in config\n- The `code` version source now only supports files with known extensions\n- Global build hooks now run before target-specific build hooks to better match expected behavior\n\n***Added:***\n\n- The `code` version source now supports loading extension modules\n- Add `search-paths` option for the `code` version source\n\n***Fixed:***\n\n- Fix removing `sources` using an empty string value in the mapping\n- The `strict-naming` option now also applies to the metadata directory of `wheel` targets\n\n## [1.5.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.5.0) - 2022-07-11 ## {: #hatchling-v1.5.0 }\n\n***Added:***\n\n- Support the final draft of PEP 639\n- Add `strict-naming` option for `sdist` and `wheel` targets\n\n***Fixed:***\n\n- Project names are now stored in `sdist` and `wheel` target core metadata exactly as defined in `pyproject.toml` without normalization to allow control of how PyPI displays them\n\n## [1.4.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.4.1) - 2022-07-04 ## {: #hatchling-v1.4.1 }\n\n***Fixed:***\n\n- Fix forced inclusion of important files like licenses for `sdist` targets when using the explicit selection options\n- Don't sort project URL metadata so that the rendered order on PyPI can be controlled\n\n## [1.4.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.4.0) - 2022-07-03 ## {: #hatchling-v1.4.0 }\n\n***Changed:***\n\n- The `packages` option uses the new `only-include` option to provide targeted inclusion, since that is desired most of the time. You can retain the old behavior by using the `include` and `sources` options together.\n\n***Added:***\n\n- Support PEP 561 type hinting\n- Add `version` build hook\n- Add `only-include` option\n- The `editable` version of `wheel` targets now respects the `force-include` option by default\n- The `force-include` option now supports path rewriting with the `sources` option\n- The `wheel` target `shared-data` and `extra-metadata` options now respect file selection options\n- The `wheel` target now auto-detects single module layouts\n- Improve performance by never entering directories that are guaranteed to be undesirable like `__pycache__` rather than excluding individual files within\n- Update SPDX license information to version 3.17\n\n***Fixed:***\n\n- Don't write empty entry points file for `wheel` targets if there are no entry points defined\n- Allow metadata hooks to set the `version` in all cases\n- Prevent duplicate file entries from inclusion when using the `force-include` option\n\n## [1.3.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.3.1) - 2022-05-30 ## {: #hatchling-v1.3.1 }\n\n***Fixed:***\n\n- Better populate global variables for the `code` version source\n\n## [1.3.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.3.0) - 2022-05-22 ## {: #hatchling-v1.3.0 }\n\n***Removed:***\n\n- Remove unused global `args` context string formatting field\n\n***Added:***\n\n- Improve error messages for the `env` context string formatting field\n\n***Fixed:***\n\n- Fix `uri` context string formatting modifier on Windows\n\n## [1.2.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.2.0) - 2022-05-20 ## {: #hatchling-v1.2.0 }\n\n***Added:***\n\n- Allow context formatting for `project.dependencies` and `project.optional-dependencies`\n\n## [1.1.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.1.0) - 2022-05-19 ## {: #hatchling-v1.1.0 }\n\n***Added:***\n\n- Add `uri` and `real` context string formatting modifiers for file system paths\n\n## [1.0.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.0.0) - 2022-05-17 ## {: #hatchling-v1.0.0 }\n\n***Changed:***\n\n- Drop support for Python 2\n\n***Added:***\n\n- Improve error messaging for invalid versions\n- Update project metadata to reflect support for Python 3.11\n\n## [0.25.1](https://github.com/pypa/hatch/releases/tag/hatchling-v0.25.1) - 2022-06-14 ## {: #hatchling-v0.25.1 }\n\n***Fixed:***\n\n- Fix support for Windows on Python 2 by removing its support for symlinks\n\n## [0.25.0](https://github.com/pypa/hatch/releases/tag/hatchling-v0.25.0) - 2022-05-15 ## {: #hatchling-v0.25.0 }\n\n***Added:***\n\n- Add `skip-excluded-dirs` build option\n- Allow build data to add additional project dependencies for `wheel` and `sdist` build targets\n- Add `force_include_editable` build data for the `wheel` build target\n- Add `build_hooks` build data\n- Add support for Mercurial's `.hgignore` files when using glob syntax\n- Update project metadata to reflect the adoption by PyPA\n\n***Fixed:***\n\n- Properly use underscores for the name of `force_include` build data\n- No longer greedily skip excluded directories by default\n\n## [0.24.0](https://github.com/pypa/hatch/releases/tag/hatchling-v0.24.0) - 2022-04-28 ## {: #hatchling-v0.24.0 }\n\nThis is the initial public release of the Hatchling build system. Support for Python 2 will be dropped in version 1.\n"
  },
  {
    "path": "docs/how-to/config/dynamic-metadata.md",
    "content": "# How to configure custom dynamic metadata\n\n----\n\nIf you have [project metadata](../../config/metadata.md) that is not appropriate for static entry into `pyproject.toml` you will need to provide a [custom metadata hook](../../plugins/metadata-hook/custom.md) to apply such data during builds.\n\n!!! abstract \"Alternatives\"\n    Dynamic metadata is a way to have a single source of truth that will be available at build time and at run time. Another way to achieve that is to enter the build data statically and then look up the same information dynamically in the program or package, using [importlib.metadata](https://docs.python.org/3/library/importlib.metadata.html#module-importlib.metadata).\n\n    If the [version field](../../config/metadata.md#version) is the only metadata of concern, Hatchling provides a few built-in ways such as the [`regex` version source](../../plugins/version-source/regex.md) and also [third-party plugins](../../plugins/version-source/reference.md). The approach here will also work, but is more complex.\n\n## Update project metadata\n\nChange the `[project]` section of `pyproject.toml`:\n\n1. Define the [dynamic field](../../config/metadata.md#dynamic) as an array of all the fields you will set dynamically e.g. `dynamic = [\"version\", \"license\", \"authors\", \"maintainers\"]`\n2. If any of those fields have static definitions in `pyproject.toml`, delete those definitions. It is verboten to define a field statically and dynamically.\n\nAdd a section to trigger loading of dynamic metadata plugins: `[tool.hatch.metadata.hooks.custom]`. Use exactly that name, regardless of the name of the class you will use or its `PLUGIN_NAME`. There doesn't need to be anything in the section.\n\nIf your plugin requires additional third-party packages to do its work, add them to the `requires` array in the `[build-system]` section of `pyproject.toml`.\n\n## Implement hook\n\nThe dynamic lookup must happen in a custom plugin that you write. The [default expectation](../../plugins/metadata-hook/custom.md#options) is that it is in a `hatch_build.py` file at the root of the project. Subclass `MetadataHookInterface` and implement `update()`; for example, here's plugin that reads metadata from a JSON file:\n\n```python tab=\"hatch_build.py\"\nimport json\nimport os\n\nfrom hatchling.metadata.plugin.interface import MetadataHookInterface\n\n\nclass JSONMetaDataHook(MetadataHookInterface):\n    def update(self, metadata):\n        src_file = os.path.join(self.root, \"gnumeric\", \".constants.json\")\n        with open(src_file) as src:\n            constants = json.load(src)\n            metadata[\"version\"] = constants[\"__version__\"]\n            metadata[\"license\"] = constants[\"__license__\"]\n            metadata[\"authors\"] = [\n                {\"name\": constants[\"__author__\"], \"email\": constants[\"__author_email__\"]},\n            ]\n```\n\n1. You must import the [MetadataHookInterface](../../plugins/metadata-hook/reference.md#hatchling.metadata.plugin.interface.MetadataHookInterface) to subclass it.\n2. Do your operations inside the [`update`](../../plugins/metadata-hook/reference.md#hatchling.metadata.plugin.interface.MetadataHookInterface.update) method.\n3. `metadata` refers to [project metadata](../../config/metadata.md).\n4. When writing to metadata, use `list` for TOML arrays. Note that if a list is expected, it is required even if there is a single element.\n5. Use `dict` for TOML tables e.g. `authors`.\n\nIf you want to store the hook in a different location, set the [`path` option](../../plugins/metadata-hook/custom.md#options):\n\n```toml config-example\n[tool.hatch.metadata.hooks.custom]\npath = \"some/where.py\"\n```\n"
  },
  {
    "path": "docs/how-to/environment/dependency-resolution.md",
    "content": "# How to configure dependency resolution\n\n-----\n\nMost Hatch environment types, like the default [virtual](../../plugins/environment/virtual.md), simply use [pip](https://github.com/pypa/pip) to install dependencies. Therefore, you can use the standard [environment variables](https://pip.pypa.io/en/stable/topics/configuration/#environment-variables) that influence `pip`'s behavior.\n\nHere's an example of setting up the [default](../../config/environment/overview.md#inheritance) environment to look at 2 private indices (using [context formatting](../../config/context.md#environment-variables) for authentication) before finally falling back to PyPI:\n\n```toml config-example\n[tool.hatch.envs.default.env-vars]\nPIP_INDEX_URL = \"https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group1_path>/-/packages/pypi/simple/\"\nPIP_EXTRA_INDEX_URL = \"https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group2_path>/-/packages/pypi/simple/ https://pypi.org/simple/\"\n```\n\n## UV\n\nIf you're [using UV](select-installer.md), a different set of [environment variables](https://github.com/astral-sh/uv/tree/0.1.35#environment-variables) are available to configure its behavior. The previous example would look like this instead:\n\n```toml config-example\n[tool.hatch.envs.default.env-vars]\nUV_INDEX = \"https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group1_path>/-/packages/pypi/simple/ https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group2_path>/-/packages/pypi/simple/\"\nUV_DEFAULT_INDEX = \"https://pypi.org/simple/\"\n```\n\n!!! tip\n    If you need precise control over the prioritization of package indices, then using UV is recommended because `pip` has no [index order guarantee](https://github.com/pypa/pip/issues/8606).\n"
  },
  {
    "path": "docs/how-to/environment/select-installer.md",
    "content": "# How to select the installer\n\n-----\n\n## Enabling UV\n\nThe [virtual](../../plugins/environment/virtual.md) environment type by default uses [virtualenv](https://github.com/pypa/virtualenv) for virtual environment creation and [pip](https://github.com/pypa/pip) to install dependencies. You can speed up environment creation and dependency resolution by using [UV](https://github.com/astral-sh/uv) instead of both of those tools.\n\n!!! warning \"caveat\"\n    UV is under active development and may not work for all dependencies.\n\nTo do so, set the `installer` [option](../../plugins/environment/virtual.md#options) to `uv`. For example, if you wanted to enable this functionality for the [default](../../config/environment/overview.md#inheritance) environment, you could set the following:\n\n```toml config-example\n[tool.hatch.envs.default]\ninstaller = \"uv\"\n```\n\n!!! tip\n    All environments that enable UV will have the path to UV available as the `HATCH_UV` environment variable.\n\n## Configuring the version\n\nThe UV that is shared by all environments uses a specific version range that is known to work with Hatch. If you want to use a different version, you can override the [dependencies](../../config/environment/overview.md#dependencies) for the internal `hatch-uv` environment:\n\n```toml config-example\n[tool.hatch.envs.hatch-uv]\ndependencies = [\n  \"uv>9000\",\n]\n```\n\n## Externally managed\n\nIf you want to manage UV yourself, you can expose it to Hatch by setting the `HATCH_ENV_TYPE_VIRTUAL_UV_PATH` environment variable which should be the absolute path to a UV binary for Hatch to use instead. This implicitly [enables UV](#enabling-uv).\n\n## Installer script alias\n\nIf you have [scripts](../../config/environment/overview.md#scripts) or [commands](../../config/environment/overview.md#commands) that call `pip`, it may be useful to alias the `uv pip` command to `pip` so that you can use the same commands for both methods of configuration and retain your muscle memory. The following is an example of a matrix that [conditionally](../../config/environment/advanced.md#option-overrides) enables UV and sets the alias:\n\n```toml config-example\n[[tool.hatch.envs.example.matrix]]\ntool = [\"uv\", \"pip\"]\n\n[tool.hatch.envs.example.overrides]\nmatrix.tool.installer = { value = \"{matrix:tool}\" }\nmatrix.tool.scripts = [\n  { key = \"pip\", value = \"{env:HATCH_UV} pip {args}\", if = [\"uv\"] },\n]\n```\n\nAnother common use case is to expose UV to all [test environments](../../config/internal/testing.md). In this case, you often wouldn't want to modify the `scripts` mapping directly but rather add an [extra script](../../config/environment/overview.md#extra-scripts):\n\n```toml config-example\n[tool.hatch.envs.hatch-test.extra-scripts]\npip = \"{env:HATCH_UV} pip {args}\"\n```\n"
  },
  {
    "path": "docs/how-to/environment/workspace.md",
    "content": "# How to configure workspace environments\n\n-----\n\nWorkspace environments allow you to manage multiple related packages within a single environment. This is useful for monorepos or projects with multiple interdependent packages.\n\n## Basic workspace configuration\n\nDefine workspace members in your environment configuration using the `workspace.members` option:\n\n```toml config-example\n[tool.hatch.envs.default]\nworkspace.members = [\n  \"packages/core\",\n  \"packages/utils\",\n  \"packages/cli\"\n]\n```\n\nWorkspace members are automatically installed as editable packages in the environment.\n\n## Pattern matching\n\nUse glob patterns to automatically discover workspace members:\n\n```toml config-example\n[tool.hatch.envs.default]\nworkspace.members = [\"packages/*\"]\n```\n\n## Excluding members\n\nExclude specific packages from workspace discovery:\n\n```toml config-example\n[tool.hatch.envs.default]\nworkspace.members = [\"packages/*\"]\nworkspace.exclude = [\"packages/experimental*\"]\n```\n\n## Member-specific features\n\nInstall specific optional dependencies for workspace members:\n\n```toml config-example\n[tool.hatch.envs.default]\nworkspace.members = [\n  {path = \"packages/core\", features = [\"dev\"]},\n  {path = \"packages/utils\", features = [\"test\", \"docs\"]},\n  \"packages/cli\"\n]\n```\n\n## Environment-specific workspaces\n\nDifferent environments can include different workspace members:\n\n```toml config-example\n[tool.hatch.envs.unit-tests]\nworkspace.members = [\"packages/core\", \"packages/utils\"]\nscripts.test = \"pytest tests/unit\"\n\n[tool.hatch.envs.integration-tests]\nworkspace.members = [\"packages/*\"]\nscripts.test = \"pytest tests/integration\"\n\n[tool.hatch.envs.docs]\nworkspace.members = [\n  {path = \"packages/core\", features = [\"docs\"]},\n  {path = \"packages/utils\", features = [\"docs\"]}\n]\n```\n\n## Test matrices with workspaces\n\nCombine workspace configuration with test matrices:\n\n```toml config-example\n[[tool.hatch.envs.test.matrix]]\npython = [\"3.9\", \"3.10\", \"3.11\", \"3.12\"]\n\n[tool.hatch.envs.test]\nworkspace.members = [\"packages/*\"]\ndependencies = [\"pytest\", \"coverage\"]\nscripts.test = \"pytest {args}\"\n\n[[tool.hatch.envs.test-core.matrix]]\npython = [\"3.9\", \"3.10\", \"3.11\", \"3.12\"]\n\n[tool.hatch.envs.test-core]\nworkspace.members = [\"packages/core\"]\ndependencies = [\"pytest\", \"coverage\"]\nscripts.test = \"pytest packages/core/tests {args}\"\n```\n\n## Performance optimization\n\nEnable parallel dependency resolution for faster environment setup:\n\n```toml config-example\n[tool.hatch.envs.default]\nworkspace.members = [\"packages/*\"]\nworkspace.parallel = true\n```\n\n## Monorepo example\n\nComplete configuration for a typical monorepo structure:\n\n```toml config-example\n# Root pyproject.toml\n[project]\nname = \"my-monorepo\"\nversion = \"1.0.0\"\n\n[tool.hatch.envs.default]\nworkspace.members = [\"packages/*\"]\nworkspace.exclude = [\"packages/experimental*\"]\nworkspace.parallel = true\ndependencies = [\"pytest\", \"black\", \"ruff\"]\n\n[tool.hatch.envs.test]\nworkspace.members = [\n  {path = \"packages/core\", features = [\"test\"]},\n  {path = \"packages/utils\", features = [\"test\"]},\n  \"packages/cli\"\n]\ndependencies = [\"pytest\", \"coverage\", \"pytest-cov\"]\nscripts.test = \"pytest --cov {args}\"\n\n[tool.hatch.envs.lint]\ndetached = true\nworkspace.members = [\"packages/*\"]\ndependencies = [\"ruff\", \"black\", \"mypy\"]\nscripts.check = [\"ruff check .\", \"black --check .\", \"mypy .\"]\nscripts.fmt = [\"ruff check --fix .\", \"black .\"]\n\n[[tool.hatch.envs.ci.matrix]]\npython = [\"3.9\", \"3.10\", \"3.11\", \"3.12\"]\n\n[tool.hatch.envs.ci]\ntemplate = \"test\"\nworkspace.parallel = false  # Disable for CI stability\n```\n\n## Library with plugins example\n\nConfiguration for a library with optional plugins:\n\n```toml config-example\n[tool.hatch.envs.default]\nworkspace.members = [\"core\"]\ndependencies = [\"pytest\"]\n\n[tool.hatch.envs.full]\nworkspace.members = [\n  \"core\",\n  \"plugins/database\",\n  \"plugins/cache\",\n  \"plugins/auth\"\n]\ndependencies = [\"pytest\", \"pytest-asyncio\"]\n\n[tool.hatch.envs.database-only]\nworkspace.members = [\n  \"core\",\n  {path = \"plugins/database\", features = [\"postgresql\", \"mysql\"]}\n]\n\n[[tool.hatch.envs.plugin-test.matrix]]\nplugin = [\"database\", \"cache\", \"auth\"]\n\n[tool.hatch.envs.plugin-test]\nworkspace.members = [\n  \"core\",\n  \"plugins/{matrix:plugin}\"\n]\nscripts.test = \"pytest plugins/{matrix:plugin}/tests {args}\"\n```\n"
  },
  {
    "path": "docs/how-to/integrate/vscode.md",
    "content": "# How to use Hatch environments from Visual Studio Code\n\n-----\n\nVisual Studio Code announced support for [Hatch environment discovery](https://code.visualstudio.com/updates/v1_88#_hatch-environment-discovery) in `vscode-python`'s [2024.4 release](https://github.com/microsoft/vscode-python/releases/tag/v2024.4.0).\n\nFor it to work, you should [install Hatch](../../install.md) globally. If you used the GUI installers on Windows or macOS, or your system package manager on e.g. Arch Linux or Fedora, this should be taken care of.\n\n??? note \"Setting up PATH\"\n\n    If you installed Hatch with [pipx](../../install.md#pipx) rather than system-wide, you might need to add `$HOME/.local/bin` to your PATH environment variable *for your graphical session*, not just your terminal. Check like this:\n\n    ```console\n    $ pgrep bin/code  # or some other graphical application\n    1234\n    $ cat /proc/1234/environ | tr '\\0' '\\n' | grep -E '^PATH='\n    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n    ```\n\n    If the directory is not in there, you need to add it in your session startup script, in a way that depends on your desktop environment:\n\n    - [KDE Plasma](https://userbase.kde.org/Session_Environment_Variables)\n    - [GNOME](https://help.ubuntu.com/community/EnvironmentVariables#Session-wide_environment_variables)\n\n## Project setup\n\n1. Make Hatch install the project and its dependencies to an environment using the [`env create`](../../cli/reference.md#hatch-env-create) command.\n\n2. Select an interpreter using the ++\"Python: Select Interpreter\"++ command:\n\n     <figure markdown>\n         ![Select interpreter](./vscode/select-interpreter.png){ loading=lazy role=\"img\" }\n     </figure>\n\n3. You should now be able to use the environment. For example, if you have the `python.terminal.activateEnvironment` setting set to `true` and you open a new terminal, the environment should be activated. Alternatively, you could press the \"play\" button to run a file in the environment:\n\n     <figure markdown>\n         ![Run file](./vscode/run-file.png){ loading=lazy role=\"img\" }\n     </figure>\n"
  },
  {
    "path": "docs/how-to/meta/report-issues.md",
    "content": "# How to report issues\n\n-----\n\nAll reports regarding unexpected behavior should be generated with the [`self report`](../../cli/reference.md#hatch-self-report) command:\n\n```\n$ hatch self report\n```\n\nBy default, this will open a new tab in your default browser with pre-populated information about your environment.\n\nIf Hatch is not installed alongside a web browser, you may also pass the `--no-open`/`-n` command which will output the URL with correct parameters for copying elsewhere:\n\n```\n$ hatch self report -n\nhttps://github.com/pypa/hatch/issues/new?body=%23%23+Current+behavior%0A%3C%21--+A+clear+and+concise+description+of+the+behavior.+--%3E%0A%0A%23%23+Expected+behavior%0A%3C%21--+A+clear+and+concise+description+of+what+you+expected+to+happen.+--%3E%0A%0A%23%23+Additional+context%0A%3C%21--+Add+any+other+context+about+the+problem+here.+If+applicable%2C+add+screenshots+to+help+explain.+--%3E%0A%0A%23%23+Debug%0A%0A%23%23%23+Installation%0A%0A-+Source%3A+pip%0A-+Version%3A+1.9.2.dev5%0A-+Platform%3A+Windows%0A-+Python+version%3A%0A++++%60%60%60%0A++++3.11.1+%28tags%2Fv3.11.1%3Aa7a450f%2C+Dec++6+2022%2C+19%3A58%3A39%29+%5BMSC+v.1934+64+bit+%28AMD64%29%5D%0A++++%60%60%60%0A%0A%23%23%23+Configuration%0A%0A%60%60%60toml%0Amode+%3D+%22local%22%0Ashell+%3D+%22nu%22%0A%60%60%60%0A\n```\n"
  },
  {
    "path": "docs/how-to/plugins/testing-builds.md",
    "content": "# Testing build plugins\n\n-----\n\nFor testing [Hatchling plugins](../../plugins/about.md#hatchling), you'll usually want to generate a project to execute builds as a real user would. For example, as a minimal [pytest](https://github.com/pytest-dev/pytest) fixture:\n\n```python\nfrom pathlib import Path\n\nimport pytest\n\n\n@pytest.fixture\ndef new_project(tmp_path):\n    project_dir = tmp_path / 'my-app'\n    project_dir.mkdir()\n\n    project_file = project_dir / 'pyproject.toml'\n    project_file.write_text(\n        f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\", \"hatch-plugin-name @ {Path.cwd().as_uri()}\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"my-app\"\nversion = \"0.1.0\"\n\"\"\",\n        encoding='utf-8',\n    )\n    ...\n```\n\nThe issue with this is that after the first test session, the project will be forever cached by pip based on the file path. Therefore, subsequent tests runs will never use updated code.\n\nTo invalidate the cache, copy your code to a new path for every test session:\n\n```python\nimport shutil\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\n\nimport pytest\n\n\n@pytest.fixture(scope='session')\ndef plugin_dir():\n    with TemporaryDirectory() as d:\n        directory = Path(d, 'plugin')\n        shutil.copytree(\n            Path.cwd(), directory, ignore=shutil.ignore_patterns('.git')\n        )\n\n        yield directory.resolve()\n\n\n@pytest.fixture\ndef new_project(tmp_path, plugin_dir):\n    project_dir = tmp_path / 'my-app'\n    project_dir.mkdir()\n\n    project_file = project_dir / 'pyproject.toml'\n    project_file.write_text(\n        f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\", \"hatch-plugin-name @ {plugin_dir.as_uri()}\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"my-app\"\nversion = \"0.1.0\"\n\"\"\",\n        encoding='utf-8',\n    )\n    ...\n```\n\n!!! note\n    This example chooses to ignore copying `.git` for performance reasons. You may want to ignore more patterns, or copy only specific paths like [this plugin](https://github.com/hynek/hatch-fancy-pypi-readme/blob/main/tests/conftest.py) does.\n"
  },
  {
    "path": "docs/how-to/publish/auth.md",
    "content": "# How to authenticate for index publishing\n\n----\n\nThe username is derived from the following sources, in order of precedence:\n\n1. The  `--user` / `-u` cli option.\n2. The `HATCH_INDEX_USER` environment variable.\n3. The [`repos` tables](../../plugins/publisher/package-index.md).\n4. The [`~/.pypirc` file](https://packaging.python.org/en/latest/specifications/pypirc/).\n5. The input to an interactive prompt.\n\nAs a fallback the value `__token__` is applied.\n\nThe password is looked up in these:\n\n1. The [`~/.pypirc` file](https://packaging.python.org/en/latest/specifications/pypirc/)\n   if the username was provided by it.\n2. The `--auth` / `-a` cli option.\n3. The `HATCH_INDEX_AUTH` environment variable.\n4. The [`repos` tables](../../plugins/publisher/package-index.md).\n5. A variety of OS-level credentials services backed by [keyring](https://github.com/jaraco/keyring).\n6. The input to an interactive prompt.\n\nIf interactively provided credentials were used, the username will be stored in\n[Hatch's cache](../../config/hatch.md#cache) and the password stored in the available\n[keyring](https://github.com/jaraco/keyring) backed credentials stores.\n\nFor automated releasing to PyPI, it is recommended to use [\"Trusted Publishing\" with OIDC](https://docs.pypi.org/trusted-publishers/)\n(e.g. PyPA's [`pypi-publish`](https://github.com/pypa/gh-action-pypi-publish) GitHub Action)\nor per-project [API tokens](https://pypi.org/help/#apitoken).\n"
  },
  {
    "path": "docs/how-to/publish/repo.md",
    "content": "# How to configure repositories for index publishing\n\n----\n\nYou can select the repository with which to upload using the `-r`/`--repo` option or by setting the `HATCH_INDEX_REPO` environment variable.\n\nRather than specifying the full URL of a repository, you can use a named repository from a `publish.index.repos` table defined in Hatch's [config file](../../config/hatch.md):\n\n```toml tab=\"config.toml\"\n[publish.index.repos.private]\nurl = \"...\"\n...\n```\n\nThe following repository names are reserved by Hatch and cannot be overridden:\n\n| Name | Repository |\n| --- | --- |\n| `main` | https://upload.pypi.org/legacy/ |\n| `test` | https://test.pypi.org/legacy/ |\n\nThe `main` repository is used by default.\n"
  },
  {
    "path": "docs/how-to/python/custom.md",
    "content": "# How to use custom Python distributions\n\n----\n\nThe built-in [Python management](../../tutorials/python/manage.md) capabilities offer full support for using custom distributions.\n\n## Configuration\n\nConfiguring custom Python distributions is done entirely through three environment variables that must all be defined, for each desired distribution. In the following sections, the placeholder `<NAME>` is the uppercased version of the distribution name with periods replaced by underscores e.g. `pypy3.10` would become `PYPY3_10`.\n\n### Source\n\nThe `HATCH_PYTHON_CUSTOM_SOURCE_<NAME>` variable is the URL to the distribution's archive. The value must end with the archive's real file extension, which is used to determine the extraction method.\n\nThe following extensions are supported:\n\n| Extensions | Description |\n| --- | --- |\n| <ul><li><code>.tar.bz2</code></li><li><code>.bz2</code></li></ul> | A [tar file](https://en.wikipedia.org/wiki/Tar_(computing)) with [bzip2 compression](https://en.wikipedia.org/wiki/Bzip2) |\n| <ul><li><code>.tar.gz</code></li><li><code>.tgz</code></li></ul> | A [tar file](https://en.wikipedia.org/wiki/Tar_(computing)) with [gzip compression](https://en.wikipedia.org/wiki/Gzip) |\n| <ul><li><code>.tar.zst</code></li><li><code>.tar.zstd</code></li></ul> | A [tar file](https://en.wikipedia.org/wiki/Tar_(computing)) with [Zstandard compression](https://en.wikipedia.org/wiki/Zstd) |\n| <ul><li><code>.zip</code></li></ul> | A [ZIP file](https://en.wikipedia.org/wiki/ZIP_(file_format)) with [DEFLATE compression](https://en.wikipedia.org/wiki/Deflate) |\n\n### Python path\n\nThe `HATCH_PYTHON_CUSTOM_PATH_<NAME>` variable is the path to the Python interpreter within the archive. This path is relative to the root of the archive and must be a Unix-style path, even on Windows.\n\n### Version\n\nThe `HATCH_PYTHON_CUSTOM_VERSION_<NAME>` variable is the version of the distribution. This value is used to determine whether updates are required and is displayed in the output of the [`python show`](../../cli/reference.md#hatch-python-show) command.\n"
  },
  {
    "path": "docs/how-to/run/python-scripts.md",
    "content": "# How to run Python scripts\n\n-----\n\nThe [`run`](../../cli/reference.md#hatch-run) command supports executing Python scripts with [inline metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/), such that a dedicated [environment](../../config/environment/overview.md) is automatically created with the required dependencies and with the correct version of Python.\n\nA script metadata block is a comment block that starts with `# /// script` and ends with `# ///`. Every line between those two lines must be a comment line that starts with `#` and contains a [TOML](https://github.com/toml-lang/toml) document when the comment characters are removed.\n\nThe top-level fields are:\n\n- `dependencies`: A list of strings that specifies the runtime dependencies of the script. Each entry must be a valid [dependency specifier](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#dependency-specifiers).\n- `requires-python`: A string that specifies the Python version(s) with which the script is compatible. The value of this field must be a valid [version specifier](https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers).\n\nThe following is an example of Python script with a valid metadata block:\n\n```python tab=\"script.py\"\n# /// script\n# requires-python = \">=3.11\"\n# dependencies = [\n#   \"httpx\",\n#   \"rich\",\n# ]\n# ///\n\nimport httpx\nfrom rich.pretty import pprint\n\nresp = httpx.get(\"https://peps.python.org/api/peps.json\")\ndata = resp.json()\npprint([(k, v[\"title\"]) for k, v in data.items()][:10])\n```\n\nRun it directly:\n\n```\n$ hatch run /path/to/script.py\nCreating environment: SyB4bPbL\nChecking dependencies\nSyncing dependencies\n[\n│   ('1', 'PEP Purpose and Guidelines'),\n│   ('2', 'Procedure for Adding New Modules'),\n│   ('3', 'Guidelines for Handling Bug Reports'),\n│   ('4', 'Deprecation of Standard Modules'),\n│   ('5', 'Guidelines for Language Evolution'),\n│   ('6', 'Bug Fix Releases'),\n│   ('7', 'Style Guide for C Code'),\n│   ('8', 'Style Guide for Python Code'),\n│   ('9', 'Sample Plaintext PEP Template'),\n│   ('10', 'Voting Guidelines')\n]\n```\n\n!!! note \"notes\"\n    - The informational text in this example is only temporarily shown in your terminal on the first run.\n    - Although the environment name is based on the script's absolute path, the command line argument does not have to be.\n\n## Environment configuration\n\nYou may use the `[tool.hatch]` table directly to control the script's [environment](../../config/environment/overview.md). For example, if you wanted to disable UV (which is [enabled](../environment/select-installer.md#enabling-uv) by default for scripts), you could add the following:\n\n```python tab=\"script.py\"\n# /// script\n# ...\n# [tool.hatch]\n# installer = \"pip\"\n# ///\n```\n"
  },
  {
    "path": "docs/how-to/static-analysis/behavior.md",
    "content": "# Customize static analysis behavior\n\n-----\n\nYou can [fully alter](../../config/internal/static-analysis.md#customize-behavior) the static analysis performed by the [`fmt`](../../cli/reference.md#hatch-fmt) command by modifying the reserved [environment](../../config/environment/overview.md) named `hatch-static-analysis`. For example, you could define the following if you wanted to replace the default behavior with a mix of [Black](https://github.com/psf/black), [isort](https://github.com/PyCQA/isort) and basic [flake8](https://github.com/PyCQA/flake8):\n\n```toml config-example\n[tool.hatch.envs.hatch-static-analysis]\ndependencies = [\"black\", \"flake8\", \"isort\"]\n\n[tool.hatch.envs.hatch-static-analysis.scripts]\nformat-check = [\n  \"black --check --diff {args:.}\",\n  \"isort --check-only --diff {args:.}\",\n]\nformat-fix = [\n  \"isort {args:.}\",\n  \"black {args:.}\",\n]\nlint-check = \"flake8 {args:.}\"\nlint-fix = \"lint-check\"\n```\n\nThe `format-*` scripts correspond to the `--formatter`/`-f` flag while the `lint-*` scripts correspond to the `--linter`/`-l` flag. The `*-fix` scripts run by default while the `*-check` scripts correspond to the `--check` flag. Based on this example, the following shows how the various scripts influence behavior:\n\n| Command | Expanded scripts |\n| --- | --- |\n| `hatch fmt` | <ul><li><code>flake8 .</code></li><li><code>isort .</code></li><li><code>black .</code></li></ul> |\n| `hatch fmt src tests` | <ul><li><code>flake8 src tests</code></li><li><code>isort src tests</code></li><li><code>black src tests</code></li></ul> |\n| `hatch fmt -f` | <ul><li><code>isort .</code></li><li><code>black .</code></li></ul> |\n| `hatch fmt -l` | <ul><li><code>flake8 .</code></li></ul> |\n| `hatch fmt --check` | <ul><li><code>flake8 .</code></li><li><code>black --check --diff .</code></li><li><code>isort --check-only --diff .</code></li></ul> |\n| `hatch fmt --check -f` | <ul><li><code>black --check --diff .</code></li><li><code>isort --check-only --diff .</code></li></ul> |\n| `hatch fmt --check -l` | <ul><li><code>flake8 .</code></li></ul> |\n"
  },
  {
    "path": "docs/index.md",
    "content": "# Hatch\n\n<div class=\"grid\" markdown>\n\n![Hatch logo](assets/images/logo.svg){ role=\"img\" }\n\n| | |\n| --- | --- |\n| CI/CD | [![CI - Test](https://github.com/pypa/hatch/actions/workflows/test.yml/badge.svg){ loading=lazy .off-glb }](https://github.com/pypa/hatch/actions/workflows/test.yml) [![CD - Build Hatch](https://github.com/pypa/hatch/actions/workflows/build-hatch.yml/badge.svg){ loading=lazy .off-glb }](https://github.com/pypa/hatch/actions/workflows/build-hatch.yml) [![CD - Build Hatchling](https://github.com/pypa/hatch/actions/workflows/build-hatchling.yml/badge.svg){ loading=lazy .off-glb }](https://github.com/pypa/hatch/actions/workflows/build-hatchling.yml) |\n| Docs | [![Docs - Release](https://github.com/pypa/hatch/actions/workflows/docs-release.yml/badge.svg){ loading=lazy .off-glb }](https://github.com/pypa/hatch/actions/workflows/docs-release.yml) [![Docs - Dev](https://github.com/pypa/hatch/actions/workflows/docs-dev.yml/badge.svg){ loading=lazy .off-glb }](https://github.com/pypa/hatch/actions/workflows/docs-dev.yml) |\n| Package | [![PyPI - Version](https://img.shields.io/pypi/v/hatch.svg?logo=pypi&label=PyPI&logoColor=gold){ loading=lazy .off-glb }](https://pypi.org/project/hatch/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/hatch.svg?logo=python&label=Python&logoColor=gold){ loading=lazy .off-glb }](https://pypi.org/project/hatch/) [![PyPI - Installs](https://img.shields.io/pypi/dm/hatchling.svg?color=blue&label=Installs&logo=pypi&logoColor=gold){ loading=lazy .off-glb }](https://pypi.org/project/hatch/) [![Release - Downloads](https://img.shields.io/github/downloads/pypa/hatch/total?label=Downloads){ loading=lazy .off-glb }](https://github.com/pypa/hatch/releases) |\n| Meta | [![Hatch project](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pypa/hatch/master/docs/assets/badge/v0.json){ loading=lazy .off-glb }](https://github.com/pypa/hatch) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json){ loading=lazy .off-glb }](https://github.com/astral-sh/ruff) [![types - Mypy](https://img.shields.io/badge/types-Mypy-blue.svg){ loading=lazy .off-glb }](https://github.com/python/mypy) [![License - MIT](https://img.shields.io/badge/license-MIT-9400d3.svg){ loading=lazy .off-glb }](https://spdx.org/licenses/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/ofek?logo=GitHub%20Sponsors&style=social){ loading=lazy .off-glb }](https://github.com/sponsors/ofek) |\n\n</div>\n\n-----\n\nHatch is a modern, extensible Python project manager. See the [Why Hatch?](why.md) page for more information.\n\n<div class=\"grid cards\" markdown>\n\n-   :material-hammer-wrench:{ .lg .middle } __Build system__\n\n    ---\n\n    Reproducible builds by default with a rich ecosystem of plugins\n\n    [:octicons-arrow-right-24: Configure builds](config/build.md#build-system)\n\n-   :material-lock:{ .lg .middle } __Environments__\n\n    ---\n\n    Robust environment management with support for custom scripts and UV\n\n    [:octicons-arrow-right-24: Getting started](environment.md)\n\n-   :material-language-python:{ .lg .middle } __Python management__\n\n    ---\n\n    Choose between easy manual installations or automatic as part of environments\n\n    [:octicons-arrow-right-24: Try it](tutorials/python/manage.md)\n\n-   :octicons-shield-check-24:{ .lg .middle } __Testing__\n\n    ---\n\n    Test execution with known best practices\n\n    [:octicons-arrow-right-24: Run](tutorials/testing/overview.md)\n\n-   :material-magnify-scan:{ .lg .middle } __Static analysis__\n\n    ---\n\n    Static analysis backed by Ruff with up-to-date, sane defaults\n\n    [:octicons-arrow-right-24: Learn](config/internal/static-analysis.md)\n\n-   :material-console-line:{ .lg .middle } __Script runner__\n\n    ---\n\n    Execute Python scripts with specific dependencies and Python versions\n\n    [:octicons-arrow-right-24: Execute](how-to/run/python-scripts.md)\n\n-   :material-publish:{ .lg .middle } __Publishing__\n\n    ---\n\n    Easily upload to PyPI or other indices\n\n    [:octicons-arrow-right-24: See how](publish.md)\n\n-   :octicons-number-24:{ .lg .middle } __Versioning__\n\n    ---\n\n    Streamlined workflow for bumping versions\n\n    [:octicons-arrow-right-24: Managing versions](version.md)\n\n-   :octicons-project-template-24:{ .lg .middle } __Project generation__\n\n    ---\n\n    Create new projects from templates with known best practices\n\n    [:octicons-arrow-right-24: Project setup](intro.md#setup)\n\n-   :material-speedometer:{ .lg .middle } __Responsive CLI__\n\n    ---\n\n    Hatch is up to 3x faster than equivalent tools\n\n    [:octicons-arrow-right-24: CLI reference](cli/about.md)\n\n</div>\n\n## License\n\nHatch is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n\n## Navigation\n\nDocumentation for specific `MAJOR.MINOR` versions can be chosen by using the dropdown on the top of every page. The `dev` version reflects changes that have not yet been released.\n\nAlso, desktop readers can use special keyboard shortcuts:\n\n| Keys | Action |\n| --- | --- |\n| <ul><li><kbd>,</kbd> (comma)</li><li><kbd>p</kbd></li></ul> | Navigate to the \"previous\" page |\n| <ul><li><kbd>.</kbd> (period)</li><li><kbd>n</kbd></li></ul> | Navigate to the \"next\" page |\n| <ul><li><kbd>/</kbd></li><li><kbd>s</kbd></li></ul> | Display the search modal |\n"
  },
  {
    "path": "docs/install.md",
    "content": "# Installation\n\n-----\n\n## GitHub Actions\n\n```yaml\n- name: Install Hatch\n  uses: pypa/hatch@install\n```\n\nRefer to the [official action](https://github.com/pypa/hatch/tree/install) for more information.\n\n## Installers\n\n=== \"macOS\"\n    === \"GUI installer\"\n        1. In your browser, download the `.pkg` file: [hatch-universal.pkg](https://github.com/pypa/hatch/releases/latest/download/hatch-universal.pkg)\n        2. Run your downloaded file and follow the on-screen instructions.\n        3. Restart your terminal.\n        4. To verify that the shell can find and run the `hatch` command in your `PATH`, use the following command.\n\n            ```\n            $ hatch --version\n            <HATCH_LATEST_VERSION>\n            ```\n    === \"Command line installer\"\n        1. Download the file using the `curl` command. The `-o` option specifies the file name that the downloaded package is written to. In this example, the file is written to `hatch-universal.pkg` in the current directory.\n\n            ```\n            curl -Lo hatch-universal.pkg https://github.com/pypa/hatch/releases/latest/download/hatch-universal.pkg\n            ```\n        2. Run the standard macOS [`installer`](https://ss64.com/osx/installer.html) program, specifying the downloaded `.pkg` file as the source. Use the `-pkg` parameter to specify the name of the package to install, and the `-target /` parameter for the drive in which to install the package. The files are installed to `/usr/local/hatch`, and an entry is created at `/etc/paths.d/hatch` that instructs shells to add the `/usr/local/hatch` directory to. You must include sudo on the command to grant write permissions to those folders.\n\n            ```\n            sudo installer -pkg ./hatch-universal.pkg -target /\n            ```\n        3. Restart your terminal.\n        4. To verify that the shell can find and run the `hatch` command in your `PATH`, use the following command.\n\n            ```\n            $ hatch --version\n            <HATCH_LATEST_VERSION>\n            ```\n\n=== \"Windows\"\n    === \"GUI installer\"\n        1. In your browser, download one the `.msi` files:\n              - [hatch-x64.msi](https://github.com/pypa/hatch/releases/latest/download/hatch-x64.msi)\n        2. Run your downloaded file and follow the on-screen instructions.\n        3. Restart your terminal.\n        4. To verify that the shell can find and run the `hatch` command in your `PATH`, use the following command.\n\n            ```\n            $ hatch --version\n            <HATCH_LATEST_VERSION>\n            ```\n    === \"Command line installer\"\n        1. Download and run the installer using the standard Windows [`msiexec`](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec) program, specifying one of the `.msi` files as the source. Use the `/passive` and `/i` parameters to request an unattended, normal installation.\n\n            === \"x64\"\n                ```\n                msiexec /passive /i https://github.com/pypa/hatch/releases/latest/download/hatch-x64.msi\n                ```\n            === \"x86\"\n                ```\n                msiexec /passive /i https://github.com/pypa/hatch/releases/latest/download/hatch-x86.msi\n                ```\n        2. Restart your terminal.\n        3. To verify that the shell can find and run the `hatch` command in your `PATH`, use the following command.\n\n            ```\n            $ hatch --version\n            <HATCH_LATEST_VERSION>\n            ```\n\n## Standalone binaries\n\nAfter downloading the archive corresponding to your platform and architecture, extract the binary to a directory that is on your PATH and rename to `hatch`.\n\n=== \"Linux\"\n    - [hatch-aarch64-unknown-linux-gnu.tar.gz](https://github.com/pypa/hatch/releases/latest/download/hatch-aarch64-unknown-linux-gnu.tar.gz)\n    - [hatch-x86_64-unknown-linux-gnu.tar.gz](https://github.com/pypa/hatch/releases/latest/download/hatch-x86_64-unknown-linux-gnu.tar.gz)\n    - [hatch-x86_64-unknown-linux-musl.tar.gz](https://github.com/pypa/hatch/releases/latest/download/hatch-x86_64-unknown-linux-musl.tar.gz)\n    - [hatch-powerpc64le-unknown-linux-gnu.tar.gz](https://github.com/pypa/hatch/releases/latest/download/hatch-powerpc64le-unknown-linux-gnu.tar.gz)\n\n=== \"macOS\"\n    - [hatch-aarch64-apple-darwin.tar.gz](https://github.com/pypa/hatch/releases/latest/download/hatch-aarch64-apple-darwin.tar.gz)\n    - [hatch-x86_64-apple-darwin.tar.gz](https://github.com/pypa/hatch/releases/latest/download/hatch-x86_64-apple-darwin.tar.gz)\n\n=== \"Windows\"\n    - [hatch-x86_64-pc-windows-msvc.zip](https://github.com/pypa/hatch/releases/latest/download/hatch-x86_64-pc-windows-msvc.zip)\n    - [hatch-i686-pc-windows-msvc.zip](https://github.com/pypa/hatch/releases/latest/download/hatch-i686-pc-windows-msvc.zip)\n\n## pip\n\nHatch is available on PyPI and can be installed with [pip](https://github.com/pypa/pip).\n\n```\npip install hatch\n```\n\n!!! warning\n    This method modifies the Python environment in which you choose to install. Consider instead using [pipx](#pipx) to avoid dependency conflicts.\n\n## pipx\n\n[pipx](https://github.com/pypa/pipx) allows for the global installation of Python applications in isolated environments.\n\n```\npipx install hatch\n```\n\n## Homebrew\n\nSee the [formula](https://formulae.brew.sh/formula/hatch) for more details.\n\n```\nbrew install hatch\n```\n\n## Conda\n\nSee the [feedstock](https://github.com/conda-forge/hatch-feedstock) for more details.\n\n```\nconda install -c conda-forge hatch\n```\n\nor with [mamba](https://github.com/mamba-org/mamba):\n\n```\nmamba install hatch\n```\n\n!!! warning\n    This method modifies the Conda environment in which you choose to install. Consider instead using [pipx](#pipx) or [condax](https://github.com/mariusvniekerk/condax) to avoid dependency conflicts.\n\n## MacPorts\n\nSee the [port](https://ports.macports.org/port/hatch/) for more details.\n\n```\nsudo port install hatch\n```\n\n## Fedora\n\nThe minimum supported version is 37, currently in development as [Rawhide](https://docs.fedoraproject.org/en-US/releases/rawhide/).\n\n```\nsudo dnf install hatch\n```\n\n## Void Linux\n\n```\nxbps-install hatch\n```\n\n## Build system availability\n\nHatchling is Hatch's [build backend](config/build.md#build-system) which you will never need to install manually. See its [changelog](history/hatchling.md) for version information.\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/hatchling.svg){ loading=lazy .off-glb }](https://repology.org/project/hatchling/versions)\n"
  },
  {
    "path": "docs/intro.md",
    "content": "# Introduction\n\n-----\n\n## Setup\n\nProjects can be set up for use by Hatch using the [`new`](cli/reference.md#hatch-new) command.\n\n### New project\n\nLet's say you want to create a project named `Hatch Demo`. You would run:\n\n```\nhatch new \"Hatch Demo\"\n```\n\nThis would create the following structure in your current working directory:\n\n```\nhatch-demo\n├── src\n│   └── hatch_demo\n│       ├── __about__.py\n│       └── __init__.py\n├── tests\n│   └── __init__.py\n├── LICENSE.txt\n├── README.md\n└── pyproject.toml\n```\n\n!!! tip\n    There are many ways to [customize](config/project-templates.md) project generation.\n\n### Existing project\n\nTo initialize an existing project, enter the directory containing the project and run the following:\n\n```\nhatch new --init\n```\n\nIf your project has a `setup.py` file the command will automatically migrate `setuptools` configuration for you. Otherwise, this will interactively guide you through the setup process.\n\n## Project metadata\n\nNext you'll want to define more of your project's [metadata](config/metadata.md) located in the `pyproject.toml` file. You can specify things like its [license](config/metadata.md#license), the [supported versions of Python](config/metadata.md#python-support), and [URLs](config/metadata.md#urls) referring to various parts of your project, like documentation.\n\n## Dependencies\n\nThe last step of the setup process is to define any [dependencies](config/dependency.md) that you'd like your project to begin with.\n\n## Configuration\n\nAll project-specific configuration recognized by Hatch can be defined in either the `pyproject.toml` file, or a file named `hatch.toml` where options are not contained within the `tool.hatch` table:\n\n```toml config-example\n[tool.hatch]\noption = \"...\"\n\n[tool.hatch.table1]\noption = \"...\"\n\n[tool.hatch.table2]\noption = \"...\"\n```\n\nTop level keys in the latter file take precedence when defined in both.\n\n!!! tip\n    If you want to make your file more compact, you can use [dotted keys](https://toml.io/en/v1.0.0#table), turning the above example into:\n\n    ```toml config-example\n    [tool.hatch]\n    option = \"...\"\n    table1.option = \"...\"\n    table2.option = \"...\"\n    ```\n"
  },
  {
    "path": "docs/meta/authors.md",
    "content": "# Authors\n\n-----\n\n## Maintainers\n\n- Ofek Lev [:material-web:](https://ofek.dev) [:material-github:](https://github.com/ofek) [:material-twitter:](https://twitter.com/Ofekmeister)\n- Cary Hawkins [:material-github:](https://github.com/cjames23)[:material-web:](https://cjameshawkins.com)\n\n## Contributors\n\n- Amjith Ramanujam [:material-twitter:](https://twitter.com/amjithr)\n- Arnaud Crowther [:material-github:](https://github.com/areknow)\n- Chaojie [:material-web:](https://chaojie.fun) [:material-github:](https://github.com/ischaojie)\n- Chris Warrick [:material-twitter:](https://twitter.com/Kwpolska)\n- Lumír 'Frenzy' Balhar [:material-email:](mailto:frenzy.madness@gmail.com) [:material-twitter:](https://twitter.com/lumirbalhar)\n- Ofek Lev [:material-web:](https://ofek.dev) [:material-github:](https://github.com/ofek) [:material-twitter:](https://twitter.com/Ofekmeister)\n- Olga Matoula [:material-github:](https://github.com/olgarithms) [:material-twitter:](https://twitter.com/olgarithms_)\n- Philip Blair [:material-email:](mailto:philip@pblair.org)\n- Robert Rosca [:material-github:](https://github.com/robertrosca)\n"
  },
  {
    "path": "docs/meta/faq.md",
    "content": "# FAQ\n\n-----\n\n## Interoperability\n\n***Q***: What is the risk of lock-in?\n\n***A***: Not much! Other than the [plugin system](../plugins/about.md), everything uses Python's established standards by default. Project metadata is based entirely on [the standard][project metadata standard], the build system is compatible with [PEP 517][]/[PEP 660][], versioning uses the scheme specified by [PEP 440](https://peps.python.org/pep-0440/#public-version-identifiers), dependencies are defined with [PEP 508][] strings, and environments use [virtualenv](https://github.com/pypa/virtualenv).\n\n***Q***: Must one use all features?\n\n***A***: No, all features are optional! You can use [just the build system](../build.md#packaging-ecosystem), publish wheels and source distributions that were built by other tools, only use the environment management, etc.\n\n## Libraries vs applications\n\n***Q***: Are workflows for both libraries and applications supported?\n\n***A***: Yes, mostly! Applications can utilize environment management just like libraries, and plugins can be used to [build](../plugins/builder/reference.md) projects in arbitrary formats or [publish](../plugins/publisher/reference.md) artifacts to arbitrary destinations.\n\nThe only caveat is that currently there is no support for re-creating an environment given a set of dependencies in a reproducible manner. Although a standard lock file format may be far off since [PEP 665][] was rejected, resolving capabilities are [coming to pip](https://github.com/pypa/pip/pull/10748). When that is stabilized, Hatch will add locking functionality and dedicated documentation for managing applications.\n\n## Tool migration\n\n***Q***: How to migrate to Hatch?\n\n### Build system\n\n=== \"Setuptools\"\n    ```python tab=\"setup.py\"\n    import os\n    from io import open\n\n    from setuptools import find_packages, setup\n\n    about = {}\n    with open(os.path.join('src', 'foo', '__about__.py'), 'r', 'utf-8') as f:\n        exec(f.read(), about)\n\n    with open('README.md', 'r', 'utf-8') as f:\n        readme = f.read()\n\n    setup(\n        # Metadata\n        name='foo',\n        version=about['__version__'],\n        description='...',\n        long_description=readme,\n        long_description_content_type='text/markdown',\n        author='...',\n        author_email='...',\n        project_urls={\n            'Documentation': '...',\n            'Source': '...',\n        },\n        classifiers=[\n            '...',\n        ],\n        keywords=[\n            '...',\n        ],\n        python_requires='>=3.8',\n        install_requires=[\n            '...',\n        ],\n        extras_require={\n            'feature': ['...'],\n        },\n\n        # Packaging\n        packages=find_packages(where='src'),\n        package_dir={'': 'src'},\n        package_data={\n            'foo': ['py.typed'],\n        },\n        zip_safe=False,\n        entry_points={\n            'console_scripts': [\n                'foo = foo.cli:main',\n            ],\n        },\n    )\n    ```\n\n    ```text tab=\"MANIFEST.in\"\n    graft tests\n\n    global-exclude *.py[cod] __pycache__\n    ```\n\n=== \"Hatch\"\n    ```toml tab=\"pyproject.toml\"\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [project]\n    name = \"foo\"\n    description = \"...\"\n    readme = \"README.md\"\n    authors = [\n      { name = \"...\", email = \"...\" },\n    ]\n    classifiers = [\n      \"...\",\n    ]\n    keywords = [\n      \"...\",\n    ]\n    requires-python = \">=3.8\"\n    dependencies = [\n      \"...\",\n    ]\n    dynamic = [\"version\"]\n\n    [project.urls]\n    Documentation = \"...\"\n    Source = \"...\"\n\n    [project.optional-dependencies]\n    feature = [\"...\"]\n\n    [project.scripts]\n    foo = \"foo.cli:main\"\n\n    [tool.hatch.version]\n    path = \"src/foo/__about__.py\"\n\n    [tool.hatch.build.targets.sdist]\n    include = [\n      \"/src\",\n      \"/tests\",\n    ]\n    ```\n\n### Environments\n\n=== \"Tox\"\n    Invocation:\n\n    ```\n    tox\n    ```\n\n    ```ini tab=\"tox.ini\"\n    [tox]\n    envlist =\n        py{38,39}-{42,3.14}\n        py{39,310}-{9000}-{foo,bar}\n\n    [testenv]\n    usedevelop = true\n    deps =\n        coverage[toml]\n        pytest\n        pytest-cov\n        foo: cryptography\n    commands =\n        pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=pkg --cov=tests {posargs}\n    setenv =\n        3.14: PRODUCT_VERSION=3.14\n        42: PRODUCT_VERSION=42\n        9000: PRODUCT_VERSION=9000\n        {foo,bar}: EXPERIMENTAL=true\n    ```\n\n=== \"Hatch\"\n    Invocation:\n\n    ```\n    hatch run test\n    ```\n\n    ```toml config-example\n    [tool.hatch.envs.default]\n    dependencies = [\n      \"coverage[toml]\",\n      \"pytest\",\n      \"pytest-cov\",\n    ]\n\n    [tool.hatch.envs.default.scripts]\n    test = 'pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=pkg --cov=tests'\n\n    [tool.hatch.envs.default.overrides]\n    matrix.version.env-vars = \"PRODUCT_VERSION\"\n    matrix.feature.env-vars = \"EXPERIMENTAL=true\"\n    matrix.feature.dependencies = [\n      { value = \"cryptography\", if = [\"foo\"] },\n    ]\n\n    [[tool.hatch.envs.default.matrix]]\n    python = [\"3.8\", \"3.9\"]\n    version = [\"42\", \"3.14\"]\n\n    [[tool.hatch.envs.default.matrix]]\n    python = [\"3.9\", \"3.10\"]\n    version = [\"9000\"]\n    feature = [\"foo\", \"bar\"]\n    ```\n\n## Fast CLI?\n\nThe claim about being faster than other tools is [based on timings](https://github.com/pypa/hatch/actions/workflows/cli.yml) that are always checked in CI.\n\nHatch achieves this by using lazy imports, lazily performing computation manually and with [functools.cached_property](https://docs.python.org/3/library/functools.html#functools.cached_property), using hacks like `not not ...` instead of `bool(...)`, etc.\n"
  },
  {
    "path": "docs/next-steps.md",
    "content": "# Next steps\n\n-----\n\n## Learn more\n\nAt this point you should have a basic understanding of how to use Hatch.\n\nNow you may want to check out advanced configuration for [environments](config/environment/overview.md) or [builds](config/build.md), set up your [preferred shell](config/hatch.md#shell), or read more about Hatch's [CLI](cli/about.md).\n\nAfter that, check out the [Hatch Showcase](https://github.com/ofek/hatch-showcase) project to see examples of what is possible.\n\nFinally, if you see a need, feel free to write a [plugin](plugins/about.md) for extended functionality.\n\n## Community\n\nFor any projects using Hatch, you may add its official badge somewhere prominent like the README.\n\n[![Hatch project](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pypa/hatch/master/docs/assets/badge/v0.json){ loading=lazy .off-glb }](https://github.com/pypa/hatch)\n\n=== \"Markdown\"\n    ```md\n    [![Hatch project](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pypa/hatch/master/docs/assets/badge/v0.json)](https://github.com/pypa/hatch)\n    ```\n\n=== \"reStructuredText\"\n    ```rst\n    .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pypa/hatch/master/docs/assets/badge/v0.json\n       :alt: Hatch project\n       :target: https://github.com/pypa/hatch\n    ```\n"
  },
  {
    "path": "docs/plugins/about.md",
    "content": "# Plugins\n\n-----\n\nHatch utilizes [pluggy](https://github.com/pytest-dev/pluggy) for its plugin functionality.\n\n## Overview\n\nAll plugins provide registration hooks that return one or more classes that inherit from a particular [type](#types) interface.\n\nEach registration hook must be decorated by Hatch's hook marker. For example, if you wanted to create a new kind of environment you could do:\n\n```python tab=\"hooks.py\"\nfrom hatchling.plugin import hookimpl\n\nfrom .plugin import SpecialEnvironment\n\n\n@hookimpl\ndef hatch_register_environment():\n    return SpecialEnvironment\n```\n\nThe hooks can return a single class or a list of classes.\n\nEvery class must define an attribute called `PLUGIN_NAME` that users will select when they wish to use the plugin. So in the example above, the class might be defined like:\n\n```python tab=\"plugin.py\"\n...\nclass SpecialEnvironment(...):\n    PLUGIN_NAME = 'special'\n    ...\n```\n\n## Project configuration\n\n### Naming\n\nIt is recommended that plugin project names are prefixed with `hatch-`. For example, if you wanted to make a plugin that provides some functionality for a product named `foo` you might do:\n\n```toml tab=\"pyproject.toml\"\n[project]\nname = \"hatch-foo\"\n```\n\n### Discovery\n\nYou'll need to define your project as a [Python plugin](../config/metadata.md#plugins) for Hatch:\n\n```toml tab=\"pyproject.toml\"\n[project.entry-points.hatch]\nfoo = \"pkg.hooks\"\n```\n\nThe name of the plugin should be the project name (excluding any `hatch-` prefix) and the path should represent the module that contains the registration hooks.\n\n### Classifier\n\nAdd [`Framework :: Hatch`](https://pypi.org/search/?c=Framework+%3A%3A+Hatch) to your project's [classifiers](../config/metadata.md#classifiers) to make it easy to search for Hatch plugins:\n\n```toml tab=\"pyproject.toml\"\n[project]\nclassifiers = [\n  ...\n  \"Framework :: Hatch\",\n  ...\n]\n```\n\n## Types\n\n### Hatchling\n\nThese are all involved in building projects and therefore any defined dependencies are automatically installed in each build environment.\n\n- [Builder](builder/reference.md)\n- [Build hook](build-hook/reference.md)\n- [Metadata hook](metadata-hook/reference.md)\n- [Version source](version-source/reference.md)\n- [Version scheme](version-scheme/reference.md)\n\n### Hatch\n\nThese must be installed in the same environment as Hatch itself.\n\n- [Environment](environment/reference.md)\n- [Environment collector](environment-collector/reference.md)\n- [Publisher](publisher/reference.md)\n"
  },
  {
    "path": "docs/plugins/build-hook/custom.md",
    "content": "# Custom build hook\n\n-----\n\nThis is a custom class in a given Python file that inherits from the [BuildHookInterface](reference.md#hatchling.builders.hooks.plugin.interface.BuildHookInterface).\n\n## Configuration\n\nThe build hook plugin name is `custom`.\n\n```toml config-example\n[tool.hatch.build.hooks.custom]\n[tool.hatch.build.targets.<TARGET_NAME>.hooks.custom]\n```\n\n## Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `path` | `hatch_build.py` | The path of the Python file |\n\n## Example\n\n```python tab=\"hatch_build.py\"\nfrom hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n\nclass CustomBuildHook(BuildHookInterface):\n    ...\n```\n\nIf multiple subclasses are found, you must define a function named `get_build_hook` that returns the desired build hook.\n\n!!! note\n    Any defined [PLUGIN_NAME](reference.md#hatchling.builders.hooks.plugin.interface.BuildHookInterface.PLUGIN_NAME) is ignored and will always be `custom`.\n"
  },
  {
    "path": "docs/plugins/build-hook/reference.md",
    "content": "# Build hook plugins\n\n-----\n\nA build hook provides code that will be executed at various stages of the build process. See the documentation for [build hook configuration](../../config/build.md#build-hooks).\n\n## Known third-party\n\n- [hatch-argparse-manpage](https://github.com/damonlynch/hatch-argparse-manpage) - generate man pages for [argparse](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser)-based CLIs\n- [hatch-autorun](https://github.com/ofek/hatch-autorun) - used to inject code into an installation that will automatically run before the first import\n- [hatch-build-scripts](https://github.com/rmorshea/hatch-build-scripts) - run arbitrary shell commands that create artifacts\n- [hatch-cython](https://github.com/joshua-auchincloss/hatch-cython) - build [Cython](https://github.com/cython/cython) extensions\n- [hatch-gettext](https://github.com/damonlynch/hatch-gettext) - compiles multi-lingual messages with GNU `gettext` tools\n- [hatch-jupyter-builder](https://github.com/jupyterlab/hatch-jupyter-builder) - used for packages in the Project Jupyter ecosystem\n- [hatch-mypyc](https://github.com/ofek/hatch-mypyc) - compiles code with [Mypyc](https://github.com/mypyc/mypyc)\n- [hatch-odoo](https://github.com/acsone/hatch-odoo) - package Odoo add-ons into the appropriate namespace\n- [hatch-project-name](https://github.com/valentinoli/hatch-project-name/) - writes the project name to a file\n- [scikit-build-core](https://github.com/scikit-build/scikit-build-core) - build extension modules with CMake\n\n## Overview\n\nBuild hooks run for every selected [version](../../config/build.md#versions) of build targets.\n\nThe [initialization](#hatchling.builders.hooks.plugin.interface.BuildHookInterface.initialize) stage occurs immediately before each build and the [finalization](#hatchling.builders.hooks.plugin.interface.BuildHookInterface.finalize) stage occurs immediately after. Each stage has the opportunity to view or modify [build data](#build-data).\n\n## Build data\n\nBuild data is a simple mapping whose contents can influence the behavior of builds. Which fields exist and are recognized depends on each build target.\n\nThe following fields are always present and recognized by the build system itself:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `artifacts` | `#!python list[str]` | This is a list of extra [`artifact` patterns](../../config/build.md#artifacts) and should generally only be appended to |\n| `force_include` | `#!python dict[str, str]` | This is a mapping of extra [forced inclusion paths](../../config/build.md#forced-inclusion), with this mapping taking precedence in case of conflicts |\n| `build_hooks` | `#!python tuple[str, ...]` | This is an immutable sequence of the names of the configured build hooks and matches the order in which they run |\n\n!!! attention\n    While user-facing TOML options are hyphenated, build data fields should be named with underscores to allow plugins to use them as valid Python identifiers.\n\n## Notes\n\nIn some cases it may be necessary to use `force_include` rather than `artifacts`. For example, say that you want to install a `lib.so` directly at the root of `site-packages` and a project defines a [package](../../config/build.md#packages) `src/foo`. If you create `src/lib.so`, there will never be a match because the directory traversal starts at `src/foo` rather than `src`. In that case you must do either:\n\n```python\nbuild_data['force_include']['src/lib.so'] = 'src/lib.so'\n```\n\nor\n\n```python\nbuild_data['force_include']['/absolute/path/to/src/lib.so'] = 'src/lib.so'\n```\n\n::: hatchling.builders.hooks.plugin.interface.BuildHookInterface\n    options:\n      members:\n      - PLUGIN_NAME\n      - app\n      - root\n      - config\n      - build_config\n      - target_name\n      - directory\n      - dependencies\n      - clean\n      - initialize\n      - finalize\n"
  },
  {
    "path": "docs/plugins/build-hook/version.md",
    "content": "# Version build hook\n\n-----\n\nThis writes the project's version to a file.\n\n## Configuration\n\nThe build hook plugin name is `version`.\n\n```toml config-example\n[tool.hatch.build.hooks.version]\n[tool.hatch.build.targets.<TARGET_NAME>.hooks.version]\n```\n\n## Options\n\n| Option | Description |\n| --- | --- |\n| `path` (required) | A relative path to the desired file |\n| `template` | A string representing the entire contents of `path` that will be formatted with a `version` variable |\n| `pattern` | Rather than updating the entire file, a regular expression may be used that has a named group called `version` that represents the version. If set to `true`, a pattern will be used that looks for a variable named `__version__` or `VERSION` that is set to a string containing the version, optionally prefixed with the lowercase letter `v`. |\n"
  },
  {
    "path": "docs/plugins/builder/binary.md",
    "content": "# Binary builder\n\n-----\n\nThis uses [PyApp](https://github.com/ofek/pyapp) to build an application that is able to bootstrap itself at runtime.\n\n!!! note\n    This requires an installation of [Rust](https://www.rust-lang.org).\n\n## Configuration\n\nThe builder plugin name is `binary`.\n\n```toml config-example\n[tool.hatch.build.targets.binary]\n```\n\n## Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `scripts` | all defined | An array of defined [script](../../config/metadata.md#cli) names to limit what gets built |\n| `python-version` | latest compatible Python minor version | The [Python version ID](https://ofek.dev/pyapp/latest/config/#known) to use |\n| `pyapp-version` | | The version of PyApp to use |\n\n## Build behavior\n\nIf any [scripts](../../config/metadata.md#cli) are defined then each one will be built (limited by the `scripts` option). Otherwise, a single executable will be built based on the project name assuming there is an equivalently named module with a `__main__.py` file.\n\nEvery executable will be built inside an `app` directory in the [output directory](../../config/build.md#output-directory).\n\nIf the `CARGO` environment variable is set then that path will be used as the executable for performing builds.\n\nIf the [`CARGO_BUILD_TARGET`](https://doc.rust-lang.org/cargo/reference/config.html#buildtarget) environment variable is set then its value will be appended to the file name stems.\n\nIf the `PYAPP_REPO` environment variable is set then a local build will be performed inside that directory rather than installing from [crates.io](https://crates.io). Note that this is [required](https://github.com/cross-rs/cross/issues/1215) if the `CARGO` environment variable refers to [cross](https://github.com/cross-rs/cross).\n"
  },
  {
    "path": "docs/plugins/builder/custom.md",
    "content": "# Custom builder\n\n-----\n\nThis is a custom class in a given Python file that inherits from the [BuilderInterface](reference.md#hatchling.builders.plugin.interface.BuilderInterface).\n\n## Configuration\n\nThe builder plugin name is `custom`.\n\n```toml config-example\n[tool.hatch.build.targets.custom]\n```\n\n## Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `path` | `hatch_build.py` | The path of the Python file |\n\n## Example\n\n```python tab=\"hatch_build.py\"\nfrom hatchling.builders.plugin.interface import BuilderInterface\n\n\nclass CustomBuilder(BuilderInterface):\n    ...\n```\n\nIf multiple subclasses are found, you must define a function named `get_builder` that returns the desired builder.\n\n!!! note\n    Any defined [PLUGIN_NAME](reference.md#hatchling.builders.plugin.interface.BuilderInterface.PLUGIN_NAME) is ignored and will always be `custom`.\n"
  },
  {
    "path": "docs/plugins/builder/reference.md",
    "content": "# Builder plugins\n\n-----\n\nSee the documentation for [build configuration](../../config/build.md).\n\n## Known third-party\n\n- [hatch-aws](https://github.com/aka-raccoon/hatch-aws) - used for building AWS Lambda functions with SAM\n- [hatch-zipped-directory](https://github.com/dairiki/hatch-zipped-directory) - used for building ZIP archives for installation into various foreign package installation systems\n\n::: hatchling.builders.plugin.interface.BuilderInterface\n    options:\n      members:\n      - PLUGIN_NAME\n      - app\n      - root\n      - build_config\n      - target_config\n      - config\n      - get_config_class\n      - get_version_api\n      - get_default_versions\n      - clean\n      - recurse_included_files\n      - get_default_build_data\n"
  },
  {
    "path": "docs/plugins/builder/sdist.md",
    "content": "# Source distribution builder\n\n-----\n\nA source distribution, or `sdist`, is an archive of Python \"source code\". Although largely unspecified, by convention it should include everything that is required to build a [wheel](wheel.md) without making network requests.\n\n## Configuration\n\nThe builder plugin name is `sdist`.\n\n```toml config-example\n[tool.hatch.build.targets.sdist]\n```\n\n## Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `core-metadata-version` | `\"2.4\"` | The version of [core metadata](https://packaging.python.org/specifications/core-metadata/) to use |\n| `strict-naming` | `true` | Whether or not file names should contain the normalized version of the project name |\n| `support-legacy` | `false` | Whether or not to include a `setup.py` file to support legacy installation mechanisms |\n\n## Versions\n\n| Version | Description |\n| --- | --- |\n| `standard` (default) | The latest conventional format |\n\n## Default file selection\n\nWhen the user has not set any [file selection](../../config/build.md#file-selection) options, all files that are not [ignored by your VCS](../../config/build.md#vcs) will be included.\n\n!!! note\n    The following files are always included and cannot be excluded:\n\n    - `/pyproject.toml`\n    - `/hatch.toml`\n    - `/hatch_build.py`\n    - `/.gitignore` or `/.hgignore`\n    - Any defined [`readme`](../../config/metadata.md#readme) file\n    - All defined [`license-files`](../../config/metadata.md#license)\n\n## Reproducibility\n\n[Reproducible builds](../../config/build.md#reproducible-builds) are supported.\n\n## Build data\n\nThis is data that can be modified by [build hooks](../build-hook/reference.md).\n\n| Data | Default | Description |\n| --- | --- | --- |\n| `dependencies` | | Extra [project dependencies](../../config/metadata.md#required) |\n"
  },
  {
    "path": "docs/plugins/builder/wheel.md",
    "content": "# Wheel builder\n\n-----\n\nA [wheel](https://packaging.python.org/specifications/binary-distribution-format/) is a binary distribution of a Python package that can be installed directly into an environment.\n\n## Configuration\n\nThe builder plugin name is `wheel`.\n\n```toml config-example\n[tool.hatch.build.targets.wheel]\n```\n\n## Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `core-metadata-version` | `\"2.4\"` | The version of [core metadata](https://packaging.python.org/specifications/core-metadata/) to use |\n| `shared-data` | | A mapping similar to the [forced inclusion](../../config/build.md#forced-inclusion) option corresponding to the `data` subdirectory within the standard [data directory](https://packaging.python.org/en/latest/specifications/binary-distribution-format/#the-data-directory) that will be installed globally in a given Python environment, usually under `#!python sys.prefix` |\n| `shared-scripts` | | A mapping similar to the [forced inclusion](../../config/build.md#forced-inclusion) option corresponding to the `scripts` subdirectory within the standard [data directory](https://packaging.python.org/en/latest/specifications/binary-distribution-format/#the-data-directory) that will be installed in a given Python environment, usually under `Scripts` on Windows or `bin` otherwise, and would normally be available on PATH |\n| `extra-metadata` | | A mapping similar to the [forced inclusion](../../config/build.md#forced-inclusion) option corresponding to extra [metadata](https://peps.python.org/pep-0427/#the-dist-info-directory) that will be shipped in a directory named `extra_metadata` |\n| `strict-naming` | `true` | Whether or not file names should contain the normalized version of the project name |\n| `macos-max-compat` | `false` | Whether or not on macOS, when build hooks have set the `infer_tag` [build data](#build-data), the wheel name should signal broad support rather than specific versions for newer SDK versions.<br><br>Note: This option will eventually be removed. |\n| `bypass-selection` | `false` | Whether or not to suppress the error when one has not defined any file selection options and all heuristics have failed to determine what to ship |\n| `sbom-files` | | A list of paths to [Software Bill of Materials](https://peps.python.org/pep-0770/) files that will be included in the `.dist-info/sboms/` directory of the wheel |\n\n!!! note\n    Many build frontends will build the wheel from a source distribution. This is the recommended approach, but it means you need to ensure the SBOM files passed to `sbom-files` are also [included in the source distribution](https://hatch.pypa.io/latest/config/build/#file-selection).\n\n## Versions\n\n| Version | Description |\n| --- | --- |\n| `standard` (default) | The latest standardized format |\n| `editable` | A wheel that only ships `.pth` files or import hooks for real-time development |\n\n## Default file selection\n\nWhen the user has not set any [file selection](../../config/build.md#file-selection) options, the [project name](../../config/metadata.md#name) will be used to determine the package to ship in the following heuristic order:\n\n1. `<NAME>/__init__.py`\n2. `src/<NAME>/__init__.py`\n3. `<NAME>.py`\n4. `<NAMESPACE>/<NAME>/__init__.py`\n\nIf none of these heuristics are satisfied, an error will be raised.\n\n## Reproducibility\n\n[Reproducible builds](../../config/build.md#reproducible-builds) are supported.\n\n## Build data\n\nThis is data that can be modified by [build hooks](../build-hook/reference.md).\n\n| Data | Default | Description |\n| --- | --- | --- |\n| `tag` | | The full [tag](https://peps.python.org/pep-0425/) part of the filename (e.g. `py3-none-any`), defaulting to a cross-platform wheel with the supported major versions of Python based on [project metadata](../../config/metadata.md#python-support) |\n| `infer_tag` | `#!python False` | When `tag` is not set, this may be enabled to use the one most specific to the platform, Python interpreter, and ABI |\n| `pure_python` | `#!python True` | Whether or not to write metadata indicating that the package does not contain any platform-specific files |\n| `dependencies` | | Extra [project dependencies](../../config/metadata.md#required) |\n| `shared_data` | | Additional [`shared-data`](#options) entries, which take precedence in case of conflicts |\n| `shared_scripts` | | Additional [`shared-scripts`](#options) entries, which take precedence in case of conflicts |\n| `extra_metadata` | | Additional [`extra-metadata`](#options) entries, which take precedence in case of conflicts |\n| `force_include_editable` | | Similar to the [`force_include` option](../build-hook/reference.md#build-data) but specifically for the `editable` [version](#versions) and takes precedence |\n| `sbom_files` | | This is a list of the sbom files that should be included under `.dist-info/sboms`. |\n"
  },
  {
    "path": "docs/plugins/environment/reference.md",
    "content": "# Environment plugins\n\n-----\n\nSee the documentation for [environment configuration](../../config/environment/overview.md).\n\n## Known third-party\n\n- [hatch-conda](https://github.com/OldGrumpyViking/hatch-conda) - environments backed by Conda/Mamba\n- [hatch-containers](https://github.com/ofek/hatch-containers) - environments run inside containers\n- [hatch-pip-compile](https://github.com/juftin/hatch-pip-compile) - use [pip-compile](https://github.com/jazzband/pip-tools) to manage project dependencies and lockfiles\n- [hatch-pip-deepfreeze](https://github.com/sbidoul/hatch-pip-deepfreeze) - [virtual](virtual.md) environments with dependency locking by [pip-deepfreeze](https://github.com/sbidoul/pip-deepfreeze)\n\n## Installation\n\nAny required environment types that are not built-in must be manually installed alongside Hatch or listed in the `tool.hatch.env.requires` array for automatic management:\n\n```toml config-example\n[tool.hatch.env]\nrequires = [\n  \"...\",\n]\n```\n\n## Life cycle\n\nWhenever an environment is used, the following logic is performed:\n\n::: hatch.project.core.Project.prepare_environment\n    options:\n      show_root_heading: false\n      show_root_toc_entry: false\n\n## Build environments\n\nAll environment types should [offer support](#hatch.env.plugin.interface.EnvironmentInterface.fs_context) for synchronized storage between the local file system and the environment. This functionality is used in the following scenarios:\n\n- the [`build`](../../cli/reference.md#hatch-build) command\n- commands that read dependencies, like [`dep hash`](../../cli/reference.md#hatch-dep-hash), if any [project dependencies](../../config/metadata.md#dependencies) are [set dynamically](../../config/metadata.md#dynamic)\n\n::: hatch.env.plugin.interface.EnvironmentInterface\n    options:\n      members:\n      - PLUGIN_NAME\n      - find\n      - create\n      - remove\n      - exists\n      - install_project\n      - install_project_dev_mode\n      - dependencies_in_sync\n      - sync_dependencies\n      - dependency_hash\n      - project_dependencies\n      - project_root\n      - sep\n      - pathsep\n      - fs_context\n      - activate\n      - deactivate\n      - app_status_creation\n      - app_status_pre_installation\n      - app_status_post_installation\n      - app_status_project_installation\n      - app_status_dependency_state_check\n      - app_status_dependency_installation_check\n      - app_status_dependency_synchronization\n      - app\n      - root\n      - name\n      - data_directory\n      - isolated_data_directory\n      - config\n      - platform\n      - environment_dependencies\n      - dependencies\n      - env_vars\n      - env_include\n      - env_exclude\n      - platforms\n      - skip_install\n      - dev_mode\n      - description\n      - command_context\n      - enter_shell\n      - run_shell_command\n      - resolve_commands\n      - get_env_vars\n      - apply_features\n      - construct_pip_install_command\n      - join_command_args\n      - check_compatibility\n      - get_option_types\n      - get_env_var_option\n      - get_context\n"
  },
  {
    "path": "docs/plugins/environment/virtual.md",
    "content": "# Virtual environment\n\n-----\n\nThis uses virtual environments backed by [virtualenv](https://github.com/pypa/virtualenv) or [UV](https://github.com/astral-sh/uv).\n\n## Configuration\n\nThe environment plugin name is `virtual`.\n\n```toml config-example\n[tool.hatch.envs.<ENV_NAME>]\ntype = \"virtual\"\n```\n\n## Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `python` | | The version of Python to find on your system and subsequently use to create the environment, defaulting to the `HATCH_PYTHON` environment variable, followed by the [normal resolution logic](#python-resolution). Setting the `HATCH_PYTHON` environment variable to `self` will force the use of the Python executable Hatch is running on. For more information, see the [documentation](https://virtualenv.pypa.io/en/latest/user_guide.html#python-discovery). |\n| `python-sources` | `['external', 'internal']` | This may be set to an array of strings that are either the literal `internal` or `external`. External considers only Python executables that are already on `PATH`. Internal considers only [internally managed Python distributions](#internal-distributions). |\n| `path` | | An explicit path to the virtual environment. The path may be absolute or relative to the project root. Any environments that [inherit](../../config/environment/overview.md#inheritance) this option will also use this path. The environment variable `HATCH_ENV_TYPE_VIRTUAL_PATH` may be used, which will take precedence. |\n| `system-packages` | `false` | Whether or not to give the virtual environment access to the system `site-packages` directory |\n| `installer` | `pip` | When set to `uv`, [UV](https://github.com/astral-sh/uv) will be used in place of virtualenv & pip for virtual environment creation and dependency management, respectively. If you intend to provide UV yourself, you may set the `HATCH_ENV_TYPE_VIRTUAL_UV_PATH` environment variable which should be the absolute path to a UV binary. This environment variable implicitly sets the `installer` option to `uv` (if unset). |\n\n## Location\n\nThe [location](../../cli/reference.md#hatch-env-find) of environments is determined in the following heuristic order:\n\n1. The `path` option\n2. A directory named after the environment within the configured `virtual` [environment directory](../../config/hatch.md#environments) if the directory resides somewhere within the project root or if it is set to a `.virtualenvs` directory within the user's home directory\n3. Otherwise, environments are stored within the configured `virtual` [environment directory](../../config/hatch.md#environments) in a deeply nested structure in order to support multiple projects\n\nAdditionally, when the `path` option is not used, the name of the directory for the `default` environment will be the normalized project name to provide a more meaningful default [shell](../../cli/reference.md#hatch-shell) prompt.\n\n## Python resolution\n\nVirtual environments necessarily require a parent installation of Python. The following rules determine how the parent is resolved.\n\nThe Python choice is determined by the [`python` option](#options) followed by the `HATCH_PYTHON` environment variable. If the choice is via the environment variable, then resolution stops and that path is used unconditionally.\n\nThe resolvers will be based on the [`python-sources` option](#options) and all resolved interpreters will ensure compatibility with the project's defined [Python support](../../config/metadata.md#python-support).\n\nIf a Python version has been chosen then each resolver will try to find an interpreter that satisfies that version.\n\nIf no version has been chosen, then each resolver will try to find a version that matches the version of Python that Hatch is currently running on. If not found then each resolver will try to find the highest compatible version.\n\n!!! note\n    Some external Python paths are considered unstable and are ignored during resolution. For example, if Hatch is installed via Homebrew then `sys.executable` will be ignored because the interpreter could change or be removed at any time.\n\n!!! note\n    When resolution finds a match using an [internally managed distribution](#internal-distributions) and an update is available, the latest distribution will automatically be downloaded before environment creation.\n\n## Internal distributions\n\nThe following options are recognized for internal Python resolution.\n\n!!! tip\n    You can set custom sources for distributions by setting the `HATCH_PYTHON_SOURCE_<NAME>` environment variable where `<NAME>` is the uppercased version of the distribution name with periods replaced by underscores e.g. `HATCH_PYTHON_SOURCE_PYPY3_10`.\n\n### CPython\n\n| NAME |\n| --- |\n| `3.7` |\n| `3.8` |\n| `3.9` |\n| `3.10` |\n| `3.11` |\n| `3.12` |\n| `3.13` |\n\nThe source of distributions is the [python-build-standalone](https://github.com/indygreg/python-build-standalone) project.\n\nSome distributions have [variants](https://gregoryszorc.com/docs/python-build-standalone/main/running.html) that may be configured with environment variables. Options may be combined.\n\n| Option | Platforms | Allowed values |\n| --- | --- | --- |\n| `HATCH_PYTHON_VARIANT_CPU` | <ul><li>Linux</li></ul> | <ul><li><code>v1</code></li><li><code>v2</code></li><li><code>v3</code> (default)</li><li><code>v4</code></li></ul> |\n| `HATCH_PYTHON_VARIANT_GIL` | <ul><li>Linux</li><li>Windows</li><li>macOS</li></ul> | <ul><li><code>freethreaded</code></li></ul> |\n\n### PyPy\n\n| NAME |\n| --- |\n| `pypy2.7` |\n| `pypy3.9` |\n| `pypy3.10` |\n\nThe source of distributions is the [PyPy](https://www.pypy.org) project.\n\n## Troubleshooting\n\n### macOS SIP\n\nIf you need to set linker environment variables like those starting with `DYLD_` or `LD_`, any executable secured by [System Integrity Protection](https://en.wikipedia.org/wiki/System_Integrity_Protection) that is invoked when [running commands](../../environment.md#command-execution) will not see those environment variable modifications as macOS strips those.\n\nHatch interprets such commands as shell commands but deliberately ignores such paths to protected shells. This workaround suffices for the majority of use cases but there are 2 situations in which it may not:\n\n1. There are no unprotected `sh`, `bash`, `zsh`, nor `fish` executables found along PATH.\n2. The desired executable is a project's [script](../../config/metadata.md#cli), and the [location](#location) of environments contains spaces or is longer than 124[^1] characters. In this case `pip` and other installers will create such an entry point with a shebang pointing to `/bin/sh` (which is protected) to avoid shebang limitations. Rather than changing the location, you could invoke the script as e.g. `python -m pytest` (if the project supports that method of invocation by shipping a `__main__.py`).\n\n[^1]: The shebang length limit is [usually](https://web.archive.org/web/20221231220856/https://www.in-ulm.de/~mascheck/various/shebang/#length) 127 but 3 characters surround the executable path: `#!<EXE_PATH>\\n`\n"
  },
  {
    "path": "docs/plugins/environment-collector/custom.md",
    "content": "# Custom environment collector\n\n-----\n\nThis is a custom class in a given Python file that inherits from the [EnvironmentCollectorInterface](reference.md#hatch.env.collectors.plugin.interface.EnvironmentCollectorInterface).\n\n## Configuration\n\nThe environment collector plugin name is `custom`.\n\n```toml config-example\n[tool.hatch.env.collectors.custom]\n```\n\n## Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `path` | `hatch_plugins.py` | The path of the Python file |\n\n## Example\n\n```python tab=\"hatch_plugins.py\"\n    from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\n\n\n    class CustomEnvironmentCollector(EnvironmentCollectorInterface):\n        ...\n```\n\nIf multiple subclasses are found, you must define a function named `get_environment_collector` that returns the desired environment collector.\n\n!!! note\n    Any defined [PLUGIN_NAME](reference.md#hatch.env.collectors.plugin.interface.EnvironmentCollectorInterface.PLUGIN_NAME) is ignored and will always be `custom`.\n"
  },
  {
    "path": "docs/plugins/environment-collector/default.md",
    "content": "# Default environment collector\n\n-----\n\nThis adds the `default` environment with [type](../../config/environment/overview.md#type) set to [virtual](../environment/virtual.md) and will always be applied.\n\n## Configuration\n\nThe environment collector plugin name is `default`.\n\n```toml config-example\n[tool.hatch.env.collectors.default]\n```\n\n## Options\n\nThere are no options available currently.\n"
  },
  {
    "path": "docs/plugins/environment-collector/reference.md",
    "content": "# Environment collector plugins\n\n-----\n\nEnvironment collectors allow for dynamically modifying environments or adding environments beyond those defined in config. Users can override default values provided by each environment.\n\n## Known third-party\n\n- [hatch-mkdocs](https://github.com/mkdocs/hatch-mkdocs) - integrate [MkDocs](https://github.com/mkdocs/mkdocs) and infer dependencies into an env\n\n## Installation\n\nAny required environment collectors that are not built-in must be manually installed alongside Hatch or listed in the `tool.hatch.env.requires` array for automatic management:\n\n```toml config-example\n[tool.hatch.env]\nrequires = [\n  \"...\",\n]\n```\n\n::: hatch.env.collectors.plugin.interface.EnvironmentCollectorInterface\n    options:\n      members:\n      - PLUGIN_NAME\n      - root\n      - config\n      - get_initial_config\n      - finalize_config\n      - finalize_environments\n"
  },
  {
    "path": "docs/plugins/metadata-hook/custom.md",
    "content": "# Custom metadata hook\n\n-----\n\nThis is a custom class in a given Python file that inherits from the [MetadataHookInterface](reference.md#hatchling.metadata.plugin.interface.MetadataHookInterface).\n\n## Configuration\n\nThe metadata hook plugin name is `custom`.\n\n```toml config-example\n[tool.hatch.metadata.hooks.custom]\n```\n\n## Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `path` | `hatch_build.py` | The path of the Python file |\n\n## Example\n\n```python tab=\"hatch_build.py\"\nfrom hatchling.metadata.plugin.interface import MetadataHookInterface\n\n\nclass CustomMetadataHook(MetadataHookInterface):\n    ...\n```\n\nIf multiple subclasses are found, you must define a function named `get_metadata_hook` that returns the desired build hook.\n\n!!! note\n    Any defined [PLUGIN_NAME](reference.md#hatchling.metadata.plugin.interface.MetadataHookInterface.PLUGIN_NAME) is ignored and will always be `custom`.\n"
  },
  {
    "path": "docs/plugins/metadata-hook/reference.md",
    "content": "# Metadata hook plugins\n\n-----\n\nMetadata hooks allow for the modification of [project metadata](../../config/metadata.md) after it has been loaded.\n\n## Known third-party\n\n- [hatch-docstring-description](https://github.com/flying-sheep/hatch-docstring-description) - set the project description using docstrings\n- [hatch-fancy-pypi-readme](https://github.com/hynek/hatch-fancy-pypi-readme) - dynamically construct the README\n- [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version) - uses fields from NodeJS `package.json` files\n- [hatch-odoo](https://github.com/acsone/hatch-odoo) - determine dependencies based on manifests of Odoo add-ons\n- [hatch-requirements-txt](https://github.com/repo-helper/hatch-requirements-txt) - read project dependencies from `requirements.txt` files\n- [UniDep](https://github.com/basnijholt/unidep) - for unified `pip` and `conda` dependency management using a single `requirements.yaml` file for both \n\n::: hatchling.metadata.plugin.interface.MetadataHookInterface\n    options:\n      members:\n      - PLUGIN_NAME\n      - root\n      - config\n      - update\n      - get_known_classifiers\n"
  },
  {
    "path": "docs/plugins/publisher/package-index.md",
    "content": "# Index publisher\n\n-----\n\nSee the documentation for [publishing](../../publish.md).\n\n## Options\n\n| Flag | Config name | Description |\n| --- | --- | --- |\n| `-r`/`--repo` | `repo` | The repository with which to publish artifacts |\n| `-u`/`--user` | `user` | The user with which to authenticate |\n| `-a`/`--auth` | `auth` | The credentials to use for authentication |\n| `--ca-cert` | `ca-cert` | The path to a CA bundle |\n| `--client-cert` | `client-cert` | The path to a client certificate, optionally containing the private key |\n| `--client-key` | `client-key` | The path to the client certificate's private key |\n| | `repos` | A table of named [repositories](#repositories) to their respective options |\n\n## Configuration\n\nThe publisher plugin name is `index`.\n\n```toml tab=\"config.toml\"\n[publish.index]\n```\n\n### Repositories\n\nAll top-level options can be overridden per repository using the `repos` table\nwith a required `url` attribute for each repository. The following shows the\ndefault configuration:\n\n```toml tab=\"config.toml\"\n[publish.index.repos.main]\nurl = \"https://upload.pypi.org/legacy/\"\n\n[publish.index.repos.test]\nurl = \"https://test.pypi.org/legacy/\"\n```\n\nThe `repo` and `repos` options have no effect.\n\n### Confirmation prompt\n\nYou can require a confirmation prompt or use of the `-y`/`--yes` flag by\nsetting publishers' `disable` option to `true` in either Hatch's\n[config file](../../config/hatch.md) or project-specific configuration (which takes\nprecedence):\n\n```toml tab=\"config.toml\"\n[publish.index]\ndisable = true\n```\n\n```toml config-example\n[tool.hatch.publish.index]\ndisable = true\n```\n"
  },
  {
    "path": "docs/plugins/publisher/reference.md",
    "content": "# Publisher plugins\n\n-----\n\n## Known third-party\n\n- [hatch-aws-publisher](https://github.com/aka-raccoon/hatch-aws-publisher) - publish AWS Lambda functions with SAM\n\n::: hatch.publish.plugin.interface.PublisherInterface\n    options:\n      members:\n      - PLUGIN_NAME\n      - app\n      - root\n      - cache_dir\n      - project_config\n      - plugin_config\n      - disable\n      - publish\n"
  },
  {
    "path": "docs/plugins/utilities.md",
    "content": "# Plugin utilities\n\n-----\n\n::: hatchling.builders.utils.get_reproducible_timestamp\n    options:\n      show_root_full_path: true\n\n::: hatchling.builders.config.BuilderConfig\n    options:\n      show_source: false\n      members:\n      - directory\n      - ignore_vcs\n      - reproducible\n      - dev_mode_dirs\n      - versions\n      - dependencies\n      - default_include\n      - default_exclude\n      - default_packages\n      - default_only_include\n\n::: hatchling.bridge.app.Application\n    options:\n      show_source: false\n      members:\n      - abort\n      - verbosity\n      - display_debug\n      - display_error\n      - display_info\n      - display_success\n      - display_waiting\n      - display_warning\n\n::: hatch.utils.platform.Platform\n    options:\n      show_source: false\n      members:\n      - format_for_subprocess\n      - run_command\n      - check_command\n      - check_command_output\n      - capture_process\n      - exit_with_command\n      - default_shell\n      - modules\n      - home\n      - name\n      - display_name\n      - windows\n      - macos\n      - linux\n\n::: hatch.env.context.EnvironmentContextFormatter\n    options:\n      show_source: false\n      members:\n      - formatters\n\n::: hatch.env.plugin.interface.FileSystemContext\n    options:\n      show_source: false\n      members:\n      - env\n      - sync_local\n      - sync_env\n      - local_path\n      - env_path\n      - join\n"
  },
  {
    "path": "docs/plugins/version-scheme/reference.md",
    "content": "# Version scheme plugins\n\n-----\n\n## Known third-party\n\n- [hatch-semver](https://github.com/Nagidal/hatch-semver) - uses [semantic versioning](https://semver.org)\n\n::: hatchling.version.scheme.plugin.interface.VersionSchemeInterface\n    options:\n      members:\n      - PLUGIN_NAME\n      - root\n      - config\n      - validate_bump\n      - update\n"
  },
  {
    "path": "docs/plugins/version-scheme/standard.md",
    "content": "# Standard version scheme\n\n-----\n\nSee the documentation for [versioning](../../version.md#updating).\n\n## Configuration\n\nThe version scheme plugin name is `standard`.\n\n```toml config-example\n[tool.hatch.version]\nscheme = \"standard\"\n```\n\n## Options\n\n| Option | Description |\n| --- | --- |\n| `validate-bump` | When setting a specific version, this determines whether to check that the new version is higher than the original. The default is `true`. |\n"
  },
  {
    "path": "docs/plugins/version-source/code.md",
    "content": "# Code version source\n\n-----\n\n## Updates\n\nSetting the version is not supported.\n\n## Configuration\n\nThe version source plugin name is `code`.\n\n```toml config-example\n[tool.hatch.version]\nsource = \"code\"\n```\n\n## Options\n\n| Option | Description |\n| --- | --- |\n| `path` (required) | A relative path to a Python file or extension module that will be loaded |\n| `expression` | A Python expression that when evaluated in the context of the loaded file returns the version. The default expression is simply `__version__`. |\n| `search-paths` | A list of relative paths to directories that will be prepended to Python's search path |\n\n## Missing imports\n\nIf the chosen path imports another module in your project, then you'll need to use absolute imports coupled with the `search-paths` option. For example, say you need to load the following file:\n\n```python tab=\"src/pkg/\\_\\_init\\_\\_.py\"\n    from ._version import get_version\n\n    __version__ = get_version()\n```\n\nYou should change it to:\n\n```python tab=\"src/pkg/\\_\\_init\\_\\_.py\"\n    from pkg._version import get_version\n\n    __version__ = get_version()\n```\n\nand the configuration would become:\n\n```toml config-example\n[tool.hatch.version]\nsource = \"code\"\npath = \"src/pkg/__init__.py\"\nsearch-paths = [\"src\"]\n```\n"
  },
  {
    "path": "docs/plugins/version-source/env.md",
    "content": "# Environment version source\n\n-----\n\nRetrieves the version from an environment variable. This can be useful in build pipelines where the version is set by an external trigger.\n\n## Updates\n\nSetting the version is not supported.\n\n## Configuration\n\nThe version source plugin name is `env`.\n\n```toml config-example\n[tool.hatch.version]\nsource = \"env\"\n```\n\n## Options\n\n| Option | Description |\n| --- | --- |\n| `variable` (required) | The name of the environment variable |\n"
  },
  {
    "path": "docs/plugins/version-source/reference.md",
    "content": "# Version source plugins\n\n-----\n\n## Known third-party\n\n- [hatch-vcs](https://github.com/ofek/hatch-vcs) - uses your preferred version control system (like Git)\n- [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version) - uses the `version` field of NodeJS `package.json` files\n- [hatch-regex-commit](https://github.com/frankie567/hatch-regex-commit) - automatically creates a Git commit and tag after version bumping\n- [versioningit](https://github.com/jwodder/versioningit) - determines version from Git or Mercurial tags, with customizable version formatting\n\n::: hatchling.version.source.plugin.interface.VersionSourceInterface\n    options:\n      members:\n      - PLUGIN_NAME\n      - root\n      - config\n      - get_version_data\n      - set_version\n"
  },
  {
    "path": "docs/plugins/version-source/regex.md",
    "content": "# Regex version source\n\n-----\n\nSee the documentation for [versioning](../../version.md).\n\n## Updates\n\nSetting the version is supported.\n\n## Configuration\n\nThe version source plugin name is `regex`.\n\n```toml config-example\n[tool.hatch.version]\nsource = \"regex\"\n```\n\n## Options\n\n| Option | Description |\n| --- | --- |\n| `path` (required) | A relative path to a file containing the project's version |\n| `pattern` | A regular expression that has a named group called `version` that represents the version. The default pattern looks for a variable named `__version__` or `VERSION` that is set to a string containing the version, optionally prefixed with the lowercase letter `v`. |\n"
  },
  {
    "path": "docs/publish.md",
    "content": "# Publishing\n\n-----\n\nAfter your project is [built](build.md), you can distribute it using the [`publish`](cli/reference.md#hatch-publish) command.\n\nThe `-p`/`--publisher` option controls which publisher to use, with the default being [index](plugins/publisher/package-index.md).\n\n## Artifact selection\n\nBy default, the `dist` directory located at the root of your project will be used:\n\n```console\n$ hatch publish\ndist/hatch_demo-1rc0-py3-none-any.whl ... success\ndist/hatch_demo-1rc0.tar.gz ... success\n\n[hatch-demo]\nhttps://pypi.org/project/hatch-demo/1rc0/\n```\n\nYou can instead pass specific paths as arguments:\n\n```\nhatch publish /path/to/artifacts foo-1.tar.gz\n```\n\nOnly files ending with `.whl` or `.tar.gz` will be published.\n\n## Further resources\n\nPlease refer to the publisher plugin [reference](plugins/publisher/package-index.md)\nfor configuration options.\n\nThere's a How-To on [authentication](how-to/publish/auth.md)\nand on options to select the target [repository](how-to/publish/repo.md).\n\nThe `publish` command is implemented as a built-in plugin, if you're\nplanning your own plugin, read about the [publisher plugin API](plugins/publisher/reference.md). \n"
  },
  {
    "path": "docs/tutorials/environment/basic-usage.md",
    "content": "# Managing environments\n\n-----\n\nHatch [environments](../../environment.md) are isolated workspaces that can be used for project tasks including running tests, building documentation and running code formatters and linters.\n\n## The default environment\n\nWhen you start using Hatch, you can create the `default` environment. To do this use the [`env create`](../../cli/reference.md#hatch-env-create) command:\n\n```\nhatch env create\n```\n\nThis will not only create will the `default` environment for you to work in but will also install your project in [dev mode](../../config/environment/overview.md#dev-mode) in this `default` environment.\n\n!!! tip\n    You never need to manually create environments as [spawning a shell](#launching-a-shell-within-a-specific-environment) or [running commands](#run-commands-within-a-specific-environment) within one will automatically trigger creation.\n\n### Using the default environment\n\nHatch will always use the `default` environment if an environment is not chosen explicitly when [running a command](../../environment.md#command-execution).\n\nFor instance, the following shows how to get version information for the Python in use.\n\n```console\n$ hatch run python -V\nPython 3.12.1\n```\n\n### Configure the default environment\n\nYou can customize the tools that are installed into the `default` environment by adding a table called `tool.hatch.envs.default` to your `pyproject.toml` file. Below is an example of adding the [dependencies](../../config/environment/overview.md#dependencies) `pydantic` and `numpy` to the `default` environment.\n\n```toml config-example\n[tool.hatch.envs.default]\ndependencies = [\n  \"pydantic\",\n  \"numpy\",\n]\n```\n\nYou can declare versions for your dependencies as well within this configuration.\n\n```toml config-example\n[tool.hatch.envs.default]\ndependencies = [\n  \"pydantic>=2.0\",\n  \"numpy\",\n]\n```\n\n## Create custom environment\n\nYou can create custom environments in Hatch by adding a section to your `pyproject.toml` file `[tool.hatch.envs.<ENV_NAME>]`. Below you define an environment called `test` and you add the `pytest` and `pytest-cov` dependencies to that environment's configuration.\n\n```toml config-example\n[tool.hatch.envs.test]\ndependencies = [\n  \"pytest\",\n  \"pytest-cov\"\n]\n```\n\nThe first time that you call the test environment, Hatch will:\n\n1. Create the environment\n2. Install your project into that environment in [dev mode](../../config/environment/overview.md#dev-mode) (by default) along with its [dependencies](../../config/metadata.md#dependencies).\n3. Install the environment's [dependencies](../../config/environment/overview.md#dependencies)\n\n## Run commands within a specific environment\n\nHatch offers a unique environment feature that allows you run a specific command within a specific environment rather than needing to activate the environment as you would using a tool such as [Conda](https://conda.org) or [venv](https://docs.python.org/3/library/venv.html).\n\nFor instance, if you define an environment called `test` that contains the dependencies from the previous section, you can run the `pytest` command from the `test` environment using the syntax:\n\n```\nhatch run <ENV_NAME>:command\n```\n\nTo access the `test` environment and run `pytest`, you can run:\n\n```console\n$ hatch run test:pytest\n============================== test session starts ===============================\nplatform darwin -- Python 3.12.1, pytest-7.4.4, pluggy-1.3.0\nrootdir: /your/path/to/yourproject\ncollected 0 items\n```\n\n!!! note\n    `test:pytest` represents the name of the environment to call (`test`) and the command to run (`pytest`).\n\n## View current environments\n\nAbove you defined and created a new test environment in your `pyproject.toml` file. You can now use the [`env show`](../../cli/reference.md#hatch-env-show) command to see both the currently created environments and the dependencies in each environment.\n\n```\n$ hatch env show\n             Standalone\n┏━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┓\n┃ Name    ┃ Type    ┃ Dependencies ┃\n┡━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━┩\n│ default │ virtual │              │\n├─────────┼─────────┼──────────────┤\n│ test    │ virtual │ pytest       │\n│         │         │ pytest-cov   │\n└─────────┴─────────┴──────────────┘\n```\n\n!!! note\n    The output may have more columns depending on your environment configuration.\n\n## Locating environments\n\nTo see where your current environment is located you can use the [`env find`](../../cli/reference.md#hatch-env-find) command.\n\n```\n$ hatch env find test\n/your/path/Application Support/hatch/env/virtual/yourproject/twO2iQR3/test\n```\n\n!!! note\n    That path is what you would see on macOS but differs for each platform, and is [configurable](../../plugins/environment/virtual.md#location).\n\n## Launching a shell within a specific environment\n\nIf you wish to [launch a shell](../../environment.md#entering-environments) for a specific environment that you have created, like the previous `test` environment, you can use:\n\n```\nhatch -e test shell\n```\n\nOnce the environment is active, you can run commands like you would in any Python environment.\n\nNotice below that when running `pip list` in the test environment, you can see:\n\n1. That your package is installed in editable mode.\n2. That the environment contains both `pytest` and `pytest-cov` as specified above in the `pyproject.toml` file.\n\n```\n$ pip list\nPackage     Version Editable project location\n----------- ------- ----------------------------------------------------\ncoverage    7.4.1\niniconfig   2.0.0\npackaging   23.2\npip         23.3.1\npluggy      1.4.0\npytest      8.0.0\npytest-cov  4.1.0\nyourproject 0.1.0  /your/path/to/yourproject\n```\n\n## Conda environments\n\nIf you prefer to use [Conda](https://conda.org) environments with Hatch, you can check out the [hatch-conda plugin](https://github.com/OldGrumpyViking/hatch-conda).\n"
  },
  {
    "path": "docs/tutorials/python/manage.md",
    "content": "# Managing Python distributions\n\n-----\n\nThe [`python`](../../cli/reference.md#hatch-python) command group provides a set of commands to manage Python distributions that may be used by other tools.\n\n!!! note\n    When using environments, manual management is not necessary since by default Hatch will [automatically](../../plugins/environment/virtual.md#python-resolution) download and manage Python distributions internally when a requested version cannot be found.\n\n## Location\n\nThere are two ways to control where Python distributions are installed. Both methods make it so that each installed distribution is placed in a subdirectory of the configured location named after the distribution.\n\n1. The globally configured [default directory](../../config/hatch.md#python-installations) for Python installations.\n2. The `-d`/`--dir` option of every [`python`](../../cli/reference.md#hatch-python) subcommand, which takes precedence over the default directory.\n\n## Installation\n\nTo install a Python distribution, use the [`python install`](../../cli/reference.md#hatch-python-install) command. For example:\n\n```\nhatch python install 3.12\n```\n\nThis will:\n\n1. Download the `3.12` Python distribution\n2. Unpack it into a directory named `3.12` within the configured [default directory](../../config/hatch.md#python-installations) for Python installations\n3. Add the installation to the user PATH\n\nNow its `python` executable can be used by you or other tools.\n\n!!! note\n    For PATH changes to take effect in the current shell, you will need to restart it.\n\n### Multiple\n\nYou can install multiple Python distributions at once by providing multiple distribution names. For example:\n\n```\nhatch python install 3.12 3.11 pypy3.10\n```\n\nIf you would like to install all available Python distributions that are compatible with your system, use `all` as the distribution name:\n\n```\nhatch python install all\n```\n\n!!! tip\n    The commands for [updating](#updates) and [removing](#removal) also support this functionality.\n\n### Private\n\nBy default, installing Python distributions will add them to the user PATH. To disable this behavior, use the `--private` flag like so:\n\n```\nhatch python install 3.12 --private\n```\n\nThis when combined with the [directory option](#location) can be used to create private, isolated installations.\n\n## Listing distributions\n\nYou can see all of the available and installed Python distributions by using the [`python show`](../../cli/reference.md#hatch-python-show) command. For example, if you already installed the `3.12` distribution you may see something like this:\n\n```\n$ hatch python show\n    Installed\n┏━━━━━━┳━━━━━━━━━┓\n┃ Name ┃ Version ┃\n┡━━━━━━╇━━━━━━━━━┩\n│ 3.12 │ 3.12.7  │\n└──────┴─────────┘\n      Available\n┏━━━━━━━━━━┳━━━━━━━━━┓\n┃ Name     ┃ Version ┃\n┡━━━━━━━━━━╇━━━━━━━━━┩\n│ 3.7      │ 3.7.9   │\n├──────────┼─────────┤\n│ 3.8      │ 3.8.20  │\n├──────────┼─────────┤\n│ 3.9      │ 3.9.20  │\n├──────────┼─────────┤\n│ 3.10     │ 3.10.15 │\n├──────────┼─────────┤\n│ 3.11     │ 3.11.10 │\n├──────────┼─────────┤\n│ 3.13     │ 3.13.0  │\n├──────────┼─────────┤\n│ pypy2.7  │ 7.3.15  │\n├──────────┼─────────┤\n│ pypy3.9  │ 7.3.15  │\n├──────────┼─────────┤\n│ pypy3.10 │ 7.3.15  │\n└──────────┴─────────┘\n```\n\n## Finding installations\n\nThe Python executable of an installed distribution can be found by using the [`python find`](../../cli/reference.md#hatch-python-find) command. For example:\n\n```\n$ hatch python find 3.12\n/home/.local/share/hatch/pythons/3.12/python/bin/python3\n```\n\nYou can instead output its parent directory by using the `-p`/`--parent` flag:\n\n```\n$ hatch python find 3.12 --parent\n/home/.local/share/hatch/pythons/3.12/python/bin\n```\n\nThis is useful when other tools do not need to use the executable directly but require knowing the directory containing it.\n\n## Updates\n\nTo update installed Python distributions, use the [`python update`](../../cli/reference.md#hatch-python-update) command. For example:\n\n```\nhatch python update 3.12 3.11 pypy3.10\n```\n\nWhen there are no updates available for a distribution, a warning will be displayed:\n\n```\n$ hatch python update 3.12\nThe latest version is already installed: 3.12.7\n```\n\n## Removal\n\nTo remove installed Python distributions, use the [`python remove`](../../cli/reference.md#hatch-python-remove) command. For example:\n\n```\nhatch python remove 3.12 3.11 pypy3.10\n```\n"
  },
  {
    "path": "docs/tutorials/testing/overview.md",
    "content": "# Testing projects\n\n-----\n\nThe [`test`](../../cli/reference.md#hatch-test) command ([by default](../../config/internal/testing.md#customize-environment)) uses [pytest](https://github.com/pytest-dev/pytest) with select plugins and [coverage.py](https://github.com/nedbat/coveragepy). View the [testing configuration](../../config/internal/testing.md) for more information.\n\nThe majority of projects can be fully tested this way without the need for custom [environments](../../config/environment/overview.md).\n\n## Passing arguments\n\nWhen you run the `test` command without any arguments, `tests` is passed as the [default argument](../../config/internal/testing.md#default-arguments) to `pytest` (this assumes that you have a `tests` directory). For example, the following command invocation:\n\n```\nhatch test\n```\n\nwould be translated roughly to:\n\n```\npytest tests\n```\n\nYou can pass arguments to `pytest` by appending them to the `test` command. For example, the following command invocation:\n\n```\nhatch test -vv tests/test_foo.py::test_bar\n```\n\nwould be translated roughly to:\n\n```\npytest -vv tests/test_foo.py::test_bar\n```\n\nYou can force the treatment of arguments as positional by using the `--` separator, especially useful when built-in flags of the `test` command conflict with those of `pytest`, such as the `--help` flag. For example, the following command invocation:\n\n```\nhatch test -r -- -r fE -- tests\n```\n\nwould be translated roughly to:\n\n```\npytest -r fE -- tests\n```\n\n!!! note\n    It's important to ensure that `pytest` receives an argument instructing what to run/where to locate tests. It's default behavior is `.` meaning that it will exhaustively search for tests in the current directory. This can not just be slow but also lead to unexpected behavior.\n\n## Environment selection\n\n### Single environment\n\nIf no environment options are selected, the `test` command will only run tests in the first defined environment that either already exists or is compatible. Additionally, the checking order will prioritize environments that define a [version of Python](../../config/environment/overview.md#python-version) that matches the interpreter that Hatch is running on.\n\nFor example, if you overrode the [default matrix](../../config/internal/testing.md#matrix) as follows:\n\n```toml config-example\n[[tool.hatch.envs.hatch-test.matrix]]\npython = [\"3.12\", \"3.11\"]\n\n[[tool.hatch.envs.hatch-test.matrix]]\npython = [\"3.11\"]\nfeature = [\"foo\", \"bar\"]\n```\n\nthe expanded environments would normally be:\n\n```\nhatch-test.py3.12\nhatch-test.py3.11\nhatch-test.py3.11-foo\nhatch-test.py3.11-bar\n```\n\nIf you install Hatch on Python 3.11, the checking order would be:\n\n```\nhatch-test.py3.11\nhatch-test.py3.11-foo\nhatch-test.py3.11-bar\nhatch-test.py3.12\n```\n\n!!! note\n    If you installed Hatch with an official [installer](../../install.md#installers) or are using one of the [standalone binaries](../../install.md#standalone-binaries), the version of Python that Hatch runs on is out of your control. If you are relying on the single environment resolution behavior, consider [explicitly selecting environments](#specific-environments) based on the Python version instead.\n\n### All environments\n\nYou can run tests in all compatible environments by using the `--all` flag. For example, say you defined the matrix and [overrides](../../config/environment/advanced.md#option-overrides) as follows:\n\n```toml config-example\n[[tool.hatch.envs.hatch-test.matrix]]\npython = [\"3.12\", \"3.11\"]\nfeature = [\"foo\", \"bar\"]\n\n[tool.hatch.envs.hatch-test.overrides]\nmatrix.feature.platforms = [\n  { value = \"linux\", if = [\"foo\", \"bar\"] },\n  { value = \"windows\", if = [\"foo\"] },\n  { value = \"macos\", if = [\"bar\"] },\n]\n```\n\nThe following table shows the environments in which tests would be run:\n\n| Environment | Linux | Windows | macOS |\n| --- | --- | --- | --- |\n| `hatch-test.py3.12-foo` | :white_check_mark: | :white_check_mark: | :x: |\n| `hatch-test.py3.12-bar` | :white_check_mark: | :x: | :white_check_mark: |\n| `hatch-test.py3.11-foo` | :white_check_mark: | :white_check_mark: | :x: |\n| `hatch-test.py3.11-bar` | :white_check_mark: | :x: | :white_check_mark: |\n\n### Specific environments\n\nYou can select subsets of environments by using the `--include`/`-i` and `--exclude`/`-x` options. These options may be used to include or exclude certain matrix variables, optionally followed by specific comma-separated values, and may be selected multiple times.\n\nFor example, say you defined the matrix as follows:\n\n```toml config-example\n[[tool.hatch.envs.hatch-test.matrix]]\npython = [\"3.12\", \"3.11\"]\nfeature = [\"foo\", \"bar\", \"baz\"]\n```\n\nIf you wanted to run tests in all environments that have Python 3.12 and either the `foo` or `bar` feature, you could use the following command invocation:\n\n```\nhatch test -i python=3.12 -i feature=foo,bar\n```\n\nAlternatively, we could exclude the `baz` feature to achieve the same result:\n\n```\nhatch test -i python=3.12 -x feature=baz\n```\n\n!!! tip\n    Since selecting the version of Python is a common use case, you can use the `--python`/`-py` option as a shorthand. For example, the previous commands could have been written as:\n\n    ```\n    hatch test -py 3.12 -i feature=foo,bar\n    hatch test -py 3.12 -x feature=baz\n    ```\n\n## Measuring code coverage\n\nYou can enable [code coverage](https://github.com/nedbat/coveragepy) by using the `--cover` flag. For example, the following command invocation:\n\n```\nhatch test --cover\n```\n\nwould be translated roughly to:\n\n```\ncoverage run -m pytest tests\n```\n\nAfter tests run in all of the [selected environments](#environment-selection), the coverage data is combined and a report is shown. The `--cover-quiet` flag can be used to suppress the report and implicitly enables the `--cover` flag:\n\n```\nhatch test --cover-quiet\n```\n\n!!! note\n    Coverage data files are generated at the root of the project. Be sure to exclude them from version control with the following glob-style pattern:\n\n    ```\n    .coverage*\n    ```\n\n## Retry failed tests\n\nYou can [retry](https://github.com/pytest-dev/pytest-rerunfailures) failed tests with the `--retries` option:\n\n```\nhatch test --retries 2\n```\n\nIf a test fails every time and the number of retries is set to `2`, the test will be run a total of three times.\n\nYou can also set the number of seconds to wait between retries with the `--retry-delay` option:\n\n```\nhatch test --retries 2 --retry-delay 1\n```\n\n## Parallelize test execution\n\nYou can [parallelize](https://github.com/pytest-dev/pytest-xdist) test execution with the `--parallel`/`-p` flag:\n\n```\nhatch test --parallel\n```\n\nThis distributes tests within an environment across multiple workers. The number of workers corresponds to the number of logical rather than physical CPUs that are available.\n\n## Randomize test order\n\nYou can [randomize](https://github.com/pytest-dev/pytest-randomly) the order of tests with the `--randomize`/`-r` flag:\n\n```\nhatch test --randomize\n```\n"
  },
  {
    "path": "docs/version.md",
    "content": "# Versioning\n\n-----\n\n## Configuration\n\nWhen the version is not [statically set](config/metadata.md#version), configuration is defined in the `tool.hatch.version` table. The `source` option determines the [source](plugins/version-source/reference.md) to use for [retrieving](#display) and [updating](#updating) the version. The [regex](plugins/version-source/regex.md) source is used by default.\n\nThe `regex` source requires an option `path` that represents a relative path to a file containing the project's version:\n\n```toml config-example\n[tool.hatch.version]\npath = \"src/hatch_demo/__about__.py\"\n```\n\nThe default pattern looks for a variable named `__version__` or `VERSION` that is set to a string containing the version, optionally prefixed with the lowercase letter `v`.\n\nIf this doesn't reflect how you store the version, you can define a different regular expression using the `pattern` option:\n\n```toml config-example\n[tool.hatch.version]\npath = \"pkg/__init__.py\"\npattern = \"BUILD = 'b(?P<version>[^']+)'\"\n```\n\nThe pattern must have a named group called `version` that represents the version.\n\n## Display\n\nInvoking the [`version`](cli/reference.md#hatch-version) command without any arguments will display the current version of the project:\n\n```console\n$ hatch version\n0.0.1\n```\n\n## Updating\n\nYou can update the version like so:\n\n```console\n$ hatch version \"0.1.0\"\nOld: 0.0.1\nNew: 0.1.0\n```\n\nThe `scheme` option determines the [scheme](plugins/version-scheme/reference.md) to use for parsing both the existing and new versions. The [standard](plugins/version-scheme/standard.md) scheme is used by default, which is based on [PEP 440](https://peps.python.org/pep-0440/#public-version-identifiers).\n\nRather than setting the version explicitly, you can select the name of a [segment](#supported-segments) used to increment the version:\n\n```console\n$ hatch version minor\nOld: 0.1.0\nNew: 0.2.0\n```\n\nYou can chain multiple segment updates with a comma. For example, if you wanted to release a preview of your project's first major version, you could do:\n\n```console\n$ hatch version major,rc\nOld: 0.2.0\nNew: 1.0.0rc0\n```\n\nWhen you want to release the final version, you would do:\n\n```console\n$ hatch version release\nOld: 1.0.0rc0\nNew: 1.0.0\n```\n\n### Supported segments\n\nHere are the supported segments and how they would influence an existing version of `1.0.0`:\n\n| Segments | New version |\n| --- | --- |\n| `release` | `1.0.0` |\n| `major` | `2.0.0` |\n| `minor` | `1.1.0` |\n| `micro`<br>`patch`<br>`fix` | `1.0.1` |\n| `a`<br>`alpha` | `1.0.0a0` |\n| `b`<br>`beta` | `1.0.0b0` |\n| `c`<br>`rc`<br>`pre`<br>`preview` | `1.0.0rc0` |\n| `r`<br>`rev`<br>`post` | `1.0.0.post0` |\n| `dev` | `1.0.0.dev0` |\n"
  },
  {
    "path": "docs/why.md",
    "content": "# Why Hatch?\n\n-----\n\nThe high level value proposition of Hatch is that if one adopts all functionality then many other tools become unnecessary since there is support for everything one might require. Further, if one chooses to use only specific features then there are still benefits compared to alternatives.\n\n## Build backend\n\nHatchling, the [build backend](config/build.md#build-system) sister project, has many benefits compared to [setuptools](https://github.com/pypa/setuptools). Here we only compare setuptools as that is the one most people are familiar with.\n\n- **Better defaults:** The default behavior for setuptools is often not desirable for the average user.\n    - For source distributions, setuptools has a custom enumeration of files that get included and excluded by default. Hatchling takes the [defaults](plugins/builder/sdist.md#default-file-selection) from your version control system such as Git's `.gitignore` file.\n    - For wheels, setuptools attempts to find every directory that looks like a Python package. This is often undesirable as you might ship files to the end-user unintentionally such as test or tooling directories. Hatchling [defaults](plugins/builder/wheel.md#default-file-selection) to very specific inclusion based on the project name and errors if no heuristic is satisfied.\n- **Ease of configurability:** Hatchling was designed based on a history of significant challenges when configuring setuptools.\n    - Hatchling [uses](config/build.md#patterns) the same glob pattern syntax as Git itself for every option which is what most users are familiar with. On the other hand, setuptools uses shell-style glob patterns for source distributions while wheels use a mix of shell-style globs and Python package syntax.\n    - Configuring what gets included in source distributions requires a separate [`MANIFEST.in` file](https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html#using-manifest-in). The custom syntax and directives must be learned and it is difficult knowing which options in the main files like `setup.py` influence the behavior and under what conditions. For Hatchling, everything gets [configured](config/build.md) in a single file under dedicated sections for specific targets like `[tool.hatch.build.targets.wheel]`.\n    - By default, non-Python files are excluded from wheels. Including such files requires usually verbose rules for every nested package directory. Hatchling makes no such distinction between file types and acts more like a general build system one might already be familiar with.\n- **Editable installations:** The default behavior of Hatchling allows for proper static analysis by external tools such as IDEs. With setuptools, you must provide [additional configuration](https://setuptools.pypa.io/en/latest/userguide/development_mode.html#legacy-behavior) which means that by default, for example, you would not get autocompletion in Visual Studio Code. This is marked as a legacy feature and may in fact be removed in future versions of setuptools.\n- **Reproducibility:** Hatchling builds reproducible wheels and source distributions by default. setuptools [does not support this](https://github.com/pypa/setuptools/issues/2133) for source distributions and there is no guarantee that wheels are reproducible.\n- **Extensibility:** Although it is possible to [extend](https://setuptools.pypa.io/en/latest/userguide/extension.html) setuptools, the API is quite low level. Hatchling has the concept of [plugins](https://hatch.pypa.io/latest/plugins/about/) that are separated into discrete types and only expose what is necessary, leading to an easier developer experience.\n\n***Why not?:***\n\nIf building extension modules is required then it is recommended that you continue using setuptools, or even other backends that specialize in interfacing with compilers.\n\n## Environment management\n\nHere we compare to both `tox` and `nox`. At a high level, there are a few common advantages:\n\n- **Python management:** Hatch is able to automatically download [Python distributions](plugins/environment/virtual.md#internal-distributions) on the fly when specific versions that environments request cannot be found. The alternatives will raise an error, with the option to ignore unknown distributions.\n- **Philosophy:** In the alternatives, environments are for the most part treated as executable units where a dependency set is associated with an action. If you are familiar with container ecosystems, this would be like defining a `CMD` at the end of a Dockerfile but without the ability to change the action at runtime. This involves significant wasted disk space usually because one often requires slight modifications to the actions and therefore will define entirely different environments inherited from a base config just to perform different logic. Additionally, this can be confusing to users not just configuration-wise but also for execution of the different environments.\n\n    In Hatch, [environments](environment.md) are treated as isolated areas where you can execute arbitrary commands at runtime. For example, you can define a single test environment with named [scripts](config/environment/overview.md#scripts) that runs unit vs non-unit tests, each command being potentially very long but named however you wish so you get to control the interface. Since environments are treated as places where work is performed, you can also [spawn a shell](environment.md#entering-environments) into any which will execute a subprocess that automatically drops into your [shell of choice](config/hatch.md#shell). Your shell will be configured appropriately like `python` on PATH being updated and the prompt being changed to reflect the chosen environment.\n\n- **Configuration:**\n    - `nox` config is defined in Python which often leads to increased verbosity and makes it challenging to onboard folks compared to a standardized format with known behaviors.\n- **Extensibility:**\n    - `tox` allows for [extending](https://tox.wiki/en/4.11.4/plugins_api.html) most aspects of its functionality however the API is so low-level and attached to internals that creating plugins may be challenging. For example, [here](https://github.com/DataDog/integrations-core/blob/4f4cf10613797e97e7155c75859532a0732d1dff/datadog_checks_dev/datadog_checks/dev/plugin/tox.py) is a `tox` plugin that was [migrated](https://github.com/DataDog/integrations-core/blob/4eb2a1d530bcf810542cf9e45b48fadc7057301c/datadog_checks_dev/datadog_checks/dev/plugin/hatch/environment_collector.py#L100-L148) to an equivalent Hatch [environment collector plugin](plugins/environment-collector/reference.md).\n    - `nox` is configured with Python so for the local project you can do whatever you want, however there is no concept of third-party plugins per se. To achieve that, you must usually use a package that wraps `nox` and use that package's imports instead ([example](https://github.com/cjolowicz/nox-poetry)).\n\n***Why not?:***\n\nIf you are using `nox` and you wish to migrate, and for some reason you [notify](https://nox.thea.codes/en/stable/config.html#nox.sessions.Session.notify) sessions, then migration wouldn't be a straight translation but rather you might have to redesign that conditional step.\n\n## Python management\n\nHere we compare [Python management](tutorials/python/manage.md) to that of [pyenv](https://github.com/pyenv/pyenv).\n\n- ***Cross-platform:*** Hatch allows for the same experience no matter the system whereas `pyenv` does not support Windows so you must use an [entirely different project](https://github.com/pyenv-win/pyenv-win) that tries to emulate the functionality.\n- ***No build dependencies:*** Hatch guarantees that every [available distribution](cli/reference.md#hatch-python-show) is prebuilt whereas the alternative requires one to maintain a precise [build environment](https://github.com/pyenv/pyenv/wiki#suggested-build-environment) which differs by platform and potentially Python version. Another benefit to this is extremely fast installations since the distributions are simply downloaded and unpacked.\n- ***Optimized by default:*** The [CPython distributions](plugins/environment/virtual.md#cpython) are built with profile guided optimization and link-time optimization, resulting in a 10-30% performance improvement depending on the workload. These distributions have seen wide adoption throughout the industry and are even used by the build system [Bazel](https://bazel.build).\n- ***Simplicity:*** Hatch treats Python installations as just another directory that one would add to PATH. It can do this for you or you can manage PATH yourself, even allowing for custom install locations. On the other hand, `pyenv` operates by adding [shims](https://github.com/pyenv/pyenv/tree/74a2523c97d2e5c1dbdca7b58f3372324ccad4e6#understanding-shims) which then act as wrappers around the actual underlying binaries. This has many unfortunate side effects:\n    - It is incumbent upon the user to manage which specific Python comes first via the CLI, switch when necessary, and/or have a mental model of which versions are exposed globally and locally per-project. This can become confusing quite quickly. When working with Hatch, your global Python installations are only important insofar as they are on PATH somewhere since environments do not use them directly but rather create virtual environments from them, always using a version that is compatible with your project.\n    - Configuration is required for each shell to properly set up `pyenv` on start, leading to inconsistencies when running processes that do not spawn a shell.\n    - Debugging issues with Python search paths can be extremely difficult, especially for users of software. If you or users have ever ran into an issue where code was being executed that you did not anticipate, the issue is almost always `pyenv` influencing the `python` on PATH.\n\n***Why not?:***\n\nCurrently, Hatch does not allow for the installation of specific patch release versions but rather only uses minor release granularity that tracks the latest patch release. If specific patch releases are important to you then it is best to use an alternative installation mechanism.\n"
  },
  {
    "path": "hatch.toml",
    "content": "\n[envs.hatch-static-analysis]\nconfig-path = \"ruff_defaults.toml\"\n\n[envs.default]\ninstaller = \"uv\"\n\n\n[envs.hatch-test]\nworkspace.members = [\"backend/\"]\nextra-dependencies = [\n  \"filelock\",\n  \"flit-core\",\n  \"trustme\",\n  \"editables\",\n]\nextra-args = [\"--dist\", \"worksteal\"]\n\n[envs.hatch-test.extra-scripts]\npip = \"{env:HATCH_UV} pip {args}\"\n\n[envs.coverage]\ndetached = true\ndependencies = [\n  \"coverage[toml]>=6.2\",\n  \"lxml\",\n]\n[envs.coverage.scripts]\ncombine = \"coverage combine {args}\"\nreport-xml = \"coverage xml\"\nreport-uncovered-html = \"coverage html --skip-covered --skip-empty\"\ngenerate-summary = \"python scripts/generate_coverage_summary.py\"\nwrite-summary-report = \"python scripts/write_coverage_summary_report.py\"\n\n[envs.types]\nextra-dependencies = [\n  \"mypy>=1.0.0\",\n]\n[envs.types.scripts]\ncheck = \"mypy --install-types --non-interactive {args:backend/src/hatchling src/hatch tests}\"\n\n[envs.docs]\ndependencies = [\n  \"mkdocs~=1.6.0\",\n  \"mkdocs-material~=9.5.24\",\n  # Plugins\n  \"mkdocs-minify-plugin~=0.8.0\",\n  \"mkdocs-git-revision-date-localized-plugin~=1.2.5\",\n  \"mkdocs-git-committers-plugin-2~=2.3.0\",\n  \"mkdocstrings[python]~=0.26.0\",\n  \"mkdocs-redirects~=1.2.1\",\n  \"mkdocs-glightbox~=0.4.0\",\n  \"mike~=2.1.1\",\n  \"mkdocs-autorefs>=1.0.0\",\n  # Extensions\n  \"mkdocs-click~=0.8.1\",\n  \"pymdown-extensions~=10.8.1\",\n  # Necessary for syntax highlighting in code blocks\n  \"pygments~=2.18.0\",\n  # Validation\n  \"linkchecker~=10.5.0\",\n  \"griffe>=1.0.0\",\n]\npre-install-commands = [\n  \"python scripts/install_mkdocs_material_insiders.py\",\n]\n[envs.docs.overrides]\nenv.GH_TOKEN_MKDOCS_MATERIAL_INSIDERS.env-vars = [\n  { key = \"MKDOCS_CONFIG\", value = \"mkdocs.insiders.yml\" },\n  { key = \"MKDOCS_CONFIG\", value = \"mkdocs.yml\", if = [\"\"] },\n  { key = \"MKDOCS_IMAGE_PROCESSING\", value = \"true\" },\n]\n[envs.docs.env-vars]\nSOURCE_DATE_EPOCH = \"1580601600\"\nPYTHONUNBUFFERED = \"1\"\nMKDOCS_CONFIG = \"mkdocs.yml\"\n[envs.docs.scripts]\nbuild = \"mkdocs build --config-file {env:MKDOCS_CONFIG} --clean --strict {args}\"\nserve = \"mkdocs serve --config-file {env:MKDOCS_CONFIG} --dev-addr localhost:8000 {args}\"\nci-build = \"mike deploy --config-file {env:MKDOCS_CONFIG} --update-aliases {args}\"\nvalidate = \"linkchecker --config .linkcheckerrc site\"\n# https://github.com/linkchecker/linkchecker/issues/678\nbuild-check = [\n  \"python -W ignore::DeprecationWarning -m mkdocs build --no-directory-urls\",\n  \"validate\",\n]\n\n[envs.backend]\ndetached = true\ninstaller = \"uv\"\ndependencies = [\n  \"build~=0.7.0\",\n  \"httpx\",\n]\n[envs.backend.env-vars]\nHATCH_BUILD_CLEAN = \"true\"\n[envs.backend.scripts]\nbuild = \"python -m build backend\"\npublish = \"hatch publish backend/dist\"\nversion = \"cd backend && hatch version {args}\"\n\n[envs.upkeep]\ndetached = true\ninstaller = \"uv\"\ndependencies = [\n  \"httpx\",\n  \"ruff\",\n]\n[envs.upkeep.scripts]\nupdate-hatch = [\n  \"update-distributions\",\n  \"update-ruff\",\n]\nupdate-distributions = \"python scripts/update_distributions.py\"\nupdate-ruff = [\n  \"{env:HATCH_UV} pip install --upgrade ruff\",\n  \"python scripts/update_ruff.py\",\n]\n\n[envs.release]\nworkspace.members = [\"backend/\"]\ndetached = true\ninstaller = \"uv\"\n\n[envs.release.scripts]\nbump = \"python scripts/bump.py {args}\"\ngithub = \"python scripts/release_github.py {args}\"\n"
  },
  {
    "path": "mkdocs.insiders.yml",
    "content": "INHERIT: mkdocs.yml\n\nplugins:\n  git-committers:\n    repository: pypa/hatch\n    enabled: !ENV [GITHUB_ACTIONS, false]\n    token: !ENV [GH_TOKEN]\n  social:\n    cards_layout_options:\n      logo: docs/assets/images/logo.svg\n    enabled: !ENV [MKDOCS_IMAGE_PROCESSING, false]\n  material/blog:\n    categories_allowed:\n      - General\n      - News\n      - Release\n      - Roadmap\n    post_excerpt: required\n    post_slugify: !!python/object/apply:pymdownx.slugs.slugify\n      kwds:\n        case: lower\n    categories_slugify: !!python/object/apply:pymdownx.slugs.slugify\n      kwds:\n        case: lower\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Hatch\nsite_description: Modern, extensible Python project management\nsite_author: Ofek Lev\nsite_url: https://hatch.pypa.io\nrepo_name: pypa/hatch\nrepo_url: https://github.com/pypa/hatch\nedit_uri: blob/master/docs\ncopyright: 'Copyright &copy; Ofek Lev 2017-present'\n\ndocs_dir: docs\nsite_dir: site\ntheme:\n  name: material\n  custom_dir: docs/.overrides\n  language: en\n  favicon: assets/images/logo.svg\n  icon:\n    repo: fontawesome/brands/github-alt\n    logo: material/egg\n  font:\n    text: Roboto\n    code: Roboto Mono\n  palette:\n    - media: \"(prefers-color-scheme: dark)\"\n      scheme: slate\n      primary: indigo\n      accent: indigo\n      toggle:\n        icon: material/weather-night\n        name: Switch to light mode\n    - media: \"(prefers-color-scheme: light)\"\n      scheme: default\n      primary: indigo\n      accent: indigo\n      toggle:\n        icon: material/weather-sunny\n        name: Switch to dark mode\n  features:\n    - content.action.edit\n    - content.code.copy\n    - content.tabs.link\n    - content.tooltips\n    - navigation.expand\n    - navigation.footer\n    - navigation.instant\n    - navigation.sections\n    - navigation.tabs\n    - navigation.tabs.sticky\n\nnav:\n  - Home:\n    - About: index.md\n    - Walkthrough:\n      - Installation: install.md\n      - Introduction: intro.md\n      - Environments: environment.md\n      - Versioning: version.md\n      - Builds: build.md\n      - Publishing: publish.md\n    - Learn:\n      - Next steps: next-steps.md\n      - Why Hatch?: why.md\n    - History:\n      - Hatch: history/hatch.md\n      - Hatchling: history/hatchling.md\n    - Community:\n      - Users: community/users.md\n      - Highlights: community/highlights.md\n      - Contributing: community/contributing.md\n  - Configuration:\n    - Metadata: config/metadata.md\n    - Dependencies: config/dependency.md\n    - Build: config/build.md\n    - Environments:\n      - Overview: config/environment/overview.md\n      - Advanced: config/environment/advanced.md\n    - Internal:\n      - Testing: config/internal/testing.md\n      - Static analysis: config/internal/static-analysis.md\n      - Building: config/internal/build.md\n    - Context formatting: config/context.md\n    - Project templates: config/project-templates.md\n    - Hatch: config/hatch.md\n  - CLI:\n    - About: cli/about.md\n    - Reference: cli/reference.md\n  - Plugins:\n    - About: plugins/about.md\n    - Builder:\n      - Reference: plugins/builder/reference.md\n      - Wheel: plugins/builder/wheel.md\n      - Source distribution: plugins/builder/sdist.md\n      - Binary: plugins/builder/binary.md\n      - Custom: plugins/builder/custom.md\n    - Build hook:\n      - Reference: plugins/build-hook/reference.md\n      - Version: plugins/build-hook/version.md\n      - Custom: plugins/build-hook/custom.md\n    - Metadata hook:\n      - Reference: plugins/metadata-hook/reference.md\n      - Custom: plugins/metadata-hook/custom.md\n    - Environment:\n      - Reference: plugins/environment/reference.md\n      - Virtual: plugins/environment/virtual.md\n    - Environment collector:\n      - Reference: plugins/environment-collector/reference.md\n      - Custom: plugins/environment-collector/custom.md\n      - Default: plugins/environment-collector/default.md\n    - Publisher:\n      - Reference: plugins/publisher/reference.md\n      - Index: plugins/publisher/package-index.md\n    - Version source:\n      - Reference: plugins/version-source/reference.md\n      - Regex: plugins/version-source/regex.md\n      - Code: plugins/version-source/code.md\n      - Environment: plugins/version-source/env.md\n    - Version scheme:\n      - Reference: plugins/version-scheme/reference.md\n      - Standard: plugins/version-scheme/standard.md\n    - Utilities: plugins/utilities.md\n  - How-to:\n    - Meta:\n      - Report issues: how-to/meta/report-issues.md\n    - Integrate:\n      - Visual Studio Code: how-to/integrate/vscode.md\n    - Run:\n      - Python scripts: how-to/run/python-scripts.md\n    - Config:\n      - Dynamic metadata: how-to/config/dynamic-metadata.md\n    - Environments:\n      - Select installer: how-to/environment/select-installer.md\n      - Dependency resolution: how-to/environment/dependency-resolution.md\n      - Workspace: how-to/environment/workspace.md\n    - Static analysis:\n      - Customize behavior: how-to/static-analysis/behavior.md\n    - Python:\n      - Custom distributions: how-to/python/custom.md\n    - Publishing:\n      - Authentication: how-to/publish/auth.md\n      - Repository selection: how-to/publish/repo.md\n    - Plugins:\n      - Testing builds: how-to/plugins/testing-builds.md\n  - Tutorials:\n    - Python:\n      - Management: tutorials/python/manage.md\n    - Environments:\n      - Basic usage: tutorials/environment/basic-usage.md\n    - Testing:\n      - Overview: tutorials/testing/overview.md\n  - Meta:\n    - FAQ: meta/faq.md\n    - Authors: meta/authors.md\n  - Blog:\n    - blog/index.md\n\nwatch:\n- backend/src/hatchling\n- src/hatch\n\nhooks:\n- docs/.hooks/plugin_register.py\n- docs/.hooks/title_from_content.py\n\nplugins:\n  # Enable for bug reports\n  # info: {}\n  # Built-in\n  search: {}\n  # Extra\n  glightbox: {}\n  minify:\n    minify_html: true\n  git-revision-date-localized:\n    type: date\n    strict: false\n    # Required for blog plugin's generated indices\n    fallback_to_build_date: true\n    exclude:\n      - blog/**/*\n  mike:\n    alias_type: copy\n  mkdocstrings:\n    default_handler: python\n    handlers:\n      python:\n        paths:\n          - src\n        options:\n          # Headings\n          show_root_heading: true\n          show_root_full_path: false\n          # Docstrings\n          show_if_no_docstring: true\n          # Signatures/annotations\n          show_signature_annotations: true\n          # Other\n          show_bases: false\n  redirects:\n    redirect_maps:\n      config/environment.md: config/environment/overview.md\n      config/static-analysis.md: config/internal/static-analysis.md\n      history.md: history/hatch.md\n      how-to/environment/package-indices.md: how-to/environment/dependency-resolution.md\n      plugins/builder.md: plugins/builder/reference.md\n      plugins/build-hook.md: plugins/build-hook/reference.md\n      plugins/metadata-hook.md: plugins/metadata-hook/reference.md\n      plugins/environment.md: plugins/environment/reference.md\n      plugins/environment-collector.md: plugins/environment-collector/reference.md\n      plugins/publisher.md: plugins/publisher/reference.md\n      plugins/version-source.md: plugins/version-source/reference.md\n      plugins/version-scheme.md: plugins/version-scheme/reference.md\n      plugins/builder/app.md: plugins/builder/binary.md\n      users.md: community/users.md\n\nmarkdown_extensions:\n  # Built-in\n  - markdown.extensions.abbr:\n  - markdown.extensions.admonition:\n  - markdown.extensions.attr_list:\n  - markdown.extensions.footnotes:\n  - markdown.extensions.md_in_html:\n  - markdown.extensions.meta:\n  - markdown.extensions.tables:\n  - markdown.extensions.toc:\n      permalink: true\n  # Extra\n  - mkdocs-click:\n  - pymdownx.arithmatex:\n  - pymdownx.betterem:\n      smart_enable: all\n  - pymdownx.caret:\n  - pymdownx.critic:\n  - pymdownx.details:\n  - pymdownx.emoji:\n      # https://github.com/twitter/twemoji\n      # https://raw.githubusercontent.com/facelessuser/pymdown-extensions/master/pymdownx/twemoji_db.py\n      emoji_index: !!python/name:material.extensions.emoji.twemoji\n      emoji_generator: !!python/name:material.extensions.emoji.to_svg\n  - pymdownx.highlight:\n      guess_lang: false\n      linenums_style: pymdownx-inline\n      use_pygments: true\n  - pymdownx.inlinehilite:\n  - pymdownx.keys:\n  - pymdownx.magiclink:\n      repo_url_shortener: true\n      repo_url_shorthand: true\n      social_url_shortener: true\n      social_url_shorthand: true\n      normalize_issue_symbols: true\n      provider: github\n      user: pypa\n      repo: hatch\n  - pymdownx.mark:\n  - pymdownx.progressbar:\n  - pymdownx.saneheaders:\n  - pymdownx.smartsymbols:\n  - pymdownx.snippets:\n      check_paths: true\n      base_path:\n        - docs/.snippets\n      auto_append:\n        - links.txt\n        - abbrs.txt\n  - pymdownx.superfences:\n  - pymdownx.tabbed:\n      alternate_style: true\n      slugify: !!python/object/apply:pymdownx.slugs.slugify\n        kwds:\n          case: lower\n  - pymdownx.tasklist:\n      custom_checkbox: true\n  - pymdownx.tilde:\n\nextra:\n  version:\n    provider: mike\n  social:\n    - icon: fontawesome/brands/github-alt\n      link: https://github.com/ofek\n    - icon: fontawesome/solid/blog\n      link: https://ofek.dev/words/\n    - icon: fontawesome/brands/twitter\n      link: https://twitter.com/Ofekmeister\n    - icon: fontawesome/brands/linkedin\n      link: https://www.linkedin.com/in/ofeklev/\nextra_css:\n  - assets/css/custom.css\n  - https://cdn.jsdelivr.net/npm/firacode@6.2.0/distr/fira_code.css\n"
  },
  {
    "path": "pyoxidizer.bzl",
    "content": "VERSION = VARS[\"version\"]\nAPP_NAME = \"hatch\"\nDISPLAY_NAME = \"Hatch\"\nAUTHOR = \"Python Packaging Authority\"\n\n\ndef make_msi(target):\n    if target == \"x86_64-pc-windows-msvc\":\n        arch = \"x64\"\n    elif target == \"i686-pc-windows-msvc\":\n        arch = \"x86\"\n    else:\n        arch = \"unknown\"\n\n    # https://gregoryszorc.com/docs/pyoxidizer/main/tugger_starlark_type_wix_msi_builder.html\n    msi = WiXMSIBuilder(\n        id_prefix=APP_NAME,\n        product_name=DISPLAY_NAME,\n        product_version=VERSION,\n        product_manufacturer=AUTHOR,\n        arch=arch,\n    )\n    msi.msi_filename = APP_NAME + \"-\" + arch + \".msi\"\n    msi.help_url = \"https://hatch.pypa.io/latest/\"\n    msi.license_path = CWD + \"/LICENSE.txt\"\n\n    # https://gregoryszorc.com/docs/pyoxidizer/main/tugger_starlark_type_file_manifest.html\n    m = FileManifest()\n\n    exe_prefix = \"targets/\" + target + \"/\"\n    m.add_path(\n        path=exe_prefix + APP_NAME + \".exe\",\n        strip_prefix=exe_prefix,\n    )\n\n    msi.add_program_files_manifest(m)\n\n    return msi\n\n\ndef make_exe_installer():\n    # https://gregoryszorc.com/docs/pyoxidizer/main/tugger_starlark_type_wix_bundle_builder.html\n    bundle = WiXBundleBuilder(\n        id_prefix=APP_NAME,\n        name=DISPLAY_NAME,\n        version=VERSION,\n        manufacturer=AUTHOR,\n    )\n\n    bundle.add_vc_redistributable(\"x64\")\n    bundle.add_vc_redistributable(\"x86\")\n\n    bundle.add_wix_msi_builder(\n        builder=make_msi(\"x86_64-pc-windows-msvc\"),\n        display_internal_ui=True,\n        install_condition=\"VersionNT64\",\n    )\n    bundle.add_wix_msi_builder(\n        builder=make_msi(\"i686-pc-windows-msvc\"),\n        display_internal_ui=True,\n        install_condition=\"Not VersionNT64\",\n    )\n\n    return bundle\n\n\ndef make_macos_universal_binary():\n    # https://gregoryszorc.com/docs/pyoxidizer/main/tugger_starlark_type_apple_universal_binary.html\n    universal = AppleUniversalBinary(APP_NAME)\n\n    for target in [\"aarch64-apple-darwin\", \"x86_64-apple-darwin\"]:\n        universal.add_path(\"targets/\" + target + \"/\" + APP_NAME)\n\n    m = FileManifest()\n    m.add_file(universal.to_file_content())\n    return m\n\n\nregister_target(\"windows_installers\", make_exe_installer, default=True)\nregister_target(\"macos_universal_binary\", make_macos_universal_binary)\n\nresolve_targets()\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling>=1.27\", \"hatch-vcs>=0.3.0\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"hatch\"\ndescription = \"Modern, extensible Python project management\"\nreadme = \"README.md\"\nlicense = \"MIT\"\nlicense-files = [\"LICENSE.txt\"]\nrequires-python = \">=3.10\"\nkeywords = [\n  \"build\",\n  \"dependency\",\n  \"environment\",\n  \"hatch\",\n  \"packaging\",\n  \"plugin\",\n  \"publishing\",\n  \"release\",\n  \"versioning\",\n]\nauthors = [\n  { name = \"Ofek Lev\", email = \"oss@ofek.dev\" },\n  { name = \"Cary Hawkins\", email = \"hawkinscary23@gmail.com\" },\n]\nclassifiers = [\n  \"Development Status :: 5 - Production/Stable\",\n  \"Intended Audience :: Developers\",\n  \"Natural Language :: English\",\n  \"Operating System :: OS Independent\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: 3.13\",\n  \"Programming Language :: Python :: 3.14\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n  \"Topic :: Software Development :: Build Tools\",\n]\ndependencies = [\n  \"click>=8.0.6\",\n  \"hatchling>=1.27.0\",\n  \"httpx>=0.22.0\",\n  \"hyperlink>=21.0.0\",\n  \"keyring>=23.5.0\",\n  \"packaging>=24.2\",\n  \"pexpect~=4.8\",\n  \"python-discovery>=1.1\",\n  \"platformdirs>=2.5.0\",\n  \"pyproject-hooks\",\n  \"rich>=11.2.0\",\n  \"shellingham>=1.4.0\",\n  \"tomli-w>=1.0\",\n  \"tomlkit>=0.11.1\",\n  \"userpath~=1.7\",\n  \"uv>=0.5.23\",\n  \"virtualenv>=21\",\n  \"backports.zstd>=1.0.0 ; python_version<'3.14'\",\n]\ndynamic = [\"version\"]\n\n[project.urls]\nHomepage = \"https://hatch.pypa.io/latest/\"\nSponsor = \"https://github.com/sponsors/ofek\"\nHistory = \"https://hatch.pypa.io/dev/history/hatch/\"\nTracker = \"https://github.com/pypa/hatch/issues\"\nSource = \"https://github.com/pypa/hatch\"\n\n[project.scripts]\nhatch = \"hatch.cli:main\"\n\n[tool.hatch.version]\nsource = \"vcs\"\n\n[tool.hatch.version.raw-options]\nversion_scheme = \"python-simplified-semver\"\nlocal_scheme = \"no-local-version\"\nparentdir_prefix_version = \"hatch-\"\ngit_describe_command = [\"git\", \"describe\", \"--dirty\", \"--tags\", \"--long\", \"--match\", \"hatch-v*\"]\n\n[tool.hatch.build.hooks.vcs]\nversion-file = \"src/hatch/_version.py\"\n\n[tool.hatch.build.targets.sdist]\nexclude = [\n  \"/.github\",\n  \"/backend\",\n  \"/scripts\",\n]\n\n[tool.mypy]\ndisallow_untyped_defs = false\ndisallow_incomplete_defs = false\nenable_error_code = [\"ignore-without-code\", \"truthy-bool\"]\nfollow_imports = \"normal\"\nignore_missing_imports = true\npretty = true\nshow_column_numbers = true\nwarn_no_return = false\nwarn_unused_ignores = true\n\n[[tool.mypy.overrides]]\nmodule = [\n    \"*.hatchling.*\",\n    \"*.hatch.utils.*\",\n]\ndisallow_untyped_defs = true\ndisallow_incomplete_defs = true\nwarn_no_return = true\n\n[tool.coverage.run]\nbranch = true\nsource_pkgs = [\"hatch\", \"hatchling\", \"tests\"]\nomit = [\n  \"backend/src/hatchling/__main__.py\",\n  \"backend/src/hatchling/bridge/*\",\n  \"backend/src/hatchling/cli/dep/*\",\n  \"backend/src/hatchling/ouroboros.py\",\n  \"src/hatch/__main__.py\",\n  \"src/hatch/cli/new/migrate.py\",\n  \"src/hatch/project/frontend/scripts/*\",\n  \"src/hatch/utils/shells.py\",\n]\n\n[tool.coverage.paths]\nhatch = [\"src/hatch\", \"*/hatch/src/hatch\"]\nhatchling = [\"backend/src/hatchling\", \"*/hatch/backend/src/hatchling\"]\ntests = [\"tests\", \"*/hatch/tests\"]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"no cov\",\n  \"if __name__ == .__main__.:\",\n  \"if TYPE_CHECKING:\",\n]\n"
  },
  {
    "path": "release/README.md",
    "content": "# Release assets\n\n-----\n\nThis directory stores files related to building binaries and installers for each platform.\n"
  },
  {
    "path": "release/macos/build_pkg.py",
    "content": "\"\"\"\nThis script must be run from the root of the repository.\n\nAt a high level, the goal is to have a directory that emulates the full path structure of the\ntarget machine which then gets packaged by tools that are only available on macOS.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport shutil\nimport subprocess\nimport sys\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\n\nREPO_DIR = Path.cwd()\nASSETS_DIR = Path(__file__).parent / \"pkg\"\nIDENTIFIER = \"org.python.hatch\"\nCOMPONENT_PACKAGE_NAME = f\"{IDENTIFIER}.pkg\"\nREADME = \"\"\"\\\n<!DOCTYPE html>\n<html>\n<head></head>\n<body>\n  <p>This will install Hatch v{version} globally.</p>\n\n  <p>For more information on installing and upgrading Hatch, see our <a href=\"https://hatch.pypa.io/latest/install/\">Installation Guide</a>.</p>\n</body>\n</html>\n\"\"\"\n\n\ndef run_command(command: list[str]) -> None:\n    process = subprocess.run(command)  # noqa: PLW1510\n    if process.returncode:\n        sys.exit(process.returncode)\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"directory\")\n    parser.add_argument(\"--binary\", required=True)\n    parser.add_argument(\"--version\", required=True)\n    args = parser.parse_args()\n\n    directory = Path(args.directory).absolute()\n    staged_binary = Path(args.binary).absolute()\n    binary_name = staged_binary.stem\n    version = args.version\n\n    with TemporaryDirectory() as d:\n        temp_dir = Path(d)\n\n        # This is where we assemble files required for builds\n        resources_dir = temp_dir / \"resources\"\n        shutil.copytree(str(ASSETS_DIR / \"resources\"), str(resources_dir))\n\n        resources_dir.joinpath(\"README.html\").write_text(README.format(version=version), encoding=\"utf-8\")\n        shutil.copy2(REPO_DIR / \"LICENSE.txt\", resources_dir)\n\n        # This is what gets shipped to users starting at / (the root directory)\n        root_dir = temp_dir / \"root\"\n        root_dir.mkdir()\n\n        # This is where we globally install Hatch. We choose to not offer per-user installs because we can't\n        # find out where the location is and therefore cannot add to PATH usually. For more information, see:\n        # https://github.com/aws/aws-cli/commit/f3c3eb8262786142a1712b6da5a1515ad9dc66c5\n        relative_binary_dir = Path(\"usr\", \"local\", binary_name, \"bin\")\n        binary_dir = root_dir / relative_binary_dir\n        binary_dir.mkdir(parents=True)\n        shutil.copy2(staged_binary, binary_dir)\n\n        # This is how we add the installation directory to PATH and is also what Go does,\n        # although there are some caveats: https://apple.stackexchange.com/q/126725\n        path_file = root_dir / \"etc\" / \"paths.d\" / binary_name\n        path_file.parent.mkdir(parents=True)\n        path_file.write_text(f\"/{relative_binary_dir}\\n\", encoding=\"utf-8\")\n\n        # This is where we build the intermediate components\n        components_dir = temp_dir / \"components\"\n        components_dir.mkdir()\n\n        run_command([\n            \"pkgbuild\",\n            \"--root\",\n            str(root_dir),\n            \"--identifier\",\n            IDENTIFIER,\n            \"--version\",\n            version,\n            \"--install-location\",\n            \"/\",\n            str(components_dir / COMPONENT_PACKAGE_NAME),\n        ])\n\n        # This is where we build the final artifact\n        build_dir = temp_dir / \"build\"\n        build_dir.mkdir()\n        product_archive = build_dir / f\"{binary_name}-universal.pkg\"\n\n        run_command([\n            \"productbuild\",\n            \"--distribution\",\n            str(ASSETS_DIR / \"distribution.xml\"),\n            \"--resources\",\n            str(resources_dir),\n            \"--package-path\",\n            str(components_dir),\n            str(product_archive),\n        ])\n\n        # Copy the final artifact to the target directory\n        directory.mkdir(parents=True, exist_ok=True)\n        shutil.copy2(product_archive, directory)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "release/macos/pkg/distribution.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<installer-gui-script minSpecVersion=\"1\">\n  <!--\n  https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html\n  -->\n  <title>Hatch</title>\n  <license file=\"LICENSE.txt\" mime-type=\"text/plain\"/>\n  <readme file=\"README.html\" mime-type=\"text/html\"/>\n  <background mime-type=\"image/png\" file=\"icon.png\" alignment=\"left\" scaling=\"proportional\"/>\n  <background-darkAqua mime-type=\"image/png\" file=\"icon.png\" alignment=\"left\" scaling=\"proportional\"/>\n  <options hostArchitectures=\"arm64,x86_64\" customize=\"never\" require-scripts=\"false\"/>\n  <domains enable_localSystem=\"true\"/>\n\n  <choices-outline>\n    <line choice=\"org.python.hatch.choice\"/>\n  </choices-outline>\n  <choice title=\"Hatch (universal)\" id=\"org.python.hatch.choice\">\n    <pkg-ref id=\"org.python.hatch.pkg\"/>\n  </choice>\n\n  <pkg-ref id=\"org.python.hatch.pkg\">org.python.hatch.pkg</pkg-ref>\n</installer-gui-script>\n"
  },
  {
    "path": "release/unix/make_scripts_portable.py",
    "content": "from __future__ import annotations\n\nimport sys\nimport sysconfig\nfrom io import BytesIO\nfrom pathlib import Path\n\n\ndef main():\n    interpreter = Path(sys.executable).resolve()\n\n    # https://github.com/indygreg/python-build-standalone/blob/20240415/cpython-unix/build-cpython.sh#L812-L813\n    portable_shebang = b'#!/bin/sh\\n\"exec\" \"$(dirname $0)/%s\" \"$0\" \"$@\"\\n' % interpreter.name.encode()\n\n    scripts_dir = Path(sysconfig.get_path(\"scripts\"))\n    for script in scripts_dir.iterdir():\n        if not script.is_file():\n            continue\n\n        with script.open(\"rb\") as f:\n            data = BytesIO()\n            for line in f:\n                # Ignore leading blank lines\n                if not line.strip():\n                    continue\n\n                # Ignore binaries\n                if not line.startswith(b\"#\"):\n                    break\n\n                if line.startswith(b\"#!%s\" % interpreter.parent):\n                    executable = Path(line[2:].rstrip().decode()).resolve()\n                    data.write(portable_shebang if executable == interpreter else line)\n                else:\n                    data.write(line)\n\n                data.write(f.read())\n                break\n\n        contents = data.getvalue()\n        if not contents:\n            continue\n\n        with script.open(\"wb\") as f:\n            f.write(contents)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "release/windows/make_scripts_portable.py",
    "content": "from __future__ import annotations\n\nimport sys\nimport sysconfig\nfrom contextlib import closing\nfrom importlib.metadata import entry_points\nfrom io import BytesIO\nfrom os.path import relpath\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nfrom urllib.request import urlopen\nfrom zipfile import ZIP_DEFLATED, ZipFile, ZipInfo\n\nLAUNCHERS_URL = \"https://raw.githubusercontent.com/astral-sh/uv/main/crates/uv-trampoline-builder/trampolines\"\nSCRIPT_TEMPLATE = \"\"\"\\\n#!{executable}\nimport re\nimport sys\nfrom {module} import {import_name}\nif __name__ == \"__main__\":\n    sys.argv[0] = re.sub(r\"(-script\\\\.pyw|\\\\.exe)?$\", \"\", sys.argv[0])\n    sys.exit({function}())\n\"\"\"\n\n\ndef select_entry_points(ep, group):\n    return ep.select(group=group) if sys.version_info[:2] >= (3, 10) else ep.get(group, [])\n\n\ndef fetch_launcher(launcher_name):\n    with urlopen(f\"{LAUNCHERS_URL}/{launcher_name}\") as f:  # noqa: S310\n        return f.read()\n\n\ndef main():\n    interpreters_dir = Path(sys.executable).parent\n    scripts_dir = Path(sysconfig.get_path(\"scripts\"))\n\n    ep = entry_points()\n    for group, interpreter_name, launcher_name in (\n        (\"console_scripts\", \"python.exe\", \"uv-trampoline-x86_64-console.exe\"),\n        (\"gui_scripts\", \"pythonw.exe\", \"uv-trampoline-x86_64-gui.exe\"),\n    ):\n        interpreter = interpreters_dir / interpreter_name\n        relative_interpreter_path = relpath(interpreter, scripts_dir)\n        launcher_data = fetch_launcher(launcher_name)\n\n        for script in select_entry_points(ep, group):\n            # https://github.com/astral-sh/uv/tree/main/crates/uv-trampoline#how-do-you-use-it\n            with closing(BytesIO()) as buf:\n                # Launcher\n                buf.write(launcher_data)\n\n                # Zipped script\n                with TemporaryDirectory() as td:\n                    zip_path = Path(td) / \"script.zip\"\n                    with ZipFile(zip_path, \"w\") as zf:\n                        # Ensure reproducibility\n                        zip_info = ZipInfo(\"__main__.py\", (2020, 2, 2, 0, 0, 0))\n                        zip_info.external_attr = (0o644 & 0xFFFF) << 16\n\n                        module, _, attrs = script.value.partition(\":\")\n                        contents = SCRIPT_TEMPLATE.format(\n                            executable=relative_interpreter_path,\n                            module=module,\n                            import_name=attrs.split(\".\")[0],\n                            function=attrs,\n                        )\n                        zf.writestr(zip_info, contents, compress_type=ZIP_DEFLATED)\n\n                    buf.write(zip_path.read_bytes())\n\n                # Interpreter path\n                interpreter_path = relative_interpreter_path.encode(\"utf-8\")\n                buf.write(interpreter_path)\n\n                # Interpreter path length\n                interpreter_path_length = len(interpreter_path).to_bytes(4, \"little\")\n                buf.write(interpreter_path_length)\n\n                # Magic number\n                buf.write(b\"UVUV\")\n\n                script_data = buf.getvalue()\n\n            script_path = scripts_dir / f\"{script.name}.exe\"\n            script_path.write_bytes(script_data)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "ruff.toml",
    "content": "extend = \"ruff_defaults.toml\"\n\n# https://github.com/astral-sh/ruff/issues/8627\nexclude = [\".git\", \".mypy_cache\", \".ruff_cache\", \".venv\", \"dist\"]\n\n[format]\npreview = true\n\n[lint]\npreview = true\nignore = [\n  # Allow lazy imports for responsive CLI\n  \"PLC0415\",\n]\n\n[lint.extend-per-file-ignores]\n\"backend/src/hatchling/bridge/app.py\" = [\"T201\"]\n\"backend/tests/downstream/integrate.py\" = [\"INP001\", \"T201\"]\n\"docs/.hooks/*\" = [\"INP001\", \"T201\"]\n\"release/**/*\" = [\"INP001\"]\n\n[lint.isort]\nknown-first-party = [\"hatch\", \"hatchling\"]\n"
  },
  {
    "path": "ruff_defaults.toml",
    "content": "line-length = 120\n\n[format]\ndocstring-code-format = true\ndocstring-code-line-length = 80\n\n[lint]\nselect = [\n  \"A001\",\n  \"A002\",\n  \"A003\",\n  \"ARG001\",\n  \"ARG001\",\n  \"ARG002\",\n  \"ARG003\",\n  \"ARG004\",\n  \"ARG005\",\n  \"ASYNC100\",\n  \"B002\",\n  \"B003\",\n  \"B004\",\n  \"B005\",\n  \"B006\",\n  \"B007\",\n  \"B008\",\n  \"B009\",\n  \"B010\",\n  \"B011\",\n  \"B012\",\n  \"B013\",\n  \"B014\",\n  \"B015\",\n  \"B016\",\n  \"B017\",\n  \"B018\",\n  \"B019\",\n  \"B020\",\n  \"B021\",\n  \"B022\",\n  \"B023\",\n  \"B024\",\n  \"B025\",\n  \"B026\",\n  \"B028\",\n  \"B029\",\n  \"B030\",\n  \"B031\",\n  \"B032\",\n  \"B033\",\n  \"B034\",\n  \"B035\",\n  \"B904\",\n  \"B905\",\n  \"B909\",\n  \"BLE001\",\n  \"C400\",\n  \"C401\",\n  \"C402\",\n  \"C403\",\n  \"C404\",\n  \"C405\",\n  \"C406\",\n  \"C408\",\n  \"C409\",\n  \"C410\",\n  \"C411\",\n  \"C413\",\n  \"C414\",\n  \"C415\",\n  \"C416\",\n  \"C417\",\n  \"C418\",\n  \"C419\",\n  \"COM818\",\n  \"DTZ001\",\n  \"DTZ002\",\n  \"DTZ003\",\n  \"DTZ004\",\n  \"DTZ005\",\n  \"DTZ006\",\n  \"DTZ007\",\n  \"DTZ011\",\n  \"DTZ012\",\n  \"E101\",\n  \"E112\",\n  \"E113\",\n  \"E115\",\n  \"E116\",\n  \"E201\",\n  \"E202\",\n  \"E203\",\n  \"E211\",\n  \"E221\",\n  \"E222\",\n  \"E223\",\n  \"E224\",\n  \"E225\",\n  \"E226\",\n  \"E227\",\n  \"E228\",\n  \"E231\",\n  \"E241\",\n  \"E242\",\n  \"E251\",\n  \"E252\",\n  \"E261\",\n  \"E262\",\n  \"E265\",\n  \"E266\",\n  \"E271\",\n  \"E272\",\n  \"E273\",\n  \"E274\",\n  \"E275\",\n  \"E401\",\n  \"E402\",\n  \"E502\",\n  \"E701\",\n  \"E702\",\n  \"E703\",\n  \"E711\",\n  \"E712\",\n  \"E713\",\n  \"E714\",\n  \"E721\",\n  \"E722\",\n  \"E731\",\n  \"E741\",\n  \"E742\",\n  \"E743\",\n  \"E902\",\n  \"EM101\",\n  \"EM102\",\n  \"EM103\",\n  \"EXE001\",\n  \"EXE002\",\n  \"EXE003\",\n  \"EXE004\",\n  \"EXE005\",\n  \"F401\",\n  \"F402\",\n  \"F403\",\n  \"F404\",\n  \"F405\",\n  \"F406\",\n  \"F407\",\n  \"F501\",\n  \"F502\",\n  \"F503\",\n  \"F504\",\n  \"F505\",\n  \"F506\",\n  \"F507\",\n  \"F508\",\n  \"F509\",\n  \"F521\",\n  \"F522\",\n  \"F523\",\n  \"F524\",\n  \"F525\",\n  \"F541\",\n  \"F601\",\n  \"F602\",\n  \"F621\",\n  \"F622\",\n  \"F631\",\n  \"F632\",\n  \"F633\",\n  \"F634\",\n  \"F701\",\n  \"F702\",\n  \"F704\",\n  \"F706\",\n  \"F707\",\n  \"F722\",\n  \"F811\",\n  \"F821\",\n  \"F822\",\n  \"F823\",\n  \"F841\",\n  \"F842\",\n  \"F901\",\n  \"FA100\",\n  \"FA102\",\n  \"FBT001\",\n  \"FBT002\",\n  \"FLY002\",\n  \"FURB105\",\n  \"FURB110\",\n  \"FURB113\",\n  \"FURB116\",\n  \"FURB118\",\n  \"FURB129\",\n  \"FURB131\",\n  \"FURB132\",\n  \"FURB136\",\n  \"FURB142\",\n  \"FURB145\",\n  \"FURB148\",\n  \"FURB152\",\n  \"FURB157\",\n  \"FURB161\",\n  \"FURB163\",\n  \"FURB164\",\n  \"FURB166\",\n  \"FURB167\",\n  \"FURB168\",\n  \"FURB169\",\n  \"FURB171\",\n  \"FURB177\",\n  \"FURB180\",\n  \"FURB181\",\n  \"FURB187\",\n  \"FURB192\",\n  \"G001\",\n  \"G002\",\n  \"G003\",\n  \"G004\",\n  \"G010\",\n  \"G101\",\n  \"G201\",\n  \"G202\",\n  \"I001\",\n  \"I002\",\n  \"ICN001\",\n  \"ICN002\",\n  \"ICN003\",\n  \"INP001\",\n  \"INT001\",\n  \"INT002\",\n  \"INT003\",\n  \"ISC003\",\n  \"LOG001\",\n  \"LOG002\",\n  \"LOG007\",\n  \"LOG009\",\n  \"N801\",\n  \"N802\",\n  \"N803\",\n  \"N804\",\n  \"N805\",\n  \"N806\",\n  \"N807\",\n  \"N811\",\n  \"N812\",\n  \"N813\",\n  \"N814\",\n  \"N815\",\n  \"N816\",\n  \"N817\",\n  \"N818\",\n  \"N999\",\n  \"PERF101\",\n  \"PERF102\",\n  \"PERF401\",\n  \"PERF402\",\n  \"PERF403\",\n  \"PGH005\",\n  \"PIE790\",\n  \"PIE794\",\n  \"PIE796\",\n  \"PIE800\",\n  \"PIE804\",\n  \"PIE807\",\n  \"PIE808\",\n  \"PIE810\",\n  \"PLC0105\",\n  \"PLC0131\",\n  \"PLC0132\",\n  \"PLC0205\",\n  \"PLC0208\",\n  \"PLC0414\",\n  \"PLC0415\",\n  \"PLC1901\",\n  \"PLC2401\",\n  \"PLC2403\",\n  \"PLC2701\",\n  \"PLC2801\",\n  \"PLC3002\",\n  \"PLE0100\",\n  \"PLE0101\",\n  \"PLE0115\",\n  \"PLE0116\",\n  \"PLE0117\",\n  \"PLE0118\",\n  \"PLE0237\",\n  \"PLE0241\",\n  \"PLE0302\",\n  \"PLE0303\",\n  \"PLE0304\",\n  \"PLE0305\",\n  \"PLE0307\",\n  \"PLE0308\",\n  \"PLE0309\",\n  \"PLE0604\",\n  \"PLE0605\",\n  \"PLE0643\",\n  \"PLE0704\",\n  \"PLE1132\",\n  \"PLE1141\",\n  \"PLE1142\",\n  \"PLE1205\",\n  \"PLE1206\",\n  \"PLE1300\",\n  \"PLE1307\",\n  \"PLE1310\",\n  \"PLE1507\",\n  \"PLE1519\",\n  \"PLE1520\",\n  \"PLE1700\",\n  \"PLE2502\",\n  \"PLE2510\",\n  \"PLE2512\",\n  \"PLE2513\",\n  \"PLE2514\",\n  \"PLE2515\",\n  \"PLE4703\",\n  \"PLR0124\",\n  \"PLR0133\",\n  \"PLR0202\",\n  \"PLR0203\",\n  \"PLR0206\",\n  \"PLR0402\",\n  \"PLR1704\",\n  \"PLR1711\",\n  \"PLR1714\",\n  \"PLR1722\",\n  \"PLR1730\",\n  \"PLR1733\",\n  \"PLR1736\",\n  \"PLR2004\",\n  \"PLR2044\",\n  \"PLR5501\",\n  \"PLR6104\",\n  \"PLR6201\",\n  \"PLR6301\",\n  \"PLW0108\",\n  \"PLW0120\",\n  \"PLW0127\",\n  \"PLW0128\",\n  \"PLW0129\",\n  \"PLW0131\",\n  \"PLW0133\",\n  \"PLW0177\",\n  \"PLW0211\",\n  \"PLW0245\",\n  \"PLW0406\",\n  \"PLW0602\",\n  \"PLW0603\",\n  \"PLW0604\",\n  \"PLW0642\",\n  \"PLW0711\",\n  \"PLW1501\",\n  \"PLW1508\",\n  \"PLW1509\",\n  \"PLW1510\",\n  \"PLW1514\",\n  \"PLW1641\",\n  \"PLW2101\",\n  \"PLW2901\",\n  \"PLW3201\",\n  \"PLW3301\",\n  \"PT001\",\n  \"PT002\",\n  \"PT003\",\n  \"PT006\",\n  \"PT007\",\n  \"PT008\",\n  \"PT009\",\n  \"PT010\",\n  \"PT011\",\n  \"PT012\",\n  \"PT013\",\n  \"PT014\",\n  \"PT015\",\n  \"PT016\",\n  \"PT017\",\n  \"PT018\",\n  \"PT019\",\n  \"PT020\",\n  \"PT021\",\n  \"PT022\",\n  \"PT023\",\n  \"PT024\",\n  \"PT025\",\n  \"PT026\",\n  \"PT027\",\n  \"PYI001\",\n  \"PYI002\",\n  \"PYI003\",\n  \"PYI004\",\n  \"PYI005\",\n  \"PYI006\",\n  \"PYI007\",\n  \"PYI008\",\n  \"PYI009\",\n  \"PYI010\",\n  \"PYI011\",\n  \"PYI012\",\n  \"PYI013\",\n  \"PYI014\",\n  \"PYI015\",\n  \"PYI016\",\n  \"PYI017\",\n  \"PYI018\",\n  \"PYI019\",\n  \"PYI020\",\n  \"PYI021\",\n  \"PYI024\",\n  \"PYI025\",\n  \"PYI026\",\n  \"PYI029\",\n  \"PYI030\",\n  \"PYI032\",\n  \"PYI033\",\n  \"PYI034\",\n  \"PYI035\",\n  \"PYI036\",\n  \"PYI041\",\n  \"PYI042\",\n  \"PYI043\",\n  \"PYI044\",\n  \"PYI045\",\n  \"PYI046\",\n  \"PYI047\",\n  \"PYI048\",\n  \"PYI049\",\n  \"PYI050\",\n  \"PYI051\",\n  \"PYI052\",\n  \"PYI053\",\n  \"PYI054\",\n  \"PYI055\",\n  \"PYI056\",\n  \"PYI058\",\n  \"PYI059\",\n  \"PYI062\",\n  \"RET503\",\n  \"RET504\",\n  \"RET505\",\n  \"RET506\",\n  \"RET507\",\n  \"RET508\",\n  \"RSE102\",\n  \"RUF001\",\n  \"RUF002\",\n  \"RUF003\",\n  \"RUF005\",\n  \"RUF006\",\n  \"RUF007\",\n  \"RUF008\",\n  \"RUF009\",\n  \"RUF010\",\n  \"RUF012\",\n  \"RUF013\",\n  \"RUF015\",\n  \"RUF016\",\n  \"RUF017\",\n  \"RUF018\",\n  \"RUF019\",\n  \"RUF020\",\n  \"RUF021\",\n  \"RUF022\",\n  \"RUF023\",\n  \"RUF024\",\n  \"RUF026\",\n  \"RUF027\",\n  \"RUF028\",\n  \"RUF029\",\n  \"RUF100\",\n  \"RUF101\",\n  \"S101\",\n  \"S102\",\n  \"S103\",\n  \"S104\",\n  \"S105\",\n  \"S106\",\n  \"S107\",\n  \"S108\",\n  \"S110\",\n  \"S112\",\n  \"S113\",\n  \"S201\",\n  \"S202\",\n  \"S301\",\n  \"S302\",\n  \"S303\",\n  \"S304\",\n  \"S305\",\n  \"S306\",\n  \"S307\",\n  \"S308\",\n  \"S310\",\n  \"S311\",\n  \"S312\",\n  \"S313\",\n  \"S314\",\n  \"S315\",\n  \"S316\",\n  \"S317\",\n  \"S318\",\n  \"S319\",\n  \"S321\",\n  \"S323\",\n  \"S324\",\n  \"S401\",\n  \"S402\",\n  \"S403\",\n  \"S405\",\n  \"S406\",\n  \"S407\",\n  \"S408\",\n  \"S409\",\n  \"S411\",\n  \"S412\",\n  \"S413\",\n  \"S415\",\n  \"S501\",\n  \"S502\",\n  \"S503\",\n  \"S504\",\n  \"S505\",\n  \"S506\",\n  \"S507\",\n  \"S508\",\n  \"S509\",\n  \"S601\",\n  \"S602\",\n  \"S604\",\n  \"S605\",\n  \"S606\",\n  \"S607\",\n  \"S608\",\n  \"S609\",\n  \"S610\",\n  \"S611\",\n  \"S612\",\n  \"S701\",\n  \"S702\",\n  \"SIM101\",\n  \"SIM102\",\n  \"SIM103\",\n  \"SIM105\",\n  \"SIM107\",\n  \"SIM108\",\n  \"SIM109\",\n  \"SIM110\",\n  \"SIM112\",\n  \"SIM113\",\n  \"SIM114\",\n  \"SIM115\",\n  \"SIM116\",\n  \"SIM117\",\n  \"SIM118\",\n  \"SIM201\",\n  \"SIM202\",\n  \"SIM208\",\n  \"SIM210\",\n  \"SIM211\",\n  \"SIM212\",\n  \"SIM220\",\n  \"SIM221\",\n  \"SIM222\",\n  \"SIM223\",\n  \"SIM300\",\n  \"SIM910\",\n  \"SIM911\",\n  \"SLF001\",\n  \"SLOT000\",\n  \"SLOT001\",\n  \"SLOT002\",\n  \"T100\",\n  \"T201\",\n  \"T203\",\n  \"TC001\",\n  \"TC002\",\n  \"TC003\",\n  \"TC004\",\n  \"TC005\",\n  \"TC010\",\n  \"TD004\",\n  \"TD005\",\n  \"TD006\",\n  \"TD007\",\n  \"TID251\",\n  \"TID252\",\n  \"TID253\",\n  \"TRY002\",\n  \"TRY003\",\n  \"TRY004\",\n  \"TRY201\",\n  \"TRY203\",\n  \"TRY300\",\n  \"TRY301\",\n  \"TRY400\",\n  \"TRY401\",\n  \"UP001\",\n  \"UP003\",\n  \"UP004\",\n  \"UP005\",\n  \"UP006\",\n  \"UP007\",\n  \"UP008\",\n  \"UP009\",\n  \"UP010\",\n  \"UP011\",\n  \"UP012\",\n  \"UP013\",\n  \"UP014\",\n  \"UP015\",\n  \"UP017\",\n  \"UP018\",\n  \"UP019\",\n  \"UP020\",\n  \"UP021\",\n  \"UP022\",\n  \"UP023\",\n  \"UP024\",\n  \"UP025\",\n  \"UP026\",\n  \"UP028\",\n  \"UP029\",\n  \"UP030\",\n  \"UP031\",\n  \"UP032\",\n  \"UP033\",\n  \"UP034\",\n  \"UP035\",\n  \"UP036\",\n  \"UP037\",\n  \"UP039\",\n  \"UP040\",\n  \"UP041\",\n  \"UP042\",\n  \"W291\",\n  \"W292\",\n  \"W293\",\n  \"W391\",\n  \"W505\",\n  \"W605\",\n  \"YTT101\",\n  \"YTT102\",\n  \"YTT103\",\n  \"YTT201\",\n  \"YTT202\",\n  \"YTT203\",\n  \"YTT204\",\n  \"YTT301\",\n  \"YTT302\",\n  \"YTT303\",\n]\n\n[lint.per-file-ignores]\n\"**/scripts/*\" = [\n  \"INP001\",\n  \"T201\",\n]\n\"**/tests/**/*\" = [\n  \"PLC1901\",\n  \"PLR2004\",\n  \"PLR6301\",\n  \"S\",\n  \"TID252\",\n]\n\n[lint.flake8-tidy-imports]\nban-relative-imports = \"all\"\n\n[lint.isort]\nknown-first-party = [\"hatch\"]\n\n[lint.flake8-pytest-style]\nfixture-parentheses = false\nmark-parentheses = false\n"
  },
  {
    "path": "scripts/bump.py",
    "content": "import argparse\nimport re\nimport subprocess\nfrom datetime import datetime, timezone\n\nfrom utils import ROOT, get_latest_release\n\nTEMPLATE = (\n    \"## [{version}](https://github.com/pypa/hatch/releases/tag/{project}-v{version}) - \"\n    \"{year}-{month:02}-{day:02} ## {{: #{project}-v{version} }}\"\n)\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"project\", choices=[\"hatch\", \"hatchling\"])\n    parser.add_argument(\"version\")\n    args = parser.parse_args()\n\n    root_dir = project_dir = ROOT\n    if args.project == \"hatchling\":\n        project_dir = root_dir / \"backend\"\n\n    history_file = root_dir / \"docs\" / \"history\" / f\"{args.project}.md\"\n\n    if args.project == \"hatchling\":\n        process = subprocess.run(  # noqa: PLW1510\n            [\"hatch\", \"version\", args.version],  # noqa: S607\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n            encoding=\"utf-8\",\n            cwd=str(project_dir),\n        )\n        if process.returncode:\n            raise OSError(process.stdout)\n\n        new_version = re.search(r\"New: (.+)$\", process.stdout, re.MULTILINE).group(1)\n    else:\n        from hatchling.version.scheme.standard import StandardScheme\n\n        latest_version, _ = get_latest_release(args.project)\n\n        scheme = StandardScheme(str(project_dir), {})\n        new_version = scheme.update(args.version, latest_version, {})\n\n    now = datetime.now(timezone.utc)\n\n    history_file_lines = history_file.read_text(encoding=\"utf-8\").splitlines()\n    insertion_index = history_file_lines.index(\"## Unreleased\") + 1\n    history_file_lines.insert(\n        insertion_index,\n        TEMPLATE.format(project=args.project, version=new_version, year=now.year, month=now.month, day=now.day),\n    )\n    history_file_lines.insert(insertion_index, \"\")\n    history_file_lines.append(\"\")\n    history_file.write_text(\"\\n\".join(history_file_lines), encoding=\"utf-8\")\n\n    for command in (\n        [\"git\", \"add\", \"--all\"],\n        [\"git\", \"commit\", \"-m\", f\"release {args.project.capitalize()} v{new_version}\"],\n    ):\n        subprocess.run(command, check=True)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/generate_coverage_summary.py",
    "content": "import json\nfrom collections import defaultdict\n\nfrom lxml import etree  # nosec B410\nfrom utils import ROOT\n\nPACKAGES = {\n    \"backend/src/hatchling/\": \"hatchling\",\n    \"src/hatch/\": \"hatch\",\n    \"tests/\": \"tests\",\n}\n\n\ndef main():\n    coverage_report = ROOT / \"coverage.xml\"\n    root = etree.fromstring(coverage_report.read_text())  # nosec B320\n\n    raw_package_data = defaultdict(lambda: {\"hits\": 0, \"misses\": 0})\n    for package in root.find(\"packages\"):\n        for module in package.find(\"classes\"):\n            filename = module.attrib[\"filename\"]\n            for relative_path, package_name in PACKAGES.items():\n                if filename.startswith(relative_path):\n                    data = raw_package_data[package_name]\n                    break\n            else:\n                message = f\"unknown package: {module}\"\n                raise ValueError(message)\n\n            for line in module.find(\"lines\"):\n                if line.attrib[\"hits\"] == \"1\":\n                    data[\"hits\"] += 1\n                else:\n                    data[\"misses\"] += 1\n\n    total_statements_covered = 0\n    total_statements = 0\n    coverage_data = {}\n    for package_name, data in sorted(raw_package_data.items()):\n        statements_covered = data[\"hits\"]\n        statements = statements_covered + data[\"misses\"]\n        total_statements_covered += statements_covered\n        total_statements += statements\n\n        coverage_data[package_name] = {\"statements_covered\": statements_covered, \"statements\": statements}\n    coverage_data[\"total\"] = {\"statements_covered\": total_statements_covered, \"statements\": total_statements}\n\n    coverage_summary = ROOT / \"coverage-summary.json\"\n    coverage_summary.write_text(json.dumps(coverage_data, indent=4), encoding=\"utf-8\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/install_mkdocs_material_insiders.py",
    "content": "import os\nimport subprocess\nimport sys\n\nTOKEN = os.environ.get(\"GH_TOKEN_MKDOCS_MATERIAL_INSIDERS\", \"\")\nDEP_REF = f\"git+https://{TOKEN}@github.com/squidfunk/mkdocs-material-insiders.git\"\nGIT_REF = \"f2d5b41b2e590baf73ae5f51166d88b233ba96aa\"\n\n\ndef main():\n    if not TOKEN:\n        print(\"No token is set, skipping\")\n        return\n\n    dependency = f\"mkdocs-material[imaging] @ {DEP_REF}@{GIT_REF}\"\n    try:\n        process = subprocess.Popen(\n            [\"uv\", \"pip\", \"install\", dependency],  # noqa: S607\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n            encoding=\"utf-8\",\n        )\n    except Exception as e:  # noqa: BLE001\n        print(str(e).replace(TOKEN, \"*****\"))\n        sys.exit(1)\n\n    with process:\n        for line in iter(process.stdout.readline, \"\"):\n            print(line.replace(TOKEN, \"*****\"), end=\"\")\n\n    sys.exit(process.returncode)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/release_github.py",
    "content": "import argparse\nimport subprocess\nimport sys\nimport webbrowser\nfrom urllib.parse import urlencode\n\nfrom utils import get_latest_release\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"project\", choices=[\"hatch\", \"hatchling\"])\n    args = parser.parse_args()\n\n    version, notes = get_latest_release(args.project)\n    tag = f\"{args.project}-v{version}\"\n\n    # Create and push tag first\n    try:\n        subprocess.run([\"git\", \"tag\", tag], check=True)  # noqa: S607\n        subprocess.run([\"git\", \"push\", \"origin\", tag], check=True)  # noqa: S607\n        print(f\"Created and pushed tag: {tag}\")\n    except subprocess.CalledProcessError as e:\n        print(f\"Error creating tag: {e}\")\n        sys.exit(1)\n\n    # Open GitHub UI to create draft release\n    params = urlencode({\n        \"title\": f\"{args.project.capitalize()} v{version}\",\n        \"tag\": tag,\n        \"body\": notes,\n        \"draft\": \"true\",\n    })\n\n    url = f\"https://github.com/pypa/hatch/releases/new?{params}\"\n    webbrowser.open_new_tab(url)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/set_release_version.py",
    "content": "import os\n\nfrom utils import get_latest_release\n\n\ndef main():\n    version, _ = get_latest_release(\"hatch\")\n    parts = version.split(\".\")\n\n    with open(os.environ[\"GITHUB_ENV\"], \"a\", encoding=\"utf-8\") as f:\n        f.write(f\"HATCH_DOCS_VERSION={parts[0]}.{parts[1]}\\n\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/update_distributions.py",
    "content": "from __future__ import annotations\n\nimport re\nfrom ast import literal_eval\nfrom collections import defaultdict\n\nimport httpx\nfrom utils import ROOT\n\nURL = \"https://raw.githubusercontent.com/ofek/pyapp/master/build.rs\"\nOUTPUT_FILE = ROOT / \"src\" / \"hatch\" / \"python\" / \"distributions.py\"\nARCHES = {(\"linux\", \"x86\"): \"i686\", (\"windows\", \"x86_64\"): \"amd64\", (\"windows\", \"x86\"): \"i386\"}\n\n# system, architecture, ABI, CPU variant, GIL variant\nMAX_IDENTIFIER_COMPONENTS = 5\n\n\ndef parse_distributions(contents: str, constant: str):\n    match = re.search(f\"^const {constant}.+?^];$\", contents, flags=re.DOTALL | re.MULTILINE)\n    if not match:\n        message = f\"Could not find {constant} in {URL}\"\n        raise ValueError(message)\n\n    block = match.group(0).replace('\",\\n', '\",')\n    for raw_line in block.splitlines()[1:-1]:\n        line = raw_line.strip()\n        if not line or line.startswith(\"//\"):\n            continue\n\n        identifier, *data, source = literal_eval(line[:-1])\n        os, arch = data[:2]\n        if arch == \"powerpc64\":\n            arch = \"ppc64le\"\n        elif os == \"macos\" and arch == \"aarch64\":\n            arch = \"arm64\"\n\n        # Force everything to have the proper number of variants to maintain structure\n        if len(data) != MAX_IDENTIFIER_COMPONENTS:\n            data.extend((\"\", \"\"))\n\n        data[1] = ARCHES.get((os, arch), arch)\n        yield identifier, tuple(data), source\n\n\ndef main():\n    response = httpx.get(URL)\n    response.raise_for_status()\n\n    contents = response.text\n    distributions = defaultdict(list)\n    ordering_data = defaultdict(dict)\n\n    for i, distribution_type in enumerate((\"DEFAULT_CPYTHON_DISTRIBUTIONS\", \"DEFAULT_PYPY_DISTRIBUTIONS\")):\n        for identifier, data, source in parse_distributions(contents, distribution_type):\n            ordering_data[i][identifier] = None\n            distributions[identifier].append((data, source))\n\n    ordered = [identifier for identifiers in ordering_data.values() for identifier in reversed(identifiers)]\n    output = [\n        \"from __future__ import annotations\",\n        \"\",\n        \"# fmt: off\",\n        \"ORDERED_DISTRIBUTIONS: tuple[str, ...] = (\",\n    ]\n    output.extend(f\"    {identifier!r},\" for identifier in ordered)\n    output.extend((\")\", \"DISTRIBUTIONS: dict[str, dict[tuple[str, ...], str]] = {\"))\n\n    for identifier, data in distributions.items():\n        output.append(f\"    {identifier!r}: {{\")\n        for d, source in data:\n            output.extend((f\"        {d!r}:\", f\"            {source!r},\"))\n        output.append(\"    },\")\n\n    output.extend((\"}\", \"\"))\n    output = \"\\n\".join(output)\n\n    with open(OUTPUT_FILE, \"w\", encoding=\"utf-8\") as f:\n        f.write(output)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/update_ruff.py",
    "content": "from __future__ import annotations\n\nimport json\nimport re\nimport subprocess\nimport sys\nimport typing\nfrom importlib.metadata import version\n\nfrom utils import ROOT\n\nif typing.TYPE_CHECKING:\n    from pathlib import Path\n\n# fmt: off\nUNSELECTED_RULE_PATTERNS: list[str] = [\n    # Allow non-abstract empty methods in abstract base classes\n    'B027',\n    # Allow boolean positional values in function calls, like `dict.get(... True)`\n    'FBT003',\n    # Ignore complexity\n    'C901', 'PLR0904', 'PLR0911', 'PLR0912', 'PLR0913', 'PLR0914', 'PLR0915', 'PLR0916', 'PLR0917', 'PLR1702',\n    # These are dependent on projects themselves\n    'AIR\\\\d+', 'CPY\\\\d+', 'D\\\\d+', 'DJ\\\\d+', 'NPY\\\\d+', 'PD\\\\d+',\n    # Many projects either don't have type annotations or it would take much effort to satisfy this\n    'ANN\\\\d+',\n    # Don't be too strict about TODOs as not everyone uses them the same way\n    'FIX\\\\d+', 'TD001', 'TD002', 'TD003',\n    # There are valid reasons to not use pathlib such as performance and import cost\n    'PTH\\\\d+', 'FURB101', 'FURB103',\n    # Conflicts with type checking\n    'RET501', 'RET502',\n    # Under review https://github.com/astral-sh/ruff/issues/8796\n    'PT004', 'PT005',\n    # Buggy https://github.com/astral-sh/ruff/issues/4845\n    'ERA001',\n    # Business logic relying on other programs has no choice but to use subprocess\n    'S404',\n    # Too prone to false positives and might be removed https://github.com/astral-sh/ruff/issues/4045\n    'S603',\n    # Too prone to false positives https://github.com/astral-sh/ruff/issues/8761\n    'SIM401',\n    # Allow for easy ignores\n    'PGH003', 'PGH004',\n    # This is required sometimes, and doesn't matter on Python 3.11+\n    'PERF203',\n    # Potentially unnecessary on Python 3.12+\n    'FURB140',\n    # Conflicts with formatter, see:\n    # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules\n    'COM812', 'COM819', 'D206', 'D300', 'E111', 'E114', 'E117', 'E301', 'E302', 'E303', 'E304', 'E305', 'E306', \"E501\", 'ISC001', 'ISC002', 'Q000', 'Q001', 'Q002', 'Q003', 'Q004', 'W191',\n    # Conflicts with context formatting in dependencies\n    'RUF200',\n    # Currently broken\n]\nPER_FILE_IGNORED_RULES: dict[str, list[str]] = {\n    '**/scripts/*': [\n        # Implicit namespace packages\n        'INP001',\n        # Print statements\n        'T201',\n    ],\n    '**/tests/**/*': [\n        # Empty string comparisons\n        'PLC1901',\n        # Magic values\n        'PLR2004',\n        # Methods that don't use `self`\n        'PLR6301',\n        # Potential security issues like assert statements and hardcoded passwords\n        'S',\n        # Relative imports\n        'TID252',\n    ],\n}\n# fmt: on\n\n\ndef get_lines_until(file_path: Path, marker: str) -> list[str]:\n    lines = file_path.read_text(encoding=\"utf-8\").splitlines()\n    for i, line in enumerate(lines):\n        if line.startswith(marker):\n            block_start = i\n            break\n    else:\n        message = f\"Could not find {marker}: {file_path.relative_to(ROOT)}\"\n        raise ValueError(message)\n\n    del lines[block_start:]\n    return lines\n\n\ndef main():\n    process = subprocess.run(  # noqa: PLW1510\n        [sys.executable, \"-m\", \"ruff\", \"rule\", \"--all\", \"--output-format\", \"json\"],\n        stdout=subprocess.PIPE,\n        stderr=subprocess.STDOUT,\n        encoding=\"utf-8\",\n        cwd=str(ROOT),\n    )\n    if process.returncode:\n        raise OSError(process.stdout)\n\n    data_file = ROOT / \"src\" / \"hatch\" / \"cli\" / \"fmt\" / \"core.py\"\n    lines = get_lines_until(data_file, \"STABLE_RULES\")\n\n    ignored_pattern = re.compile(f\"^({'|'.join(UNSELECTED_RULE_PATTERNS)})$\")\n    # https://github.com/astral-sh/ruff/issues/9891#issuecomment-1951403651\n    removed_pattern = re.compile(r\"^\\s*#+\\s+(removed|removal)\", flags=re.IGNORECASE | re.MULTILINE)\n\n    stable_rules: set[str] = set()\n    preview_rules: set[str] = set()\n    unselected_rules: set[str] = set()\n    for rule in json.loads(process.stdout):\n        code = rule[\"code\"]\n        if ignored_pattern.match(code) or removed_pattern.search(rule[\"explanation\"]):\n            unselected_rules.add(code)\n            continue\n\n        if rule[\"preview\"]:\n            preview_rules.add(code)\n        else:\n            stable_rules.add(code)\n\n    lines.append(\"STABLE_RULES: tuple[str, ...] = (\")\n    lines.extend(f\"    {rule!r},\" for rule in sorted(stable_rules))\n    lines.append(\")\")\n\n    lines.append(\"PREVIEW_RULES: tuple[str, ...] = (\")\n    lines.extend(f\"    {rule!r},\" for rule in sorted(preview_rules))\n    lines.append(\")\")\n\n    lines.append(\"PER_FILE_IGNORED_RULES: dict[str, list[str]] = {\")\n    for ignored_glob, ignored_rules in sorted(PER_FILE_IGNORED_RULES.items()):\n        lines.append(f\"    {ignored_glob!r}: [\")\n        lines.extend(f\"        {rule!r},\" for rule in sorted(ignored_rules))\n        lines.append(\"    ],\")\n    lines.append(\"}\")\n\n    lines.append(\"\")\n    data_file.write_text(\"\\n\".join(lines), encoding=\"utf-8\")\n\n    version_file = ROOT / \"src\" / \"hatch\" / \"env\" / \"internal\" / \"static_analysis.py\"\n    latest_version = version(\"ruff\")\n    version_file.write_text(\n        re.sub(\n            r\"^(RUFF_DEFAULT_VERSION.+=.+\\').+?(\\')$\",\n            rf\"\\g<1>{latest_version}\\g<2>\",\n            version_file.read_text(encoding=\"utf-8\"),\n            count=1,\n            flags=re.MULTILINE,\n        ),\n        encoding=\"utf-8\",\n    )\n\n    data_file = ROOT / \"docs\" / \".hooks\" / \"render_ruff_defaults.py\"\n    lines = get_lines_until(data_file, \"UNSELECTED_RULES\")\n\n    lines.append(\"UNSELECTED_RULES: tuple[str, ...] = (\")\n    lines.extend(f\"    {rule!r},\" for rule in sorted(unselected_rules))\n    lines.append(\")\")\n\n    lines.append(\"\")\n    data_file.write_text(\"\\n\".join(lines), encoding=\"utf-8\")\n\n    print(f\"Stable rules: {len(stable_rules)}\")\n    print(f\"Preview rules: {len(preview_rules)}\")\n    print(f\"Unselected rules: {len(unselected_rules)}\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/utils.py",
    "content": "import re\nfrom pathlib import Path\n\nROOT = Path(__file__).resolve().parent.parent\n\n\ndef get_latest_release(project):\n    history_file = ROOT / \"docs\" / \"history\" / f\"{project}.md\"\n\n    release_headers = 0\n    history_file_lines = []\n    with history_file.open(encoding=\"utf-8\") as f:\n        for line in f:\n            history_file_lines.append(line.rstrip())\n\n            if line.startswith(\"## \"):\n                release_headers += 1\n\n            if release_headers == 3:  # noqa: PLR2004\n                break\n\n    release_lines = history_file_lines[history_file_lines.index(\"## Unreleased\") + 1 : -1]\n    while True:\n        release_header = release_lines.pop(0)\n        if release_header.startswith(\"## \"):\n            break\n\n    return re.search(r\"\\[(.+)\\]\", release_header).group(1), \"\\n\".join(release_lines).strip()\n"
  },
  {
    "path": "scripts/validate_history.py",
    "content": "import re\nimport sys\n\nfrom utils import ROOT\n\nHEADER_PATTERN = (\n    r\"^\\[([a-z0-9.]+)\\]\\(https://github\\.com/pypa/hatch/releases/tag/({package}-v\\1)\\)\"\n    r\" - [0-9]{{4}}-[0-9]{{2}}-[0-9]{{2}} ## \\{{: #\\2 \\}}$\"\n)\n\n\ndef main():\n    for package in (\"hatch\", \"hatchling\"):\n        history_file = ROOT / \"docs\" / \"history\" / f\"{package}.md\"\n        current_pattern = HEADER_PATTERN.format(package=package)\n\n        with history_file.open(\"r\", encoding=\"utf-8\") as f:\n            for raw_line in f:\n                line = raw_line.strip()\n                if not line:\n                    continue\n\n                if line.startswith(\"## \"):\n                    _, _, header = line.partition(\" \")\n                    if header == \"Unreleased\":\n                        continue\n\n                    if not re.search(current_pattern, header):\n                        print(\"Invalid header:\")\n                        print(header)\n                        sys.exit(1)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/write_coverage_summary_report.py",
    "content": "import json\nfrom decimal import ROUND_DOWN, Decimal\nfrom pathlib import Path\n\nPRECISION = Decimal(\".01\")\n\n\ndef main():\n    project_root = Path(__file__).resolve().parent.parent\n    coverage_summary = project_root / \"coverage-summary.json\"\n\n    coverage_data = json.loads(coverage_summary.read_text(encoding=\"utf-8\"))\n    total_data = coverage_data.pop(\"total\")\n\n    lines = [\n        \"\\n\",\n        \"Package | Statements\\n\",\n        \"--- | ---\\n\",\n    ]\n\n    for package, data in sorted(coverage_data.items()):\n        statements_covered = data[\"statements_covered\"]\n        statements = data[\"statements\"]\n\n        rate = Decimal(statements_covered) / Decimal(statements) * 100\n        rate = rate.quantize(PRECISION, rounding=ROUND_DOWN)\n        lines.append(\n            f\"{package} | {100 if rate == 100 else rate}% ({statements_covered} / {statements})\\n\"  # noqa: PLR2004\n        )\n\n    total_statements_covered = total_data[\"statements_covered\"]\n    total_statements = total_data[\"statements\"]\n    total_rate = Decimal(total_statements_covered) / Decimal(total_statements) * 100\n    total_rate = total_rate.quantize(PRECISION, rounding=ROUND_DOWN)\n    color = \"ok\" if float(total_rate) >= 95 else \"critical\"  # noqa: PLR2004\n    lines.insert(0, f\"![Code Coverage](https://img.shields.io/badge/coverage-{total_rate}%25-{color}?style=flat)\\n\")\n\n    lines.append(\n        f\"**Summary** | {100 if total_rate == 100 else total_rate}% \"  # noqa: PLR2004\n        f\"({total_statements_covered} / {total_statements})\\n\"\n    )\n\n    coverage_report = project_root / \"coverage-report.md\"\n    with coverage_report.open(\"w\", encoding=\"utf-8\") as f:\n        f.write(\"\".join(lines))\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "src/hatch/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/__main__.py",
    "content": "if __name__ == \"__main__\":\n    from hatch.cli import main\n\n    main()\n"
  },
  {
    "path": "src/hatch/cli/__init__.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom typing import cast\n\nimport click\n\nfrom hatch._version import __version__\nfrom hatch.cli.application import Application\nfrom hatch.cli.build import build\nfrom hatch.cli.clean import clean\nfrom hatch.cli.config import config\nfrom hatch.cli.dep import dep\nfrom hatch.cli.env import env\nfrom hatch.cli.fmt import fmt\nfrom hatch.cli.new import new\nfrom hatch.cli.project import project\nfrom hatch.cli.publish import publish\nfrom hatch.cli.python import python\nfrom hatch.cli.run import run\nfrom hatch.cli.self import self_command\nfrom hatch.cli.shell import shell\nfrom hatch.cli.status import status\nfrom hatch.cli.test import test\nfrom hatch.cli.version import version\nfrom hatch.config.constants import AppEnvVars, ConfigEnvVars\nfrom hatch.project.core import Project\nfrom hatch.utils.ci import running_in_ci\nfrom hatch.utils.fs import Path\n\n\n@click.group(\n    context_settings={\"help_option_names\": [\"-h\", \"--help\"], \"max_content_width\": 120}, invoke_without_command=True\n)\n@click.option(\n    \"--env\",\n    \"-e\",\n    \"env_name\",\n    envvar=AppEnvVars.ENV,\n    default=\"default\",\n    help=\"The name of the environment to use [env var: `HATCH_ENV`]\",\n)\n@click.option(\n    \"--project\",\n    \"-p\",\n    envvar=ConfigEnvVars.PROJECT,\n    help=\"The name of the project to work on [env var: `HATCH_PROJECT`]\",\n)\n@click.option(\n    \"--verbose\",\n    \"-v\",\n    envvar=AppEnvVars.VERBOSE,\n    count=True,\n    help=\"Increase verbosity (can be used additively) [env var: `HATCH_VERBOSE`]\",\n)\n@click.option(\n    \"--quiet\",\n    \"-q\",\n    envvar=AppEnvVars.QUIET,\n    count=True,\n    help=\"Decrease verbosity (can be used additively) [env var: `HATCH_QUIET`]\",\n)\n@click.option(\n    \"--color/--no-color\",\n    default=None,\n    help=\"Whether or not to display colored output (default is auto-detection) [env vars: `FORCE_COLOR`/`NO_COLOR`]\",\n)\n@click.option(\n    \"--interactive/--no-interactive\",\n    envvar=AppEnvVars.INTERACTIVE,\n    default=None,\n    help=(\n        \"Whether or not to allow features like prompts and progress bars (default is auto-detection) \"\n        \"[env var: `HATCH_INTERACTIVE`]\"\n    ),\n)\n@click.option(\n    \"--data-dir\",\n    envvar=ConfigEnvVars.DATA,\n    help=\"The path to a custom directory used to persist data [env var: `HATCH_DATA_DIR`]\",\n)\n@click.option(\n    \"--cache-dir\",\n    envvar=ConfigEnvVars.CACHE,\n    help=\"The path to a custom directory used to cache data [env var: `HATCH_CACHE_DIR`]\",\n)\n@click.option(\n    \"--config\",\n    \"config_file\",\n    envvar=ConfigEnvVars.CONFIG,\n    help=\"The path to a custom config file to use [env var: `HATCH_CONFIG`]\",\n)\n@click.version_option(version=__version__, prog_name=\"Hatch\")\n@click.pass_context\ndef hatch(\n    ctx: click.Context,\n    env_name,\n    project,\n    verbose,\n    quiet,\n    color,\n    interactive,\n    data_dir,\n    cache_dir,\n    config_file,\n):\n    \"\"\"\n    \\b\n     _   _       _       _\n    | | | |     | |     | |\n    | |_| | __ _| |_ ___| |__\n    |  _  |/ _` | __/ __| '_ \\\\\n    | | | | (_| | || (__| | | |\n    \\\\_| |_/\\\\__,_|\\\\__\\\\___|_| |_|\n    \"\"\"\n    if color is None:\n        if os.environ.get(AppEnvVars.NO_COLOR) == \"1\":\n            color = False\n        elif os.environ.get(AppEnvVars.FORCE_COLOR) == \"1\":\n            color = True\n\n    if interactive is None and running_in_ci():\n        interactive = False\n\n    app = Application(ctx.exit, verbosity=verbose - quiet, enable_color=color, interactive=interactive)\n    app.env_active = os.environ.get(AppEnvVars.ENV_ACTIVE)\n    if (\n        app.env_active\n        and (param_source := ctx.get_parameter_source(\"env_name\")) is not None\n        and param_source.name == \"DEFAULT\"\n    ):\n        app.env = app.env_active\n    else:\n        app.env = env_name\n\n    if config_file:\n        app.config_file.path = Path(config_file).resolve()\n        if not app.config_file.path.is_file():\n            app.abort(f\"The selected config file `{app.config_file.path}` does not exist.\")\n    elif not app.config_file.path.is_file():\n        if app.verbose:\n            app.display_waiting(\"No config file found, creating one with default settings now...\")\n\n        try:\n            app.config_file.restore()\n            if app.verbose:\n                app.display_success(\"Success! Please see `hatch config`.\")\n        except OSError:  # no cov\n            app.abort(\n                f\"Unable to create config file located at `{app.config_file.path}`. Please check your permissions.\"\n            )\n\n    if not ctx.invoked_subcommand:\n        app.display_info(ctx.get_help())\n        return\n\n    # Persist app data for sub-commands\n    ctx.obj = app\n\n    try:\n        app.config_file.load()\n    except OSError as e:  # no cov\n        app.abort(f\"Error loading configuration: {e}\")\n\n    app.config.terminal.styles.parse_fields()\n    errors = app.initialize_styles(app.config.terminal.styles.raw_data)\n    if errors and color is not False and not app.quiet:  # no cov\n        for error in errors:\n            app.display_warning(error)\n\n    app.data_dir = Path(data_dir or app.config.dirs.data).expand()\n    app.cache_dir = Path(cache_dir or app.config.dirs.cache).expand()\n\n    if project:\n        potential_project = Project.from_config(app.config, project)\n        if potential_project is None or potential_project.root is None:\n            app.abort(f\"Unable to locate project {project}\")\n\n        app.project = cast(Project, potential_project)\n        app.project.set_app(app)\n        return\n\n    app.project = Project(Path.cwd())\n    app.project.set_app(app)\n\n    if app.config.mode == \"local\":\n        return\n\n    # The following logic is mostly duplicated for each branch so coverage can be asserted\n    if app.config.mode == \"project\":\n        if not app.config.project:\n            app.display_warning(\"Mode is set to `project` but no project is set, defaulting to the current directory\")\n            return\n\n        possible_project = Project.from_config(app.config, app.config.project)\n        if possible_project is None:\n            app.display_warning(f\"Unable to locate project {app.config.project}, defaulting to the current directory\")\n        else:\n            app.project = possible_project\n\n        app.project.set_app(app)\n        return\n\n    if app.config.mode == \"aware\" and app.project.root is None:\n        if not app.config.project:\n            app.display_warning(\"Mode is set to `aware` but no project is set, defaulting to the current directory\")\n            return\n\n        possible_project = Project.from_config(app.config, app.config.project)\n        if possible_project is None:\n            app.display_warning(f\"Unable to locate project {app.config.project}, defaulting to the current directory\")\n        else:\n            app.project = possible_project\n\n        app.project.set_app(app)\n        return\n\n\nhatch.add_command(build)\nhatch.add_command(clean)\nhatch.add_command(config)\nhatch.add_command(dep)\nhatch.add_command(env)\nhatch.add_command(fmt)\nhatch.add_command(new)\nhatch.add_command(project)\nhatch.add_command(publish)\nhatch.add_command(python)\nhatch.add_command(run)\nhatch.add_command(self_command)\nhatch.add_command(shell)\nhatch.add_command(status)\nhatch.add_command(test)\nhatch.add_command(version)\n\n\ndef main():  # no cov\n    try:\n        hatch(prog_name=\"hatch\", windows_expand_args=False)\n    except Exception:  # noqa: BLE001\n        import sys\n\n        from rich.console import Console\n\n        console = Console()\n        hatch_debug = os.getenv(\"HATCH_DEBUG\") in {\"1\", \"true\"}\n        console.print_exception(suppress=[click], show_locals=hatch_debug)\n        sys.exit(1)\n"
  },
  {
    "path": "src/hatch/cli/application.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, cast\n\nfrom hatch.cli.terminal import Terminal\nfrom hatch.config.user import ConfigFile, RootConfig\nfrom hatch.project.core import Project\nfrom hatch.utils.fs import Path\nfrom hatch.utils.platform import Platform\nfrom hatch.utils.runner import ExecutionContext\n\nif TYPE_CHECKING:\n    from collections.abc import Generator\n\n    from hatch.dep.core import Dependency\n    from hatch.env.plugin.interface import EnvironmentInterface\n\n\nclass Application(Terminal):\n    def __init__(self, exit_func, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.platform = Platform(self.output)\n        self.__exit_func = exit_func\n\n        self.config_file = ConfigFile()\n        self.quiet = self.verbosity < 0\n        self.verbose = self.verbosity > 0\n\n        # Lazily set these as we acquire more knowledge about the environment\n        self.data_dir = cast(Path, None)\n        self.cache_dir = cast(Path, None)\n        self.project = cast(Project, None)\n        self.env = cast(str, None)\n        self.env_active = cast(str, None)\n\n    @property\n    def plugins(self):\n        return self.project.plugin_manager\n\n    @property\n    def config(self) -> RootConfig:\n        return self.config_file.model\n\n    def get_environment(self, env_name: str | None = None) -> EnvironmentInterface:\n        return self.project.get_environment(env_name)\n\n    def prepare_environment(self, environment: EnvironmentInterface, *, keep_env: bool = False):\n        self.project.prepare_environment(environment, keep_env=keep_env)\n\n    def run_shell_commands(self, context: ExecutionContext) -> None:\n        with context.env.command_context():\n            try:\n                resolved_commands = list(context.env.resolve_commands(context.shell_commands))\n            except Exception as e:  # noqa: BLE001\n                self.abort(str(e))\n\n            first_error_code = None\n            should_display_command = not context.hide_commands and (self.verbose or len(resolved_commands) > 1)\n            for i, raw_command in enumerate(resolved_commands, 1):\n                if should_display_command:\n                    self.display_info(f\"{context.source} [{i}] | {raw_command}\")\n\n                command = raw_command\n                continue_on_error = context.force_continue\n                if raw_command.startswith(\"- \"):\n                    continue_on_error = True\n                    command = command[2:]\n\n                process = context.env.run_shell_command(command)\n                sys.stdout.flush()\n                sys.stderr.flush()\n                if process.returncode:\n                    first_error_code = first_error_code or process.returncode\n                    if continue_on_error:\n                        continue\n\n                    if context.show_code_on_error:\n                        self.abort(f\"Failed with exit code: {process.returncode}\", code=process.returncode)\n                    else:\n                        self.abort(code=process.returncode)\n\n            if first_error_code and context.force_continue:\n                self.abort(code=first_error_code)\n\n    def runner_context(\n        self,\n        environments: list[str],\n        *,\n        ignore_compat: bool = False,\n        display_header: bool = False,\n        keep_env: bool = False,\n    ) -> Generator[ExecutionContext, None, None]:\n        if self.verbose or len(environments) > 1:\n            display_header = True\n\n        any_compatible = False\n        incompatible = {}\n        with self.project.ensure_cwd():\n            for env_name in environments:\n                environment = self.get_environment(env_name)\n                if not environment.exists():\n                    try:\n                        environment.check_compatibility()\n                    except Exception as e:  # noqa: BLE001\n                        if ignore_compat:\n                            incompatible[environment.name] = str(e)\n                            continue\n\n                        self.abort(f\"Environment `{env_name}` is incompatible: {e}\")\n\n                any_compatible = True\n                if display_header:\n                    self.display_header(environment.name)\n\n                context = ExecutionContext(environment)\n                yield context\n\n                self.prepare_environment(environment, keep_env=keep_env)\n                self.execute_context(context)\n\n        if incompatible:\n            num_incompatible = len(incompatible)\n            padding = \"\\n\" if any_compatible else \"\"\n            self.display_warning(\n                f\"{padding}Skipped {num_incompatible} incompatible environment{'s' if num_incompatible > 1 else ''}:\"\n            )\n            for env_name, reason in incompatible.items():\n                self.display_warning(f\"{env_name} -> {reason}\")\n\n    def execute_context(self, context: ExecutionContext) -> None:\n        from hatch.utils.structures import EnvVars\n\n        with EnvVars(context.env_vars):\n            self.run_shell_commands(context)\n\n    def ensure_environment_plugin_dependencies(self) -> None:\n        self.ensure_plugin_dependencies(\n            self.project.config.env_requires_complex, wait_message=\"Syncing environment plugin requirements\"\n        )\n\n    def ensure_plugin_dependencies(self, dependencies: list[Dependency], *, wait_message: str) -> None:\n        if not dependencies:\n            return\n\n        from hatch.dep.sync import InstalledDistributions\n        from hatch.env.utils import add_verbosity_flag\n\n        if app_path := os.environ.get(\"PYAPP\"):\n            from hatch.utils.env import PythonInfo\n\n            management_command = os.environ[\"PYAPP_COMMAND_NAME\"]\n            executable = self.platform.check_command_output([app_path, management_command, \"python-path\"]).strip()\n            python_info = PythonInfo(self.platform, executable=executable)\n            distributions = InstalledDistributions(sys_path=python_info.sys_path)\n            if distributions.dependencies_in_sync(dependencies):\n                return\n\n            pip_command = [app_path, management_command, \"pip\"]\n        else:\n            distributions = InstalledDistributions()\n            if distributions.dependencies_in_sync(dependencies):\n                return\n\n            pip_command = [sys.executable, \"-u\", \"-m\", \"pip\"]\n\n        pip_command.extend([\"install\", \"--disable-pip-version-check\"])\n\n        # Default to -1 verbosity\n        add_verbosity_flag(pip_command, self.verbosity, adjustment=-1)\n\n        pip_command.extend(str(dependency) for dependency in dependencies)\n\n        with self.status(wait_message):\n            self.platform.check_command(pip_command)\n\n    def get_env_directory(self, environment_type):\n        directories = self.config.dirs.env\n\n        if environment_type in directories:\n            path = Path(directories[environment_type]).expand()\n            if os.path.isabs(path):\n                return path\n\n            return self.project.location / path\n\n        return self.data_dir / \"env\" / environment_type\n\n    def get_python_manager(self, directory: str | None = None):\n        from hatch.python.core import PythonManager\n\n        configured_dir = directory or self.config.dirs.python\n        if configured_dir == \"isolated\":\n            return PythonManager(self.data_dir / \"pythons\")\n\n        return PythonManager(Path(configured_dir).expand())\n\n    @cached_property\n    def shell_data(self) -> tuple[str, str]:\n        from hatch.utils.shells import detect_shell\n\n        return detect_shell(self.platform)\n\n    def abort(self, text=\"\", code=1, **kwargs):\n        if text:\n            self.display_error(text, **kwargs)\n        self.__exit_func(code)\n"
  },
  {
    "path": "src/hatch/cli/build/__init__.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Build a project\")\n@click.argument(\"location\", required=False)\n@click.option(\n    \"--target\",\n    \"-t\",\n    \"targets\",\n    multiple=True,\n    help=(\n        \"The target to build, overriding project defaults. This may be selected multiple times e.g. `-t sdist -t wheel`\"\n    ),\n)\n@click.option(\n    \"--hooks-only\", is_flag=True, help=\"Whether or not to only execute build hooks [env var: `HATCH_BUILD_HOOKS_ONLY`]\"\n)\n@click.option(\n    \"--no-hooks\", is_flag=True, help=\"Whether or not to disable build hooks [env var: `HATCH_BUILD_NO_HOOKS`]\"\n)\n@click.option(\n    \"--ext\",\n    is_flag=True,\n    help=(\n        \"Whether or not to only execute build hooks for distributing binary Python packages, such as \"\n        \"compiling extensions. Equivalent to `--hooks-only -t wheel`\"\n    ),\n)\n@click.option(\n    \"--clean\",\n    \"-c\",\n    is_flag=True,\n    help=\"Whether or not existing artifacts should first be removed [env var: `HATCH_BUILD_CLEAN`]\",\n)\n@click.option(\n    \"--clean-hooks-after\",\n    is_flag=True,\n    help=(\n        \"Whether or not build hook artifacts should be removed after each build \"\n        \"[env var: `HATCH_BUILD_CLEAN_HOOKS_AFTER`]\"\n    ),\n)\n@click.option(\"--clean-only\", is_flag=True, hidden=True)\n@click.pass_obj\ndef build(app: Application, location, targets, hooks_only, no_hooks, ext, clean, clean_hooks_after, clean_only):\n    \"\"\"Build a project.\"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    from hatch.config.constants import AppEnvVars\n    from hatch.project.config import env_var_enabled\n    from hatch.project.constants import BUILD_BACKEND, DEFAULT_BUILD_DIRECTORY, BuildEnvVars\n    from hatch.utils.fs import Path\n    from hatch.utils.runner import ExecutionContext\n    from hatch.utils.structures import EnvVars\n\n    build_dir = Path(location).resolve() if location else None\n    if ext:\n        hooks_only = True\n        targets = (\"wheel\",)\n    elif not targets:\n        targets = (\"sdist\", \"wheel\")\n\n    env_vars = {}\n    if app.verbose:\n        env_vars[AppEnvVars.VERBOSE] = str(app.verbosity)\n    elif app.quiet:\n        env_vars[AppEnvVars.QUIET] = str(abs(app.verbosity))\n\n    with EnvVars(env_vars):\n        app.project.prepare_build_environment(targets=[target.split(\":\")[0] for target in targets])\n\n    build_backend = app.project.metadata.build.build_backend\n    with app.project.location.as_cwd(), app.project.build_env.get_env_vars():\n        for target in targets:\n            target_name, _, _ = target.partition(\":\")\n            if not clean_only:\n                app.display_header(target_name)\n\n            if build_backend != BUILD_BACKEND:\n                if target_name == \"sdist\":\n                    directory = build_dir or app.project.location / DEFAULT_BUILD_DIRECTORY\n                    directory.ensure_dir_exists()\n                    artifact_path = app.project.build_frontend.build_sdist(directory)\n                elif target_name == \"wheel\":\n                    directory = build_dir or app.project.location / DEFAULT_BUILD_DIRECTORY\n                    directory.ensure_dir_exists()\n                    artifact_path = app.project.build_frontend.build_wheel(directory)\n                else:\n                    app.abort(f\"Target `{target_name}` is not supported by `{build_backend}`\")\n\n                app.display_info(\n                    str(artifact_path.relative_to(app.project.location))\n                    if app.project.location in artifact_path.parents\n                    else str(artifact_path)\n                )\n            else:\n                command = [\"python\", \"-u\", \"-m\", \"hatchling\", \"build\", \"--target\", target]\n\n                # We deliberately pass the location unchanged so that absolute paths may be non-local\n                # and reflect wherever builds actually take place\n                if location:\n                    command.extend((\"--directory\", location))\n\n                if hooks_only or env_var_enabled(BuildEnvVars.HOOKS_ONLY):\n                    command.append(\"--hooks-only\")\n\n                if no_hooks or env_var_enabled(BuildEnvVars.NO_HOOKS):\n                    command.append(\"--no-hooks\")\n\n                if clean or env_var_enabled(BuildEnvVars.CLEAN):\n                    command.append(\"--clean\")\n\n                if clean_hooks_after or env_var_enabled(BuildEnvVars.CLEAN_HOOKS_AFTER):\n                    command.append(\"--clean-hooks-after\")\n\n                if clean_only:\n                    command.append(\"--clean-only\")\n\n                context = ExecutionContext(app.project.build_env)\n                context.add_shell_command(command)\n                context.env_vars.update(env_vars)\n                app.execute_context(context)\n"
  },
  {
    "path": "src/hatch/cli/clean/__init__.py",
    "content": "import click\n\n\n@click.command(short_help=\"Remove build artifacts\")\n@click.argument(\"location\", required=False)\n@click.option(\n    \"--target\",\n    \"-t\",\n    \"targets\",\n    multiple=True,\n    help=(\n        \"The target with which to remove artifacts, overriding project defaults. \"\n        \"This may be selected multiple times e.g. `-t sdist -t wheel`\"\n    ),\n)\n@click.option(\n    \"--hooks-only\",\n    is_flag=True,\n    help=\"Whether or not to only remove artifacts from build hooks [env var: `HATCH_BUILD_HOOKS_ONLY`]\",\n)\n@click.option(\n    \"--no-hooks\",\n    is_flag=True,\n    help=\"Whether or not to ignore artifacts from build hooks [env var: `HATCH_BUILD_NO_HOOKS`]\",\n)\n@click.option(\n    \"--ext\",\n    is_flag=True,\n    help=(\n        \"Whether or not to only remove artifacts from build hooks for distributing binary Python packages, such as \"\n        \"compiled extensions. Equivalent to `--hooks-only -t wheel`\"\n    ),\n)\n@click.pass_context\ndef clean(ctx, location, targets, hooks_only, no_hooks, ext):\n    \"\"\"Remove build artifacts.\"\"\"\n    from hatch.cli.build import build\n\n    ctx.invoke(\n        build, clean_only=True, location=location, targets=targets, hooks_only=hooks_only, no_hooks=no_hooks, ext=ext\n    )\n"
  },
  {
    "path": "src/hatch/cli/config/__init__.py",
    "content": "import os\n\nimport click\n\n\n@click.group(short_help=\"Manage the config file\")\ndef config():\n    pass\n\n\n@config.command(short_help=\"Open the config location in your file manager\")\n@click.pass_obj\ndef explore(app):\n    \"\"\"Open the config location in your file manager.\"\"\"\n    click.launch(str(app.config_file.path), locate=True)\n\n\n@config.command(short_help=\"Show the location of the config file\")\n@click.pass_obj\ndef find(app):\n    \"\"\"Show the location of the config file.\"\"\"\n    app.display(str(app.config_file.path))\n\n\n@config.command(short_help=\"Show the contents of the config file\")\n@click.option(\"--all\", \"-a\", \"all_keys\", is_flag=True, help=\"Do not scrub secret fields\")\n@click.pass_obj\ndef show(app, all_keys):\n    \"\"\"Show the contents of the config file.\"\"\"\n    if not app.config_file.path.is_file():  # no cov\n        app.display_critical(\"No config file found! Please try `hatch config restore`.\")\n    else:\n        text = app.config_file.read() if all_keys else app.config_file.read_scrubbed()\n        app.display_syntax(text.rstrip(), \"toml\")\n\n\n@config.command(short_help=\"Update the config file with any new fields\")\n@click.pass_obj\ndef update(app):  # no cov\n    \"\"\"Update the config file with any new fields.\"\"\"\n    app.config_file.update()\n    app.display_success(\"Settings were successfully updated.\")\n\n\n@config.command(short_help=\"Restore the config file to default settings\")\n@click.pass_obj\ndef restore(app):\n    \"\"\"Restore the config file to default settings.\"\"\"\n    app.config_file.restore()\n    app.display_success(\"Settings were successfully restored.\")\n\n\n@config.command(\"set\", short_help=\"Assign values to config file entries\")\n@click.argument(\"key\")\n@click.argument(\"value\", required=False)\n@click.pass_obj\ndef set_value(app, key, value):\n    \"\"\"\n    Assign values to config file entries. If the value is omitted,\n    you will be prompted, with the input hidden if it is sensitive.\n    \"\"\"\n    from fnmatch import fnmatch\n\n    import tomlkit\n\n    from hatch.config.model import ConfigurationError, RootConfig\n    from hatch.config.utils import create_toml_document, save_toml_document\n\n    scrubbing = key.startswith(\"publish.\")\n    if value is None:\n        value = click.prompt(f\"Value for `{key}`\", hide_input=scrubbing)\n\n    setting_project_location = bool(fnmatch(key, \"projects.*\") or fnmatch(key, \"projects.*.location\"))\n    if setting_project_location and not value.startswith(\"~\"):\n        value = os.path.abspath(value)\n\n    user_config = new_config = tomlkit.parse(app.config_file.read())\n\n    data = [value]\n    data.extend(reversed(key.split(\".\")))\n    key = data.pop()\n    value = data.pop()\n\n    # Use a separate mapping to show only what has changed in the end\n    branch_config_root = branch_config = {}\n\n    # Consider dots as keys\n    while data:\n        default_branch = {value: \"\"}\n        branch_config[key] = default_branch\n        branch_config = branch_config[key]\n\n        new_value = new_config.get(key)\n        if not hasattr(new_value, \"get\"):\n            new_value = default_branch\n\n        new_config[key] = new_value\n        new_config = new_config[key]\n\n        key = value\n        value = data.pop()\n\n    if value.startswith((\"{\", \"[\")):\n        from ast import literal_eval\n\n        value = literal_eval(value)\n    elif value.lower() == \"true\":\n        value = True\n    elif value.lower() == \"false\":\n        value = False\n\n    branch_config[key] = new_config[key] = value\n\n    # https://github.com/sdispater/tomlkit/issues/48\n    if new_config.__class__.__name__ == \"Table\":  # no cov\n        table_body = getattr(new_config.value, \"body\", [])\n        possible_whitespace = table_body[-2:]\n        if len(possible_whitespace) == 2:  # noqa: PLR2004\n            for key, item in possible_whitespace:\n                if key is not None:\n                    break\n                if item.__class__.__name__ != \"Whitespace\":\n                    break\n            else:\n                del table_body[-2]\n\n    try:\n        RootConfig(user_config).parse_fields()\n    except ConfigurationError as e:\n        app.display_error(str(e))\n        app.abort()\n    else:\n        if not user_config[\"project\"] and setting_project_location:\n            project = next(iter(branch_config_root[\"projects\"]))\n            user_config[\"project\"] = project\n            branch_config_root[\"project\"] = project\n\n        save_toml_document(user_config, app.config_file.path)\n\n    document = create_toml_document(branch_config_root)\n    if scrubbing and \"publish\" in document:\n        for data in document[\"publish\"].values():\n            for field in list(data):\n                data[field] = \"<...>\"\n\n    from rich.syntax import Syntax\n\n    app.display_success(\"New setting:\")\n    app.output(Syntax(tomlkit.dumps(document).rstrip(), \"toml\", background_color=\"default\"))\n"
  },
  {
    "path": "src/hatch/cli/dep/__init__.py",
    "content": "import click\n\n\n@click.group(short_help=\"Manage environment dependencies\")\ndef dep():\n    pass\n\n\n@dep.command(\"hash\", short_help=\"Output a hash of the currently defined dependencies\")\n@click.option(\"--project-only\", \"-p\", is_flag=True, help=\"Whether or not to exclude environment dependencies\")\n@click.option(\"--env-only\", \"-e\", is_flag=True, help=\"Whether or not to exclude project dependencies\")\n@click.pass_obj\ndef hash_dependencies(app, project_only, env_only):\n    \"\"\"Output a hash of the currently defined dependencies.\"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    from hatch.utils.dep import get_complex_dependencies, hash_dependencies\n\n    environment = app.project.get_environment()\n\n    all_requirements = []\n    if project_only:\n        dependencies, _ = app.project.get_dependencies()\n        dependencies_complex = get_complex_dependencies(dependencies)\n        all_requirements.extend(dependencies_complex.values())\n    elif env_only:\n        all_requirements.extend(environment.environment_dependencies_complex)\n    else:\n        dependencies, _ = app.project.get_dependencies()\n        dependencies_complex = get_complex_dependencies(dependencies)\n        all_requirements.extend(dependencies_complex.values())\n        all_requirements.extend(environment.environment_dependencies_complex)\n\n    app.display(hash_dependencies(all_requirements))\n\n\n@dep.group(short_help=\"Display dependencies in various formats\")\ndef show():\n    pass\n\n\n@show.command(short_help=\"Enumerate dependencies in a tabular format\")\n@click.option(\"--project-only\", \"-p\", is_flag=True, help=\"Whether or not to exclude environment dependencies\")\n@click.option(\"--env-only\", \"-e\", is_flag=True, help=\"Whether or not to exclude project dependencies\")\n@click.option(\"--lines\", \"-l\", \"show_lines\", is_flag=True, help=\"Whether or not to show lines between table rows\")\n@click.option(\"--ascii\", \"force_ascii\", is_flag=True, help=\"Whether or not to only use ASCII characters\")\n@click.pass_obj\ndef table(app, project_only, env_only, show_lines, force_ascii):\n    \"\"\"Enumerate dependencies in a tabular format.\"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    from hatch.dep.core import Dependency\n    from hatch.utils.dep import get_complex_dependencies, get_normalized_dependencies, normalize_marker_quoting\n\n    environment = app.project.get_environment()\n\n    project_requirements = []\n    environment_requirements = []\n    if project_only:\n        dependencies, _ = app.project.get_dependencies()\n        dependencies_complex = get_complex_dependencies(dependencies)\n        project_requirements.extend(dependencies_complex.values())\n    elif env_only:\n        environment_requirements.extend(environment.environment_dependencies_complex)\n    else:\n        dependencies, _ = app.project.get_dependencies()\n        dependencies_complex = get_complex_dependencies(dependencies)\n        project_requirements.extend(dependencies_complex.values())\n        environment_requirements.extend(environment.environment_dependencies_complex)\n\n    for all_requirements, table_title in (\n        (project_requirements, \"Project\"),\n        (environment_requirements, f\"Env: {app.env}\"),\n    ):\n        if not all_requirements:\n            continue\n\n        normalized_requirements = [Dependency(d) for d in get_normalized_dependencies(all_requirements)]\n\n        columns = {\"Name\": {}, \"URL\": {}, \"Versions\": {}, \"Markers\": {}, \"Features\": {}}\n        for i, requirement in enumerate(normalized_requirements):\n            columns[\"Name\"][i] = requirement.name\n\n            if requirement.url:\n                columns[\"URL\"][i] = str(requirement.url)\n\n            if requirement.specifier:\n                columns[\"Versions\"][i] = str(requirement.specifier)\n\n            if requirement.marker:\n                columns[\"Markers\"][i] = normalize_marker_quoting(str(requirement.marker))\n\n            if requirement.extras:\n                columns[\"Features\"][i] = \", \".join(sorted(requirement.extras))\n\n        column_options = {}\n        for column_title in columns:\n            if column_title != \"URL\":\n                column_options[column_title] = {\"no_wrap\": True}\n\n        app.display_table(\n            table_title, columns, show_lines=show_lines, column_options=column_options, force_ascii=force_ascii\n        )\n\n\n@show.command(short_help=\"Enumerate dependencies as a list of requirements\")\n@click.option(\"--project-only\", \"-p\", is_flag=True, help=\"Whether or not to exclude environment dependencies\")\n@click.option(\"--env-only\", \"-e\", is_flag=True, help=\"Whether or not to exclude project dependencies\")\n@click.option(\n    \"--feature\",\n    \"-f\",\n    \"features\",\n    multiple=True,\n    help=\"Whether or not to only show the dependencies of the specified features\",\n)\n@click.option(\"--all\", \"all_features\", is_flag=True, help=\"Whether or not to include the dependencies of all features\")\n@click.pass_obj\ndef requirements(app, project_only, env_only, features, all_features):\n    \"\"\"Enumerate dependencies as a list of requirements.\"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    from hatch.utils.dep import get_complex_dependencies, get_complex_features, get_normalized_dependencies\n    from hatchling.metadata.utils import normalize_project_name\n\n    environment = app.project.get_environment()\n    dependencies, optional_dependencies = app.project.get_dependencies()\n    dependencies_complex = get_complex_dependencies(dependencies)\n    optional_dependencies_complex = get_complex_features(optional_dependencies)\n\n    all_requirements = []\n    if features:\n        for raw_feature in features:\n            feature = normalize_project_name(raw_feature)\n            if feature not in optional_dependencies_complex:\n                app.abort(f\"Feature `{feature}` is not defined in field `project.optional-dependencies`\")\n\n            all_requirements.extend(optional_dependencies_complex[feature].values())\n    elif project_only:\n        all_requirements.extend(dependencies_complex.values())\n    elif env_only:\n        all_requirements.extend(environment.environment_dependencies_complex)\n    else:\n        all_requirements.extend(dependencies_complex.values())\n        all_requirements.extend(environment.environment_dependencies_complex)\n\n    if not features and all_features:\n        for optional_dependencies in optional_dependencies_complex.values():\n            all_requirements.extend(optional_dependencies.values())\n\n    for dependency in get_normalized_dependencies(all_requirements):\n        app.display(dependency)\n"
  },
  {
    "path": "src/hatch/cli/env/__init__.py",
    "content": "import click\n\nfrom hatch.cli.env.create import create\nfrom hatch.cli.env.find import find\nfrom hatch.cli.env.prune import prune\nfrom hatch.cli.env.remove import remove\nfrom hatch.cli.env.run import run\nfrom hatch.cli.env.show import show\n\n\n@click.group(short_help=\"Manage project environments\")\ndef env():\n    pass\n\n\nenv.add_command(create)\nenv.add_command(find)\nenv.add_command(prune)\nenv.add_command(remove)\nenv.add_command(run)\nenv.add_command(show)\n"
  },
  {
    "path": "src/hatch/cli/env/create.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom typing import TYPE_CHECKING\n\nimport click\n\nfrom hatch.config.constants import AppEnvVars\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Create environments\")\n@click.argument(\"env_name\", default=\"default\")\n@click.pass_obj\ndef create(app: Application, env_name: str):\n    \"\"\"Create environments.\"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    environments = app.project.expand_environments(env_name)\n    if not environments:\n        app.abort(f\"Environment `{env_name}` is not defined by project config\")\n\n    incompatible = {}\n    for env in environments:\n        environment = app.project.get_environment(env)\n        if environment.exists():\n            app.display_warning(f\"Environment `{env}` already exists\")\n            continue\n\n        try:\n            environment.check_compatibility()\n        except Exception as e:  # noqa: BLE001\n            if env_name in app.project.config.matrices:\n                incompatible[env] = str(e)\n                continue\n\n            app.abort(f\"Environment `{env}` is incompatible: {e}\")\n\n        app.project.prepare_environment(environment, keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)))\n\n    if incompatible:\n        num_incompatible = len(incompatible)\n        app.display_warning(\n            f\"Skipped {num_incompatible} incompatible environment{'s' if num_incompatible > 1 else ''}:\"\n        )\n        for env, reason in incompatible.items():\n            app.display_warning(f\"{env} -> {reason}\")\n"
  },
  {
    "path": "src/hatch/cli/env/find.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Locate environments\")\n@click.argument(\"env_name\", default=\"default\")\n@click.pass_obj\ndef find(app: Application, env_name: str):\n    \"\"\"Locate environments.\"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    environments = app.project.expand_environments(env_name)\n    if not environments:\n        app.abort(f\"Environment `{env_name}` is not defined by project config\")\n\n    for env in environments:\n        environment = app.project.get_environment(env)\n        app.display(environment.find())\n"
  },
  {
    "path": "src/hatch/cli/env/prune.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Remove all environments\")\n@click.pass_obj\ndef prune(app: Application):\n    \"\"\"Remove all environments.\"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    environment_types = app.plugins.environment.collect()\n\n    for environments in (app.project.config.envs, app.project.config.internal_envs):\n        for env_name in environments:\n            if env_name == app.env_active:\n                app.abort(f\"Cannot remove active environment: {env_name}\")\n\n        for env_name, config in environments.items():\n            environment_type = config[\"type\"]\n            if environment_type not in environment_types:\n                app.abort(f\"Environment `{env_name}` has unknown type: {environment_type}\")\n\n            environment = environment_types[environment_type](\n                app.project.location,\n                app.project.metadata,\n                env_name,\n                config,\n                app.project.config.matrix_variables.get(env_name, {}),\n                app.get_env_directory(environment_type),\n                app.data_dir / \"env\" / environment_type,\n                app.platform,\n                app.verbosity,\n                app,\n            )\n            if environment.exists():\n                with app.status(f\"Removing environment: {env_name}\"):\n                    environment.remove()\n"
  },
  {
    "path": "src/hatch/cli/env/remove.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Remove environments\")\n@click.argument(\"env_name\", default=\"default\")\n@click.pass_context\ndef remove(ctx: click.Context, env_name: str):\n    \"\"\"Remove environments.\"\"\"\n    app: Application = ctx.obj\n    app.ensure_environment_plugin_dependencies()\n\n    if (parameter_source := ctx.get_parameter_source(\"env_name\")) is not None and parameter_source.name == \"DEFAULT\":\n        env_name = app.env\n\n    environments = app.project.expand_environments(env_name)\n    if not environments:\n        app.abort(f\"Environment `{env_name}` is not defined by project config\")\n\n    for env_name in environments:\n        if env_name == app.env_active:\n            app.abort(f\"Cannot remove active environment: {env_name}\")\n\n    for env_name in environments:\n        environment = app.project.get_environment(env_name)\n        if environment.exists():\n            with app.status(f\"Removing environment: {env_name}\"):\n                environment.remove()\n"
  },
  {
    "path": "src/hatch/cli/env/run.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom typing import TYPE_CHECKING\n\nimport click\n\nfrom hatch.config.constants import AppEnvVars\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\ndef filter_environments(environments, filter_data):\n    selected_environments = []\n    for env_name, env_data in environments.items():\n        for key, value in filter_data.items():\n            if key not in env_data or env_data[key] != value:\n                break\n        else:\n            selected_environments.append(env_name)\n\n    return selected_environments\n\n\n@click.command(short_help=\"Run commands within project environments\")\n@click.argument(\"args\", required=True, nargs=-1)\n@click.option(\"--env\", \"-e\", \"env_names\", multiple=True, help=\"The environments to target\")\n@click.option(\"--include\", \"-i\", \"included_variable_specs\", multiple=True, help=\"The matrix variables to include\")\n@click.option(\"--exclude\", \"-x\", \"excluded_variable_specs\", multiple=True, help=\"The matrix variables to exclude\")\n@click.option(\"--filter\", \"-f\", \"filter_json\", default=None, help=\"The JSON data used to select environments\")\n@click.option(\n    \"--force-continue\", is_flag=True, help=\"Run every command and if there were any errors exit with the first code\"\n)\n@click.option(\"--ignore-compat\", is_flag=True, help=\"Ignore incompatibility when selecting specific environments\")\n@click.pass_obj\ndef run(\n    app: Application,\n    *,\n    args: tuple[str, ...],\n    env_names: tuple[str, ...],\n    included_variable_specs: tuple[str, ...],\n    excluded_variable_specs: tuple[str, ...],\n    filter_json: str | None,\n    force_continue: bool,\n    ignore_compat: bool,\n):\n    \"\"\"\n    Run commands within project environments.\n\n    The `-e`/`--env` option overrides the equivalent [root option](#hatch) and the `HATCH_ENV` environment variable.\n\n    The `-i`/`--include` and `-x`/`--exclude` options may be used to include or exclude certain\n    variables, optionally followed by specific comma-separated values, and may be selected multiple\n    times. For example, if you have the following configuration:\n\n    \\b\n    ```toml config-example\n    [[tool.hatch.envs.test.matrix]]\n    python = [\"3.9\", \"3.10\"]\n    version = [\"42\", \"3.14\", \"9000\"]\n    ```\n\n    then running:\n\n    \\b\n    ```\n    hatch env run -i py=3.10 -x version=9000 test:pytest\n    ```\n\n    would execute `pytest` in the environments `test.py3.10-42` and `test.py3.10-3.14`.\n    Note that `py` may be used as an alias for `python`.\n\n    \\b\n    !!! note\n        The inclusion option is treated as an intersection while the exclusion option is treated as a\n        union i.e. an environment must match all of the included variables to be selected while matching\n        any of the excluded variables will prevent selection.\n    \"\"\"\n    from hatch.utils.runner import parse_matrix_variables, select_environments\n\n    try:\n        included_variables = parse_matrix_variables(included_variable_specs)\n    except ValueError as e:\n        app.abort(f\"Duplicate included variable: {e}\")\n\n    try:\n        excluded_variables = parse_matrix_variables(excluded_variable_specs)\n    except ValueError as e:\n        app.abort(f\"Duplicate excluded variable: {e}\")\n\n    app.ensure_environment_plugin_dependencies()\n\n    project = app.project\n\n    if not env_names:\n        env_names = (app.env,)\n    elif \"system\" in env_names:\n        project.config.config[\"envs\"] = {\n            \"system\": {\n                \"type\": \"system\",\n                \"skip-install\": True,\n                \"scripts\": project.config.scripts,\n            }\n        }\n\n    # Deduplicate\n    ordered_env_names = list(dict.fromkeys(env_names))\n\n    environments = []\n    matrix_selected = False\n    for env_name in ordered_env_names:\n        if env_name in project.config.matrices:\n            matrix_selected = True\n            env_data = project.config.matrices[env_name][\"envs\"]\n            if not env_data:\n                app.abort(f\"No variables defined for matrix: {env_name}\")\n\n            environments.extend(select_environments(env_data, included_variables, excluded_variables))\n        else:\n            environments.append(env_name)\n\n    if filter_json:\n        import json\n\n        filter_data = json.loads(filter_json)\n        if not isinstance(filter_data, dict):\n            app.abort(\"The --filter/-f option must be a JSON mapping\")\n\n        environments[:] = filter_environments(project.config.envs, filter_data)\n\n    if not environments:\n        app.abort(\"No environments were selected\")\n    elif not matrix_selected and (included_variables or excluded_variables):\n        app.abort(f\"Variable selection is unsupported for non-matrix environments: {', '.join(ordered_env_names)}\")\n\n    for context in app.runner_context(\n        environments,\n        ignore_compat=ignore_compat or matrix_selected,\n        display_header=matrix_selected,\n        keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)),\n    ):\n        if context.env.name == \"system\":\n            context.env.exists = lambda: True  # type: ignore[method-assign]\n\n        context.force_continue = force_continue\n        context.add_shell_command(list(args))\n"
  },
  {
    "path": "src/hatch/cli/env/show.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Show the available environments\")\n@click.argument(\"envs\", required=False, nargs=-1)\n@click.option(\"--ascii\", \"force_ascii\", is_flag=True, help=\"Whether or not to only use ASCII characters\")\n@click.option(\"--json\", \"as_json\", is_flag=True, help=\"Whether or not to output in JSON format\")\n@click.option(\"--internal\", \"-i\", is_flag=True, help=\"Show internal environments\")\n@click.option(\"--hide-titles\", is_flag=True, hidden=True)\n@click.pass_obj\ndef show(\n    app: Application,\n    *,\n    envs: tuple[str, ...],\n    force_ascii: bool,\n    as_json: bool,\n    internal: bool,\n    hide_titles: bool,\n):\n    \"\"\"Show the available environments.\"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    from hatch.config.constants import AppEnvVars\n\n    if as_json:\n        import json\n\n        contextual_config = {}\n        for environments in (app.project.config.envs, app.project.config.internal_envs):\n            for env_name, config in environments.items():\n                environment = app.project.get_environment(env_name)\n                new_config = contextual_config[env_name] = dict(config)\n\n                env_vars = dict(environment.env_vars)\n                env_vars.pop(AppEnvVars.ENV_ACTIVE)\n                if env_vars:\n                    new_config[\"env-vars\"] = env_vars\n\n                num_dependencies = len(config.get(\"dependencies\", []))\n                dependencies = environment.environment_dependencies[:num_dependencies]\n                if dependencies:\n                    new_config[\"dependencies\"] = dependencies\n\n                extra_dependencies = environment.environment_dependencies[num_dependencies:]\n                if extra_dependencies:\n                    new_config[\"extra-dependencies\"] = extra_dependencies\n\n                if environment.pre_install_commands:\n                    new_config[\"pre-install-commands\"] = list(\n                        environment.resolve_commands(environment.pre_install_commands)\n                    )\n\n                if environment.post_install_commands:\n                    new_config[\"post-install-commands\"] = list(\n                        environment.resolve_commands(environment.post_install_commands)\n                    )\n\n                if environment.scripts:\n                    new_config[\"scripts\"] = {\n                        script: list(environment.resolve_commands([script])) for script in environment.scripts\n                    }\n\n        app.display(json.dumps(contextual_config, separators=(\",\", \":\")))\n        return\n\n    from hatch.dep.core import Dependency, InvalidDependencyError\n    from hatchling.metadata.utils import get_normalized_dependency, normalize_project_name\n\n    if internal:\n        target_standalone_envs = app.project.config.internal_envs\n        target_matrices = app.project.config.internal_matrices\n    else:\n        target_standalone_envs = app.project.config.envs\n        target_matrices = app.project.config.matrices\n\n    for env_name in envs:\n        if env_name not in target_standalone_envs and env_name not in target_matrices:\n            app.abort(f\"Environment `{env_name}` is not defined by project config\")\n\n    env_names = set(envs)\n\n    matrix_columns: dict[str, dict[int, str]] = {\n        \"Name\": {},\n        \"Type\": {},\n        \"Envs\": {},\n        \"Features\": {},\n        \"Dependencies\": {},\n        \"Environment variables\": {},\n        \"Scripts\": {},\n        \"Description\": {},\n    }\n    matrix_envs = set()\n    for i, (matrix_name, matrix_data) in enumerate(target_matrices.items()):\n        matrix_envs.update(matrix_data[\"envs\"])\n\n        if env_names and matrix_name not in env_names:\n            continue\n\n        config = matrix_data[\"config\"]\n        matrix_columns[\"Name\"][i] = matrix_name\n        matrix_columns[\"Type\"][i] = config[\"type\"]\n        matrix_columns[\"Envs\"][i] = \"\\n\".join(matrix_data[\"envs\"])\n\n        if config.get(\"features\"):\n            if app.project.metadata.hatch.metadata.allow_ambiguous_features:\n                matrix_columns[\"Features\"][i] = \"\\n\".join(sorted(set(config[\"features\"])))\n            else:\n                matrix_columns[\"Features\"][i] = \"\\n\".join(\n                    sorted({normalize_project_name(f) for f in config[\"features\"]})\n                )\n\n        dependencies = []\n        if config.get(\"dependencies\"):\n            dependencies.extend(config[\"dependencies\"])\n        if config.get(\"extra-dependencies\"):\n            dependencies.extend(config[\"extra-dependencies\"])\n        if dependencies:\n            normalized_dependencies = set()\n            for dependency in dependencies:\n                try:\n                    req = Dependency(dependency)\n                except InvalidDependencyError:\n                    normalized_dependencies.add(dependency)\n                else:\n                    normalized_dependencies.add(get_normalized_dependency(req))\n\n            matrix_columns[\"Dependencies\"][i] = \"\\n\".join(sorted(normalized_dependencies))\n\n        if config.get(\"env-vars\"):\n            matrix_columns[\"Environment variables\"][i] = \"\\n\".join(\n                \"=\".join(item) for item in sorted(config[\"env-vars\"].items())\n            )\n\n        if config.get(\"scripts\"):\n            matrix_columns[\"Scripts\"][i] = \"\\n\".join(\n                sorted(script for script in config[\"scripts\"] if app.verbose or not script.startswith(\"_\"))\n            )\n\n        if config.get(\"description\"):\n            matrix_columns[\"Description\"][i] = config[\"description\"].strip()\n\n    standalone_columns: dict[str, dict[int, str]] = {\n        \"Name\": {},\n        \"Type\": {},\n        \"Features\": {},\n        \"Dependencies\": {},\n        \"Environment variables\": {},\n        \"Scripts\": {},\n        \"Description\": {},\n    }\n    standalone_envs = (\n        (env_name, config)\n        for env_name, config in target_standalone_envs.items()\n        if env_names or env_name not in matrix_envs\n    )\n    for i, (env_name, config) in enumerate(standalone_envs):\n        if env_names and env_name not in env_names:\n            continue\n\n        environment = app.project.get_environment(env_name)\n\n        standalone_columns[\"Name\"][i] = env_name\n        standalone_columns[\"Type\"][i] = config[\"type\"]\n\n        if environment.features:\n            standalone_columns[\"Features\"][i] = \"\\n\".join(environment.features)\n\n        if environment.environment_dependencies_complex:\n            standalone_columns[\"Dependencies\"][i] = \"\\n\".join(\n                sorted({get_normalized_dependency(d) for d in environment.environment_dependencies_complex})\n            )\n\n        env_vars = dict(environment.env_vars)\n        env_vars.pop(AppEnvVars.ENV_ACTIVE)\n        if env_vars:\n            standalone_columns[\"Environment variables\"][i] = \"\\n\".join(\n                \"=\".join(item) for item in sorted(env_vars.items())\n            )\n\n        if environment.scripts:\n            standalone_columns[\"Scripts\"][i] = \"\\n\".join(\n                sorted(script for script in environment.scripts if app.verbose or not script.startswith(\"_\"))\n            )\n\n        if environment.description:\n            standalone_columns[\"Description\"][i] = environment.description.strip()\n\n    column_options = {}\n    for title in matrix_columns:\n        if title != \"Description\":\n            column_options[title] = {\"no_wrap\": True}\n\n    app.display_table(\n        \"\" if hide_titles else \"Standalone\",\n        standalone_columns,\n        show_lines=True,\n        column_options=column_options,\n        force_ascii=force_ascii,\n    )\n    app.display_table(\n        \"\" if hide_titles else \"Matrices\",\n        matrix_columns,\n        show_lines=True,\n        column_options=column_options,\n        force_ascii=force_ascii,\n    )\n"
  },
  {
    "path": "src/hatch/cli/fmt/__init__.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Format and lint source code\", context_settings={\"ignore_unknown_options\": True})\n@click.argument(\"args\", nargs=-1)\n@click.option(\"--check\", is_flag=True, help=\"Only check for errors rather than fixing them\")\n@click.option(\"--linter\", \"-l\", is_flag=True, help=\"Only run the linter\")\n@click.option(\"--formatter\", \"-f\", is_flag=True, help=\"Only run the formatter\")\n@click.option(\"--sync\", is_flag=True, help=\"Sync the default config file with the current version of Hatch\")\n@click.pass_obj\ndef fmt(\n    app: Application,\n    *,\n    args: tuple[str, ...],\n    check: bool,\n    linter: bool,\n    formatter: bool,\n    sync: bool,\n):\n    \"\"\"Format and lint source code.\"\"\"\n    if linter and formatter:\n        app.abort(\"Cannot specify both --linter and --formatter\")\n\n    from hatch.cli.fmt.core import StaticAnalysisEnvironment\n\n    app.ensure_environment_plugin_dependencies()\n\n    for context in app.runner_context([\"hatch-static-analysis\"]):\n        sa_env = StaticAnalysisEnvironment(context.env)\n\n        # TODO: remove in a few minor releases, this is very new but we don't want to break users on the cutting edge\n        if legacy_config_path := app.project.config.config.get(\"format\", {}).get(\"config-path\", \"\"):\n            app.display_warning(\n                \"The `tool.hatch.format.config-path` option is deprecated and will be removed in a future release. \"\n                \"Use `tool.hatch.envs.hatch-static-analysis.config-path` instead.\"\n            )\n            sa_env.config_path = legacy_config_path\n\n        if sync and not sa_env.config_path:\n            app.abort(\"The --sync flag can only be used when the `tool.hatch.format.config-path` option is defined\")\n\n        scripts: list[str] = []\n        if not formatter:\n            scripts.append(\"lint-check\" if check else \"lint-fix\")\n\n        if not linter:\n            scripts.append(\"format-check\" if check else \"format-fix\")\n\n        default_args = sa_env.get_default_args()\n        arguments = list(args)\n        try:\n            arguments.remove(\"--preview\")\n        except ValueError:\n            preview = False\n        else:\n            preview = True\n            default_args.append(\"--preview\")\n\n        internal_args = context.env.join_command_args(default_args)\n        if internal_args:\n            # Add an extra space if required\n            internal_args = f\" {internal_args}\"\n\n        formatted_args = context.env.join_command_args(arguments)\n        for script in scripts:\n            context.add_shell_command(f\"{script} {formatted_args}\")\n\n        context.env_vars[\"HATCH_FMT_ARGS\"] = internal_args\n\n        if not sa_env.config_path or sync:\n            sa_env.write_config_file(preview=preview)\n"
  },
  {
    "path": "src/hatch/cli/fmt/core.py",
    "content": "from __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, Any\n\nif TYPE_CHECKING:\n    from hatch.env.plugin.interface import EnvironmentInterface\n    from hatch.utils.fs import Path\n\n\nclass StaticAnalysisEnvironment:\n    def __init__(self, env: EnvironmentInterface) -> None:\n        self.env = env\n\n    @cached_property\n    def config_path(self) -> str:\n        return self.env.config.get(\"config-path\", \"\")\n\n    def get_default_args(self) -> list[str]:\n        default_args = []\n        if not self.config_path:\n            if self.internal_user_config_file is None:\n                default_args.extend([\"--config\", str(self.internal_config_file)])\n            else:\n                default_args.extend([\"--config\", str(self.internal_user_config_file)])\n\n        return default_args\n\n    @cached_property\n    def internal_config_file(self) -> Path:\n        return self.env.isolated_data_directory / \".config\" / self.env.root.id / \"ruff_defaults.toml\"\n\n    def construct_config_file(self, *, preview: bool) -> str:\n        lines = [\n            \"line-length = 120\",\n            \"\",\n            \"[format]\",\n            \"docstring-code-format = true\",\n            \"docstring-code-line-length = 80\",\n            \"\",\n            \"[lint]\",\n        ]\n\n        # Selected rules\n        rules = list(STABLE_RULES)\n        if preview or self.linter_preview:\n            rules.extend(PREVIEW_RULES)\n        rules.sort()\n\n        lines.append(\"select = [\")\n        lines.extend(f'  \"{rule}\",' for rule in rules)\n        lines.extend((\"]\", \"\"))\n\n        # Ignored rules\n        lines.append(\"[lint.per-file-ignores]\")\n        for glob, ignored_rules in PER_FILE_IGNORED_RULES.items():\n            lines.append(f'\"{glob}\" = [')\n            lines.extend(f'  \"{ignored_rule}\",' for ignored_rule in ignored_rules)\n            lines.append(\"]\")\n\n        # Default config\n        lines.extend((\n            \"\",\n            \"[lint.flake8-tidy-imports]\",\n            'ban-relative-imports = \"all\"',\n            \"\",\n            \"[lint.isort]\",\n            f'known-first-party = [\"{self.env.metadata.name.replace(\"-\", \"_\")}\"]',\n            \"\",\n            \"[lint.flake8-pytest-style]\",\n            \"fixture-parentheses = false\",\n            \"mark-parentheses = false\",\n        ))\n\n        # Ensure the file ends with a newline to satisfy other linters\n        lines.append(\"\")\n\n        return \"\\n\".join(lines)\n\n    def write_config_file(self, *, preview: bool) -> None:\n        config_contents = self.construct_config_file(preview=preview)\n        if self.config_path:\n            (self.env.root / self.config_path).write_atomic(config_contents, \"w\", encoding=\"utf-8\")\n            return\n\n        self.internal_config_file.parent.ensure_dir_exists()\n        self.internal_config_file.write_text(config_contents)\n\n        # TODO: remove everything below once this is fixed https://github.com/astral-sh/ruff/issues/8737\n        if self.internal_user_config_file is None:\n            return\n\n        if self.user_config_file is None:\n            return\n\n        old_contents = self.user_config_file.read_text()\n        config_path = str(self.internal_config_file).replace(\"\\\\\", \"\\\\\\\\\")\n        if self.user_config_file.name == \"pyproject.toml\":\n            lines = old_contents.splitlines()\n            try:\n                index = lines.index(\"[tool.ruff]\")\n            except ValueError:\n                lines.extend((\n                    \"\",\n                    \"[tool.ruff]\",\n                    f'extend = \"{config_path}\"',\n                ))\n            else:\n                lines.insert(index + 1, f'extend = \"{config_path}\"')\n\n            contents = \"\\n\".join(lines)\n        else:\n            contents = f'extend = \"{config_path}\"\\n{old_contents}'\n\n        self.internal_user_config_file.write_text(contents)\n\n    @cached_property\n    def internal_user_config_file(self) -> Path | None:\n        if self.user_config_file is None:\n            return None\n\n        return self.internal_config_file.parent / self.user_config_file.name\n\n    @cached_property\n    def user_config_file(self) -> Path | None:\n        # https://docs.astral.sh/ruff/configuration/#config-file-discovery\n        for possible_config in (\".ruff.toml\", \"ruff.toml\", \"pyproject.toml\"):\n            if (config_file := (self.env.root / possible_config)).is_file():\n                return config_file\n\n        return None\n\n    @cached_property\n    def user_config(self) -> dict[str, Any]:\n        if self.user_config_file is None:\n            return {}\n\n        from hatch.utils.toml import load_toml_data\n\n        return load_toml_data(self.user_config_file.read_text())\n\n    @cached_property\n    def linter_preview(self) -> bool:\n        return self.get_config(\"lint\").get(\"preview\", False)\n\n    @cached_property\n    def formatter_preview(self) -> bool:\n        return self.get_config(\"format\").get(\"preview\", False)\n\n    def get_config(self, section: str) -> dict[str, Any]:\n        if self.user_config_file is None:\n            return {}\n\n        if self.user_config_file.name == \"pyproject.toml\":\n            return self.user_config.get(\"tool\", {}).get(\"ruff\", {}).get(section, {})\n\n        return self.user_config.get(section, {})\n\n\nSTABLE_RULES: tuple[str, ...] = (\n    \"A001\",\n    \"A002\",\n    \"A003\",\n    \"A004\",\n    \"A005\",\n    \"A006\",\n    \"ARG001\",\n    \"ARG002\",\n    \"ARG003\",\n    \"ARG004\",\n    \"ARG005\",\n    \"ASYNC100\",\n    \"ASYNC105\",\n    \"ASYNC109\",\n    \"ASYNC110\",\n    \"ASYNC115\",\n    \"ASYNC116\",\n    \"ASYNC210\",\n    \"ASYNC220\",\n    \"ASYNC221\",\n    \"ASYNC222\",\n    \"ASYNC230\",\n    \"ASYNC251\",\n    \"B002\",\n    \"B003\",\n    \"B004\",\n    \"B005\",\n    \"B006\",\n    \"B007\",\n    \"B008\",\n    \"B009\",\n    \"B010\",\n    \"B011\",\n    \"B012\",\n    \"B013\",\n    \"B014\",\n    \"B015\",\n    \"B016\",\n    \"B017\",\n    \"B018\",\n    \"B019\",\n    \"B020\",\n    \"B021\",\n    \"B022\",\n    \"B023\",\n    \"B024\",\n    \"B025\",\n    \"B026\",\n    \"B028\",\n    \"B029\",\n    \"B030\",\n    \"B031\",\n    \"B032\",\n    \"B033\",\n    \"B034\",\n    \"B035\",\n    \"B039\",\n    \"B904\",\n    \"B905\",\n    \"B911\",\n    \"BLE001\",\n    \"C400\",\n    \"C401\",\n    \"C402\",\n    \"C403\",\n    \"C404\",\n    \"C405\",\n    \"C406\",\n    \"C408\",\n    \"C409\",\n    \"C410\",\n    \"C411\",\n    \"C413\",\n    \"C414\",\n    \"C415\",\n    \"C416\",\n    \"C417\",\n    \"C418\",\n    \"C419\",\n    \"C420\",\n    \"COM818\",\n    \"DTZ001\",\n    \"DTZ002\",\n    \"DTZ003\",\n    \"DTZ004\",\n    \"DTZ005\",\n    \"DTZ006\",\n    \"DTZ007\",\n    \"DTZ011\",\n    \"DTZ012\",\n    \"DTZ901\",\n    \"E101\",\n    \"E401\",\n    \"E402\",\n    \"E701\",\n    \"E702\",\n    \"E703\",\n    \"E711\",\n    \"E712\",\n    \"E713\",\n    \"E714\",\n    \"E721\",\n    \"E722\",\n    \"E731\",\n    \"E741\",\n    \"E742\",\n    \"E743\",\n    \"E902\",\n    \"EM101\",\n    \"EM102\",\n    \"EM103\",\n    \"EXE001\",\n    \"EXE002\",\n    \"EXE003\",\n    \"EXE004\",\n    \"EXE005\",\n    \"F401\",\n    \"F402\",\n    \"F403\",\n    \"F404\",\n    \"F405\",\n    \"F406\",\n    \"F407\",\n    \"F501\",\n    \"F502\",\n    \"F503\",\n    \"F504\",\n    \"F505\",\n    \"F506\",\n    \"F507\",\n    \"F508\",\n    \"F509\",\n    \"F521\",\n    \"F522\",\n    \"F523\",\n    \"F524\",\n    \"F525\",\n    \"F541\",\n    \"F601\",\n    \"F602\",\n    \"F621\",\n    \"F622\",\n    \"F631\",\n    \"F632\",\n    \"F633\",\n    \"F634\",\n    \"F701\",\n    \"F702\",\n    \"F704\",\n    \"F706\",\n    \"F707\",\n    \"F722\",\n    \"F811\",\n    \"F821\",\n    \"F822\",\n    \"F823\",\n    \"F841\",\n    \"F842\",\n    \"F901\",\n    \"FA100\",\n    \"FA102\",\n    \"FAST001\",\n    \"FAST002\",\n    \"FAST003\",\n    \"FBT001\",\n    \"FBT002\",\n    \"FLY002\",\n    \"FURB105\",\n    \"FURB116\",\n    \"FURB122\",\n    \"FURB129\",\n    \"FURB132\",\n    \"FURB136\",\n    \"FURB157\",\n    \"FURB161\",\n    \"FURB162\",\n    \"FURB163\",\n    \"FURB166\",\n    \"FURB167\",\n    \"FURB168\",\n    \"FURB169\",\n    \"FURB177\",\n    \"FURB181\",\n    \"FURB187\",\n    \"FURB188\",\n    \"G001\",\n    \"G002\",\n    \"G003\",\n    \"G004\",\n    \"G010\",\n    \"G101\",\n    \"G201\",\n    \"G202\",\n    \"I001\",\n    \"I002\",\n    \"ICN001\",\n    \"ICN002\",\n    \"ICN003\",\n    \"INP001\",\n    \"INT001\",\n    \"INT002\",\n    \"INT003\",\n    \"ISC003\",\n    \"LOG001\",\n    \"LOG002\",\n    \"LOG007\",\n    \"LOG009\",\n    \"LOG014\",\n    \"LOG015\",\n    \"N801\",\n    \"N802\",\n    \"N803\",\n    \"N804\",\n    \"N805\",\n    \"N806\",\n    \"N807\",\n    \"N811\",\n    \"N812\",\n    \"N813\",\n    \"N814\",\n    \"N815\",\n    \"N816\",\n    \"N817\",\n    \"N818\",\n    \"N999\",\n    \"PERF101\",\n    \"PERF102\",\n    \"PERF401\",\n    \"PERF402\",\n    \"PERF403\",\n    \"PGH005\",\n    \"PIE790\",\n    \"PIE794\",\n    \"PIE796\",\n    \"PIE800\",\n    \"PIE804\",\n    \"PIE807\",\n    \"PIE808\",\n    \"PIE810\",\n    \"PLC0105\",\n    \"PLC0131\",\n    \"PLC0132\",\n    \"PLC0205\",\n    \"PLC0206\",\n    \"PLC0208\",\n    \"PLC0414\",\n    \"PLC0415\",\n    \"PLC1802\",\n    \"PLC2401\",\n    \"PLC2403\",\n    \"PLC3002\",\n    \"PLE0100\",\n    \"PLE0101\",\n    \"PLE0115\",\n    \"PLE0116\",\n    \"PLE0117\",\n    \"PLE0118\",\n    \"PLE0237\",\n    \"PLE0241\",\n    \"PLE0302\",\n    \"PLE0303\",\n    \"PLE0305\",\n    \"PLE0307\",\n    \"PLE0308\",\n    \"PLE0309\",\n    \"PLE0604\",\n    \"PLE0605\",\n    \"PLE0643\",\n    \"PLE0704\",\n    \"PLE1132\",\n    \"PLE1142\",\n    \"PLE1205\",\n    \"PLE1206\",\n    \"PLE1300\",\n    \"PLE1307\",\n    \"PLE1310\",\n    \"PLE1507\",\n    \"PLE1519\",\n    \"PLE1520\",\n    \"PLE1700\",\n    \"PLE2502\",\n    \"PLE2510\",\n    \"PLE2512\",\n    \"PLE2513\",\n    \"PLE2514\",\n    \"PLE2515\",\n    \"PLR0124\",\n    \"PLR0133\",\n    \"PLR0206\",\n    \"PLR0402\",\n    \"PLR1704\",\n    \"PLR1711\",\n    \"PLR1714\",\n    \"PLR1716\",\n    \"PLR1722\",\n    \"PLR1730\",\n    \"PLR1733\",\n    \"PLR1736\",\n    \"PLR2004\",\n    \"PLR2044\",\n    \"PLR5501\",\n    \"PLW0120\",\n    \"PLW0127\",\n    \"PLW0128\",\n    \"PLW0129\",\n    \"PLW0131\",\n    \"PLW0133\",\n    \"PLW0177\",\n    \"PLW0211\",\n    \"PLW0245\",\n    \"PLW0406\",\n    \"PLW0602\",\n    \"PLW0603\",\n    \"PLW0604\",\n    \"PLW0642\",\n    \"PLW0711\",\n    \"PLW1501\",\n    \"PLW1507\",\n    \"PLW1508\",\n    \"PLW1509\",\n    \"PLW1510\",\n    \"PLW1641\",\n    \"PLW2101\",\n    \"PLW2901\",\n    \"PLW3301\",\n    \"PT001\",\n    \"PT002\",\n    \"PT003\",\n    \"PT006\",\n    \"PT007\",\n    \"PT008\",\n    \"PT009\",\n    \"PT010\",\n    \"PT011\",\n    \"PT012\",\n    \"PT013\",\n    \"PT014\",\n    \"PT015\",\n    \"PT016\",\n    \"PT017\",\n    \"PT018\",\n    \"PT019\",\n    \"PT020\",\n    \"PT021\",\n    \"PT022\",\n    \"PT023\",\n    \"PT024\",\n    \"PT025\",\n    \"PT026\",\n    \"PT027\",\n    \"PT028\",\n    \"PT030\",\n    \"PT031\",\n    \"PYI001\",\n    \"PYI002\",\n    \"PYI003\",\n    \"PYI004\",\n    \"PYI005\",\n    \"PYI006\",\n    \"PYI007\",\n    \"PYI008\",\n    \"PYI009\",\n    \"PYI010\",\n    \"PYI011\",\n    \"PYI012\",\n    \"PYI013\",\n    \"PYI014\",\n    \"PYI015\",\n    \"PYI016\",\n    \"PYI017\",\n    \"PYI018\",\n    \"PYI019\",\n    \"PYI020\",\n    \"PYI021\",\n    \"PYI024\",\n    \"PYI025\",\n    \"PYI026\",\n    \"PYI029\",\n    \"PYI030\",\n    \"PYI032\",\n    \"PYI033\",\n    \"PYI034\",\n    \"PYI035\",\n    \"PYI036\",\n    \"PYI041\",\n    \"PYI042\",\n    \"PYI043\",\n    \"PYI044\",\n    \"PYI045\",\n    \"PYI046\",\n    \"PYI047\",\n    \"PYI048\",\n    \"PYI049\",\n    \"PYI050\",\n    \"PYI051\",\n    \"PYI052\",\n    \"PYI053\",\n    \"PYI054\",\n    \"PYI055\",\n    \"PYI056\",\n    \"PYI057\",\n    \"PYI058\",\n    \"PYI059\",\n    \"PYI061\",\n    \"PYI062\",\n    \"PYI063\",\n    \"PYI064\",\n    \"PYI066\",\n    \"RET503\",\n    \"RET504\",\n    \"RET505\",\n    \"RET506\",\n    \"RET507\",\n    \"RET508\",\n    \"RSE102\",\n    \"RUF001\",\n    \"RUF002\",\n    \"RUF003\",\n    \"RUF005\",\n    \"RUF006\",\n    \"RUF007\",\n    \"RUF008\",\n    \"RUF009\",\n    \"RUF010\",\n    \"RUF012\",\n    \"RUF013\",\n    \"RUF015\",\n    \"RUF016\",\n    \"RUF017\",\n    \"RUF018\",\n    \"RUF019\",\n    \"RUF020\",\n    \"RUF021\",\n    \"RUF022\",\n    \"RUF023\",\n    \"RUF024\",\n    \"RUF026\",\n    \"RUF028\",\n    \"RUF030\",\n    \"RUF032\",\n    \"RUF033\",\n    \"RUF034\",\n    \"RUF040\",\n    \"RUF041\",\n    \"RUF043\",\n    \"RUF046\",\n    \"RUF048\",\n    \"RUF049\",\n    \"RUF051\",\n    \"RUF053\",\n    \"RUF057\",\n    \"RUF058\",\n    \"RUF059\",\n    \"RUF100\",\n    \"RUF101\",\n    \"S101\",\n    \"S102\",\n    \"S103\",\n    \"S104\",\n    \"S105\",\n    \"S106\",\n    \"S107\",\n    \"S108\",\n    \"S110\",\n    \"S112\",\n    \"S113\",\n    \"S201\",\n    \"S202\",\n    \"S301\",\n    \"S302\",\n    \"S303\",\n    \"S304\",\n    \"S305\",\n    \"S306\",\n    \"S307\",\n    \"S308\",\n    \"S310\",\n    \"S311\",\n    \"S312\",\n    \"S313\",\n    \"S314\",\n    \"S315\",\n    \"S316\",\n    \"S317\",\n    \"S318\",\n    \"S319\",\n    \"S321\",\n    \"S323\",\n    \"S324\",\n    \"S501\",\n    \"S502\",\n    \"S503\",\n    \"S504\",\n    \"S505\",\n    \"S506\",\n    \"S507\",\n    \"S508\",\n    \"S509\",\n    \"S601\",\n    \"S602\",\n    \"S604\",\n    \"S605\",\n    \"S606\",\n    \"S607\",\n    \"S608\",\n    \"S609\",\n    \"S610\",\n    \"S611\",\n    \"S612\",\n    \"S701\",\n    \"S702\",\n    \"S704\",\n    \"SIM101\",\n    \"SIM102\",\n    \"SIM103\",\n    \"SIM105\",\n    \"SIM107\",\n    \"SIM108\",\n    \"SIM109\",\n    \"SIM110\",\n    \"SIM112\",\n    \"SIM113\",\n    \"SIM114\",\n    \"SIM115\",\n    \"SIM116\",\n    \"SIM117\",\n    \"SIM118\",\n    \"SIM201\",\n    \"SIM202\",\n    \"SIM208\",\n    \"SIM210\",\n    \"SIM211\",\n    \"SIM212\",\n    \"SIM220\",\n    \"SIM221\",\n    \"SIM222\",\n    \"SIM223\",\n    \"SIM300\",\n    \"SIM905\",\n    \"SIM910\",\n    \"SIM911\",\n    \"SLF001\",\n    \"SLOT000\",\n    \"SLOT001\",\n    \"SLOT002\",\n    \"T100\",\n    \"T201\",\n    \"T203\",\n    \"TC001\",\n    \"TC002\",\n    \"TC003\",\n    \"TC004\",\n    \"TC005\",\n    \"TC006\",\n    \"TC007\",\n    \"TC010\",\n    \"TD004\",\n    \"TD005\",\n    \"TD006\",\n    \"TD007\",\n    \"TID251\",\n    \"TID252\",\n    \"TID253\",\n    \"TRY002\",\n    \"TRY003\",\n    \"TRY004\",\n    \"TRY201\",\n    \"TRY203\",\n    \"TRY300\",\n    \"TRY301\",\n    \"TRY400\",\n    \"TRY401\",\n    \"UP001\",\n    \"UP003\",\n    \"UP004\",\n    \"UP005\",\n    \"UP006\",\n    \"UP007\",\n    \"UP008\",\n    \"UP009\",\n    \"UP010\",\n    \"UP011\",\n    \"UP012\",\n    \"UP013\",\n    \"UP014\",\n    \"UP015\",\n    \"UP017\",\n    \"UP018\",\n    \"UP019\",\n    \"UP020\",\n    \"UP021\",\n    \"UP022\",\n    \"UP023\",\n    \"UP024\",\n    \"UP025\",\n    \"UP026\",\n    \"UP028\",\n    \"UP029\",\n    \"UP030\",\n    \"UP031\",\n    \"UP032\",\n    \"UP033\",\n    \"UP034\",\n    \"UP035\",\n    \"UP036\",\n    \"UP037\",\n    \"UP039\",\n    \"UP040\",\n    \"UP041\",\n    \"UP043\",\n    \"UP044\",\n    \"UP045\",\n    \"UP046\",\n    \"UP047\",\n    \"UP049\",\n    \"UP050\",\n    \"W291\",\n    \"W292\",\n    \"W293\",\n    \"W505\",\n    \"W605\",\n    \"YTT101\",\n    \"YTT102\",\n    \"YTT103\",\n    \"YTT201\",\n    \"YTT202\",\n    \"YTT203\",\n    \"YTT204\",\n    \"YTT301\",\n    \"YTT302\",\n    \"YTT303\",\n)\nPREVIEW_RULES: tuple[str, ...] = (\n    \"ASYNC212\",\n    \"ASYNC240\",\n    \"ASYNC250\",\n    \"B901\",\n    \"B903\",\n    \"B909\",\n    \"B912\",\n    \"DOC201\",\n    \"DOC202\",\n    \"DOC402\",\n    \"DOC403\",\n    \"DOC501\",\n    \"DOC502\",\n    \"E112\",\n    \"E113\",\n    \"E115\",\n    \"E116\",\n    \"E201\",\n    \"E202\",\n    \"E203\",\n    \"E204\",\n    \"E211\",\n    \"E221\",\n    \"E222\",\n    \"E223\",\n    \"E224\",\n    \"E225\",\n    \"E226\",\n    \"E227\",\n    \"E228\",\n    \"E231\",\n    \"E241\",\n    \"E242\",\n    \"E251\",\n    \"E252\",\n    \"E261\",\n    \"E262\",\n    \"E265\",\n    \"E266\",\n    \"E271\",\n    \"E272\",\n    \"E273\",\n    \"E274\",\n    \"E275\",\n    \"E502\",\n    \"FURB110\",\n    \"FURB113\",\n    \"FURB118\",\n    \"FURB131\",\n    \"FURB142\",\n    \"FURB145\",\n    \"FURB148\",\n    \"FURB152\",\n    \"FURB154\",\n    \"FURB156\",\n    \"FURB164\",\n    \"FURB171\",\n    \"FURB180\",\n    \"FURB189\",\n    \"FURB192\",\n    \"LOG004\",\n    \"PLC0207\",\n    \"PLC1901\",\n    \"PLC2701\",\n    \"PLC2801\",\n    \"PLE0304\",\n    \"PLE1141\",\n    \"PLE4703\",\n    \"PLR0202\",\n    \"PLR0203\",\n    \"PLR6104\",\n    \"PLR6201\",\n    \"PLR6301\",\n    \"PLW0108\",\n    \"PLW0244\",\n    \"PLW1514\",\n    \"PLW3201\",\n    \"PT029\",\n    \"RUF027\",\n    \"RUF029\",\n    \"RUF031\",\n    \"RUF036\",\n    \"RUF037\",\n    \"RUF038\",\n    \"RUF039\",\n    \"RUF045\",\n    \"RUF047\",\n    \"RUF052\",\n    \"RUF054\",\n    \"RUF055\",\n    \"RUF056\",\n    \"RUF060\",\n    \"RUF061\",\n    \"RUF063\",\n    \"RUF064\",\n    \"RUF065\",\n    \"RUF102\",\n    \"S401\",\n    \"S402\",\n    \"S403\",\n    \"S405\",\n    \"S406\",\n    \"S407\",\n    \"S408\",\n    \"S409\",\n    \"S411\",\n    \"S412\",\n    \"S413\",\n    \"S415\",\n    \"TC008\",\n    \"UP042\",\n    \"W391\",\n)\nPER_FILE_IGNORED_RULES: dict[str, list[str]] = {\n    \"**/scripts/*\": [\n        \"INP001\",\n        \"T201\",\n    ],\n    \"**/tests/**/*\": [\n        \"PLC1901\",\n        \"PLR2004\",\n        \"PLR6301\",\n        \"S\",\n        \"TID252\",\n    ],\n}\n"
  },
  {
    "path": "src/hatch/cli/new/__init__.py",
    "content": "import click\n\n\n@click.command(short_help=\"Create or initialize a project\")\n@click.argument(\"name\", required=False)\n@click.argument(\"location\", required=False)\n@click.option(\"--interactive\", \"-i\", \"interactive\", is_flag=True, help=\"Interactively choose details about the project\")\n@click.option(\"--cli\", \"feature_cli\", is_flag=True, help=\"Give the project a command line interface\")\n@click.option(\"--init\", \"initialize\", is_flag=True, help=\"Initialize an existing project\")\n@click.option(\"-so\", \"setuptools_options\", multiple=True, hidden=True)\n@click.pass_obj\ndef new(app, name, location, interactive, feature_cli, initialize, setuptools_options):\n    \"\"\"Create or initialize a project.\"\"\"\n    import sys\n    from copy import deepcopy\n    from datetime import datetime, timezone\n\n    from hatch.config.model import TemplateConfig\n    from hatch.project.core import Project\n    from hatch.template import File\n    from hatch.utils.fs import Path\n\n    if location:\n        location = Path(location).resolve()\n\n    migration_possible = False\n    if initialize:\n        interactive = True\n        location = location or Path.cwd()\n        if (location / \"setup.py\").is_file() or (location / \"setup.cfg\").is_file():\n            migration_possible = True\n            if not name:\n                name = \"temporary\"\n\n    if not name:\n        if not interactive:\n            app.abort(\"Missing required argument for the project name, use the -i/--interactive flag.\")\n\n        name = app.prompt(\"Project name\")\n\n    normalized_name = Project.canonicalize_name(name, strict=False)\n    if not location:\n        location = Path(normalized_name).resolve()\n\n    needs_config_update = False\n    if location.is_file():\n        app.abort(f\"Path `{location}` points to a file.\")\n    elif location.is_dir() and any(location.iterdir()):\n        if not initialize:\n            app.abort(f\"Directory `{location}` is not empty.\")\n\n        needs_config_update = (location / \"pyproject.toml\").is_file()\n\n    if migration_possible:\n        from hatch.cli.new.migrate import migrate\n        from hatch.venv.core import TempVirtualEnv\n\n        try:\n            with (\n                app.status(\"Migrating project metadata from setuptools\"),\n                TempVirtualEnv(sys.executable, app.platform) as venv,\n            ):\n                app.platform.run_command([\"python\", \"-m\", \"pip\", \"install\", \"-q\", \"setuptools\"])\n                migrate(str(location), setuptools_options, venv.sys_path)\n        except Exception as e:  # noqa: BLE001\n            app.display_error(f\"Could not automatically migrate from setuptools: {e}\")\n            if name == \"temporary\":\n                name = app.prompt(\"Project name\")\n        else:\n            return\n\n    default_config = {\n        \"description\": \"\",\n        \"dependencies\": set(),\n        \"package_name\": normalized_name.replace(\"-\", \"_\"),\n        \"project_name\": name,\n        \"project_name_normalized\": normalized_name,\n        \"args\": {\"cli\": feature_cli},\n    }\n\n    if interactive:\n        default_config[\"description\"] = app.prompt(\"Description\", default=\"\")\n\n        app.display_info()\n\n    if needs_config_update:\n        app.project.initialize(str(location / \"pyproject.toml\"), default_config)\n        app.display_success(\"Updated: pyproject.toml\")\n        return\n\n    template_config = deepcopy(app.config.template.raw_data)\n    if \"plugins\" in template_config and not template_config[\"plugins\"]:\n        del template_config[\"plugins\"]\n\n    TemplateConfig(template_config, (\"template\",)).parse_fields()\n\n    plugin_config = template_config.pop(\"plugins\")\n\n    # Set up default config for template files\n    template_config.update(default_config)\n\n    template_classes = app.plugins.template.collect()\n\n    templates = []\n    for template_name, template_class in sorted(template_classes.items(), key=lambda item: -item[1].PRIORITY):\n        if template_name in plugin_config:\n            templates.append(\n                template_class(plugin_config.pop(template_name), app.cache_dir, datetime.now(timezone.utc))\n            )\n\n    if not templates:\n        app.abort(f\"None of the defined plugins were found: {', '.join(sorted(plugin_config))}\")\n    elif plugin_config:\n        app.abort(f\"Some of the defined plugins were not found: {', '.join(sorted(plugin_config))}\")\n\n    for template in templates:\n        template.initialize_config(template_config)\n\n    template_files = []\n\n    for template in templates:\n        for possible_template_file in template.get_files(config=deepcopy(template_config)):\n            template_file = (\n                possible_template_file(deepcopy(template_config), template.plugin_config)\n                if possible_template_file.__class__ is not File\n                else possible_template_file\n            )\n            if template_file.path is None or (initialize and str(template_file.path) != \"pyproject.toml\"):\n                continue\n\n            template_files.append(template_file)\n\n    for template in templates:\n        template.finalize_files(config=deepcopy(template_config), files=template_files)\n\n    for template_file in template_files:\n        template_file.write(location)\n\n    if initialize:\n        app.display_success(\"Wrote: pyproject.toml\")\n        return\n\n    from rich.markup import escape\n    from rich.tree import Tree\n\n    def recurse_directory(directory, tree):\n        paths = sorted(Path(directory).iterdir(), key=lambda p: (p.is_file(), p.name))\n        for path in paths:\n            if path.is_dir():\n                recurse_directory(\n                    path, tree.add(f\"[bold magenta][link={app.platform.format_file_uri(path)}]{escape(path.name)}\")\n                )\n            else:\n                tree.add(f\"[green][link={app.platform.format_file_uri(path)}]{escape(path.name)}\")\n\n    root = Tree(\n        f\"[bold magenta][link={app.platform.format_file_uri(location)}]{escape(location.name)}\",\n        guide_style=\"bright_blue\",\n        hide_root=location == Path.cwd(),\n    )\n    recurse_directory(location, root)\n    app.output(root, markup=True)\n"
  },
  {
    "path": "src/hatch/cli/new/migrate.py",
    "content": "import os\nimport sys\n\nFILE = os.path.abspath(__file__)\nHERE = os.path.dirname(FILE)\nENV_VAR_PREFIX = \"_HATCHLING_PORT_ADD_\"\n\n\ndef _apply_env_vars(kwargs):\n    from ast import literal_eval\n\n    for key, value in os.environ.items():\n        if key.startswith(ENV_VAR_PREFIX):\n            kwargs[key.replace(ENV_VAR_PREFIX, \"\", 1).lower()] = literal_eval(value)\n\n\ndef _parse_dependencies(dependency_definition):\n    dependencies = []\n    for line in dependency_definition.splitlines():\n        dependency = line.split(\" #\", 1)[0].strip()\n        if dependency:\n            dependencies.append(dependency)\n\n    return dependencies\n\n\ndef _collapse_data(output, data):\n    import tomli_w\n\n    expected_output = new_output = tomli_w.dumps(data)\n    new_output = new_output.replace(\"    \", \"\")\n    new_output = new_output.replace(\"[\\n\", \"[\")\n    new_output = new_output.replace('\",\\n]', '\"]')\n\n    return output.replace(expected_output, new_output, 1)\n\n\ndef _parse_setup_cfg(kwargs):\n    from configparser import ConfigParser\n\n    setup_cfg_file = os.path.join(HERE, \"setup.cfg\")\n    if not os.path.isfile(setup_cfg_file):\n        return\n\n    setup_cfg = ConfigParser()\n    setup_cfg.read(setup_cfg_file, encoding=\"utf-8\")\n\n    if not setup_cfg.has_section(\"metadata\"):\n        return\n\n    metadata = setup_cfg[\"metadata\"]\n\n    if \"name\" in metadata and \"name\" not in kwargs:\n        kwargs[\"name\"] = metadata[\"name\"]\n\n    if \"description\" in metadata and \"description\" not in kwargs:\n        kwargs[\"description\"] = metadata[\"description\"]\n\n    if \"license\" in metadata and \"license\" not in kwargs:\n        kwargs[\"license\"] = metadata[\"license\"]\n\n    if \"author\" in metadata and \"author\" not in kwargs:\n        kwargs[\"author\"] = metadata[\"author\"]\n\n    if \"author_email\" in metadata and \"author_email\" not in kwargs:\n        kwargs[\"author_email\"] = metadata[\"author_email\"]\n\n    if \"keywords\" in metadata and \"keywords\" not in kwargs:\n        keywords = metadata[\"keywords\"].strip().splitlines()\n        kwargs[\"keywords\"] = keywords if len(keywords) > 1 else keywords[0]\n\n    if \"classifiers\" in metadata and \"classifiers\" not in kwargs:\n        kwargs[\"classifiers\"] = metadata[\"classifiers\"].strip().splitlines()\n\n    if \"url\" in metadata and \"url\" not in kwargs:\n        kwargs[\"url\"] = metadata[\"url\"]\n\n    if \"download_url\" in metadata and \"download_url\" not in kwargs:\n        kwargs[\"download_url\"] = metadata[\"download_url\"]\n\n    if \"project_urls\" in metadata and \"project_urls\" not in kwargs:\n        kwargs[\"project_urls\"] = dict(\n            url.replace(\" = \", \"=\", 1).split(\"=\") for url in metadata[\"project_urls\"].strip().splitlines()\n        )\n\n    if setup_cfg.has_section(\"options\"):\n        options = setup_cfg[\"options\"]\n\n        if \"python_requires\" in options and \"python_requires\" not in kwargs:\n            kwargs[\"python_requires\"] = options[\"python_requires\"]\n\n        if \"install_requires\" in options and \"install_requires\" not in kwargs:\n            kwargs[\"install_requires\"] = _parse_dependencies(options[\"install_requires\"])\n\n        if \"packages\" in options and \"packages\" not in kwargs:\n            packages = []\n            for package_spec in options[\"packages\"].strip().splitlines():\n                package = package_spec.replace(\"find:\", \"\", 1).replace(\"find_namespace:\", \"\", 1).strip()\n                if package:\n                    packages.append(package)\n\n            if packages:\n                kwargs[\"packages\"] = packages\n\n        if \"package_dir\" in options and \"package_dir\" not in kwargs:\n            kwargs[\"package_dir\"] = dict(\n                d.replace(\" = \", \"=\", 1).split(\"=\") for d in options[\"package_dir\"].strip().splitlines()\n            )\n\n    if setup_cfg.has_section(\"options.extras_require\") and \"extras_require\" not in kwargs:\n        kwargs[\"extras_require\"] = {\n            feature: _parse_dependencies(dependencies)\n            for feature, dependencies in setup_cfg[\"options.extras_require\"].items()\n        }\n\n    if setup_cfg.has_section(\"options.entry_points\") and \"entry_points\" not in kwargs:\n        kwargs[\"entry_points\"] = {\n            entry_point: definitions.strip().splitlines()\n            for entry_point, definitions in setup_cfg[\"options.entry_points\"].items()\n        }\n\n\ndef setup(**kwargs):\n    import itertools\n    import re\n\n    import tomli_w\n\n    project_metadata = {}\n    hatch_metadata = {}\n    tool_metadata = {\"hatch\": hatch_metadata}\n    project_data = {\n        \"build-system\": {\"requires\": [\"hatchling\"], \"build-backend\": \"hatchling.build\"},\n        \"project\": project_metadata,\n        \"tool\": tool_metadata,\n    }\n\n    _parse_setup_cfg(kwargs)\n    _apply_env_vars(kwargs)\n\n    name = kwargs[\"name\"]\n    project_name = name.replace(\"_\", \"-\")\n    packages = sorted(kwargs.get(\"packages\") or [name.replace(\"-\", \"_\")])\n    package_name = package_path = package_source = packages[0].split(\".\")[0].lower().strip()\n\n    project_metadata[\"name\"] = project_name\n\n    project_metadata[\"dynamic\"] = [\"version\"]\n\n    if \"description\" in kwargs:\n        project_metadata[\"description\"] = kwargs[\"description\"]\n\n    for readme_file in (\"README.md\", \"README.rst\", \"README.txt\"):\n        if os.path.isfile(os.path.join(HERE, readme_file)):\n            project_metadata[\"readme\"] = readme_file\n            break\n\n    project_metadata[\"license\"] = kwargs.get(\"license\", \"\")\n\n    if \"python_requires\" in kwargs:\n        project_metadata[\"requires-python\"] = kwargs[\"python_requires\"]\n\n    for collaborator in (\"author\", \"maintainer\"):\n        collaborators = []\n        collaborator_names = []\n        collaborator_emails = []\n        if collaborator in kwargs:\n            collaborator_names.extend(\n                collaborator_name.strip() for collaborator_name in kwargs[collaborator].split(\",\")\n            )\n        if f\"{collaborator}_email\" in kwargs:\n            collaborator_emails.extend(\n                collaborator_email.strip() for collaborator_email in kwargs[f\"{collaborator}_email\"].split(\",\")\n            )\n\n        for collaborator_name, collaborator_email in itertools.zip_longest(collaborator_names, collaborator_emails):\n            data = {}\n            if collaborator_name is not None:\n                data[\"name\"] = collaborator_name\n            if collaborator_email is not None:\n                data[\"email\"] = collaborator_email\n            if data:\n                collaborators.append(data)\n\n        if collaborators:\n            project_metadata[f\"{collaborator}s\"] = collaborators\n\n    if \"keywords\" in kwargs:\n        keywords = kwargs[\"keywords\"]\n        if isinstance(keywords, str):\n            keywords = keywords.replace(\",\", \" \").split()\n        project_metadata[\"keywords\"] = sorted(keywords)\n\n    if \"classifiers\" in kwargs:\n        project_metadata[\"classifiers\"] = sorted(kwargs[\"classifiers\"])\n        fixed_indices = []\n        final_index = 0\n        for i, classifier in enumerate(project_metadata[\"classifiers\"]):\n            if classifier.startswith(\"Programming Language :: Python :: \"):\n                final_index = i\n                for python_version in (\"3.10\", \"3.11\", \"3.12\"):\n                    if classifier.endswith(python_version):\n                        fixed_indices.append(i)\n                        break\n        for i, index in enumerate(fixed_indices):\n            project_metadata[\"classifiers\"].insert(final_index, project_metadata[\"classifiers\"].pop(index - i))\n\n    if \"install_requires\" in kwargs:\n        project_metadata[\"dependencies\"] = sorted(\n            [entry.strip() for entry in kwargs[\"install_requires\"]]\n            if isinstance(kwargs[\"install_requires\"], (list, tuple))\n            else _parse_dependencies(kwargs[\"install_requires\"]),\n            key=lambda d: d.lower(),\n        )\n\n    if \"extras_require\" in kwargs:\n        project_metadata[\"optional-dependencies\"] = {\n            group: sorted(\n                [entry.strip() for entry in dependencies]\n                if isinstance(dependencies, (list, tuple))\n                else _parse_dependencies(dependencies),\n                key=lambda d: d.lower(),\n            )\n            for group, dependencies in sorted(kwargs[\"extras_require\"].items())\n        }\n\n    if \"entry_points\" in kwargs and isinstance(kwargs[\"entry_points\"], dict):\n        entry_points = {}\n        for entry_point, raw_definitions in kwargs[\"entry_points\"].items():\n            definitions = [raw_definitions] if isinstance(raw_definitions, str) else raw_definitions\n            definitions = dict(sorted(d.replace(\" \", \"\").split(\"=\", 1) for d in definitions))\n\n            if entry_point == \"console_scripts\":\n                project_metadata[\"scripts\"] = definitions\n            elif entry_point == \"gui_scripts\":\n                project_metadata[\"gui-scripts\"] = definitions\n            else:\n                entry_points[entry_point] = definitions\n        if entry_points:\n            project_metadata[\"entry-points\"] = dict(sorted(entry_points.items()))\n\n    urls = {}\n    if \"url\" in kwargs:\n        urls[\"Homepage\"] = kwargs[\"url\"]\n    if \"download_url\" in kwargs:\n        urls[\"Download\"] = kwargs[\"download_url\"]\n    if \"project_urls\" in kwargs:\n        urls.update(kwargs[\"project_urls\"])\n    if urls:\n        project_metadata[\"urls\"] = dict(sorted(urls.items()))\n\n    build_targets = {}\n    build_data = {}\n\n    if \"use_scm_version\" in kwargs:\n        project_data[\"build-system\"][\"requires\"].append(\"hatch-vcs\")\n        hatch_metadata[\"version\"] = {\"source\": \"vcs\"}\n        build_data[\"hooks\"] = {\"vcs\": {\"version-file\": f\"{package_path}/_version.py\"}}\n    else:\n        hatch_metadata[\"version\"] = {\"path\": f\"{package_path}/__init__.py\"}\n\n    build_data[\"targets\"] = build_targets\n\n    if \"\" in kwargs.get(\"package_dir\", {}):\n        package_source = kwargs[\"package_dir\"][\"\"]\n\n        package = (kwargs.get(\"packages\") or [package_name])[0]\n        package_path = f\"{package_source}/{package}\"\n\n        if package_path != f\"src/{package_name}\":\n            build_targets.setdefault(\"wheel\", {})[\"packages\"] = [package_path]\n\n    if kwargs.get(\"data_files\", []):\n        shared_data = {}\n        for shared_directory, relative_paths in kwargs[\"data_files\"]:\n            relative_files = {}\n            for relative_path in relative_paths:\n                relative_directory, filename = os.path.split(relative_path)\n                relative_files.setdefault(relative_directory, []).append(filename)\n\n            for relative_directory, files in sorted(relative_files.items()):\n                if not os.path.isdir(relative_directory) or set(os.listdir(relative_directory)) != set(files):\n                    for filename in sorted(files):\n                        local_path = os.path.join(relative_directory, filename).replace(\"\\\\\", \"/\")\n                        shared_data[local_path] = f\"{shared_directory}/{filename}\"\n                else:\n                    shared_data[relative_directory] = shared_directory\n\n        build_targets.setdefault(\"wheel\", {})[\"shared-data\"] = shared_data\n\n    build_targets[\"sdist\"] = {\n        \"include\": [\n            f\"/{package_source}\",\n        ]\n    }\n\n    hatch_metadata[\"build\"] = build_data\n\n    output = tomli_w.dumps(project_data)\n    output = _collapse_data(output, {\"requires\": project_data[\"build-system\"][\"requires\"]})\n    output = _collapse_data(output, {\"dynamic\": project_data[\"project\"][\"dynamic\"]})\n\n    project_file = os.path.join(HERE, \"pyproject.toml\")\n    if os.path.isfile(project_file):\n        with open(project_file, encoding=\"utf-8\") as f:\n            current_contents = f.read()\n\n        for section in (\"build-system\", \"project\"):\n            for pattern in (rf\"^\\[{section}].*?(?=^\\[)\", rf\"^\\[{section}].*\"):\n                current_contents = re.sub(pattern, \"\", current_contents, flags=re.MULTILINE | re.DOTALL)\n\n        output += f\"\\n{current_contents}\"\n\n    with open(project_file, \"w\", encoding=\"utf-8\") as f:\n        f.write(output)\n\n\nif __name__ == \"setuptools\":\n    __this_shim = sys.modules.pop(\"setuptools\")\n    __current_directory = sys.path.pop(0)\n\n    import setuptools as __real_setuptools\n\n    sys.path.insert(0, __current_directory)\n    sys.modules[\"setuptools\"] = __this_shim\n\n    def __getattr__(name):\n        return getattr(__real_setuptools, name)\n\n    del __this_shim\n    del __current_directory\n\n\ndef migrate(root, setuptools_options, sys_paths):\n    import shutil\n    import subprocess\n    from tempfile import TemporaryDirectory\n\n    with TemporaryDirectory() as temp_dir:\n        repo_dir = os.path.join(os.path.realpath(temp_dir), \"repo\")\n        shutil.copytree(root, repo_dir, ignore=shutil.ignore_patterns(\".git\", \".tox\"), copy_function=shutil.copy)\n        shutil.copy(FILE, os.path.join(repo_dir, \"setuptools.py\"))\n        setup_py = os.path.join(repo_dir, \"setup.py\")\n\n        if not os.path.isfile(setup_py):\n            # Synthesize a small setup.py file since there is none\n            with open(setup_py, \"w\", encoding=\"utf-8\") as f:\n                f.write(\"from setuptools import setup\\nsetup()\\n\")\n\n        env = dict(os.environ)\n        for arg in setuptools_options:\n            key, value = arg.split(\"=\", 1)\n            env[f\"{ENV_VAR_PREFIX}{key}\"] = value\n\n        # When PYTHONSAFEPATH is non-empty, the current directory is not added automatically\n        python_paths = [repo_dir]\n        python_paths.extend(p for p in sys_paths if p)\n        if python_path := env.get(\"PYTHONPATH\", \"\"):\n            python_paths.append(python_path)\n\n        env[\"PYTHONPATH\"] = os.pathsep.join(python_paths)\n\n        subprocess.run([sys.executable, setup_py], env=env, cwd=repo_dir, check=True)\n\n        old_project_file = os.path.join(root, \"pyproject.toml\")\n        new_project_file = os.path.join(repo_dir, \"pyproject.toml\")\n        shutil.copyfile(new_project_file, old_project_file)\n"
  },
  {
    "path": "src/hatch/cli/project/__init__.py",
    "content": "import click\n\nfrom hatch.cli.project.metadata import metadata\n\n\n@click.group(short_help=\"View project information\")\ndef project():\n    pass\n\n\nproject.add_command(metadata)\n"
  },
  {
    "path": "src/hatch/cli/project/metadata.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Display project metadata\")\n@click.argument(\"field\", required=False)\n@click.pass_obj\ndef metadata(app: Application, field: str | None):\n    \"\"\"\n    Display project metadata.\n\n    If you want to view the raw readme file without rendering, you can use a JSON parser\n    like [jq](https://github.com/stedolan/jq):\n\n    \\b\n    ```\n    hatch project metadata | jq -r .readme\n    ```\n    \"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    import json\n\n    from hatch.project.constants import BUILD_BACKEND\n\n    app.project.prepare_build_environment()\n    build_backend = app.project.metadata.build.build_backend\n    with app.project.location.as_cwd(), app.project.build_env.get_env_vars():\n        if build_backend != BUILD_BACKEND:\n            project_metadata = app.project.build_frontend.get_core_metadata()\n        else:\n            project_metadata = app.project.build_frontend.hatch.get_core_metadata()\n\n    if field:\n        if field not in project_metadata:\n            app.abort(f\"Unknown metadata field: {field}\")\n        elif field == \"readme\":\n            if project_metadata[field][\"content-type\"] == \"text/markdown\":  # no cov\n                app.display_markdown(project_metadata[field][\"text\"])\n            else:\n                app.display(project_metadata[field][\"text\"])\n        elif isinstance(project_metadata[field], str):\n            app.display(project_metadata[field])\n        else:\n            app.display(json.dumps(project_metadata[field], indent=4))\n    else:\n        for key, value in list(project_metadata.items()):\n            if not value:\n                project_metadata.pop(key)\n\n        app.display(json.dumps(project_metadata, indent=4))\n"
  },
  {
    "path": "src/hatch/cli/publish/__init__.py",
    "content": "import click\n\nfrom hatch.config.constants import PublishEnvVars\n\n\n@click.command(short_help=\"Publish build artifacts\")\n@click.argument(\"artifacts\", nargs=-1)\n@click.option(\n    \"--repo\",\n    \"-r\",\n    envvar=PublishEnvVars.REPO,\n    help=\"The repository with which to publish artifacts [env var: `HATCH_INDEX_REPO`]\",\n)\n@click.option(\n    \"--user\", \"-u\", envvar=PublishEnvVars.USER, help=\"The user with which to authenticate [env var: `HATCH_INDEX_USER`]\"\n)\n@click.option(\n    \"--auth\",\n    \"-a\",\n    envvar=PublishEnvVars.AUTH,\n    help=\"The credentials to use for authentication [env var: `HATCH_INDEX_AUTH`]\",\n)\n@click.option(\n    \"--ca-cert\",\n    envvar=PublishEnvVars.CA_CERT,\n    help=\"The path to a CA bundle [env var: `HATCH_INDEX_CA_CERT`]\",\n)\n@click.option(\n    \"--client-cert\",\n    envvar=PublishEnvVars.CLIENT_CERT,\n    help=\"The path to a client certificate, optionally containing the private key [env var: `HATCH_INDEX_CLIENT_CERT`]\",\n)\n@click.option(\n    \"--client-key\",\n    envvar=PublishEnvVars.CLIENT_KEY,\n    help=\"The path to the client certificate's private key [env var: `HATCH_INDEX_CLIENT_KEY`]\",\n)\n@click.option(\"--no-prompt\", \"-n\", is_flag=True, help=\"Disable prompts, such as for missing required fields\")\n@click.option(\n    \"--initialize-auth\", is_flag=True, help=\"Save first-time authentication information even if nothing was published\"\n)\n@click.option(\n    \"--publisher\",\n    \"-p\",\n    \"publisher_name\",\n    envvar=PublishEnvVars.PUBLISHER,\n    default=\"index\",\n    help=\"The publisher plugin to use (default is `index`) [env var: `HATCH_PUBLISHER`]\",\n)\n@click.option(\n    \"--option\",\n    \"-o\",\n    \"options\",\n    envvar=PublishEnvVars.OPTIONS,\n    multiple=True,\n    help=(\n        \"Options to pass to the publisher plugin. This may be selected multiple \"\n        \"times e.g. `-o foo=bar -o baz=23` [env var: `HATCH_PUBLISHER_OPTIONS`]\"\n    ),\n)\n@click.option(\"--yes\", \"-y\", is_flag=True, help=\"Confirm without prompting when the plugin is disabled\")\n@click.pass_obj\ndef publish(\n    app,\n    artifacts,\n    repo,\n    user,\n    auth,\n    ca_cert,\n    client_cert,\n    client_key,\n    no_prompt,\n    initialize_auth,\n    publisher_name,\n    options,\n    yes,\n):\n    \"\"\"Publish build artifacts.\"\"\"\n    option_map = {\"no_prompt\": no_prompt, \"initialize_auth\": initialize_auth}\n    if publisher_name == \"index\":\n        if options:\n            app.abort(\"Use the standard CLI flags rather than passing explicit options when using the `index` plugin\")\n\n        if repo:\n            option_map[\"repo\"] = repo\n        if user:\n            option_map[\"user\"] = user\n        if auth:\n            option_map[\"auth\"] = auth\n        if ca_cert:\n            option_map[\"ca_cert\"] = ca_cert\n        if client_cert:\n            option_map[\"client_cert\"] = client_cert\n        if client_key:\n            option_map[\"client_key\"] = client_key\n    else:  # no cov\n        for option in options:\n            key, _, value = option.partition(\"=\")\n            option_map[key] = value\n\n    publisher_class = app.plugins.publisher.get(publisher_name)\n    if publisher_class is None:\n        app.abort(f\"Unknown publisher: {publisher_name}\")\n\n    publisher = publisher_class(\n        app,\n        app.project.location,\n        app.cache_dir / \"publish\" / publisher_name,\n        app.project.config.publish.get(publisher_name, {}),\n        app.config.publish.get(publisher_name, {}),\n    )\n    if publisher.disable and not (yes or (not no_prompt and app.confirm(f\"Confirm `{publisher_name}` publishing\"))):\n        app.abort(f\"Publisher is disabled: {publisher_name}\")\n\n    publisher.publish(list(artifacts), option_map)\n"
  },
  {
    "path": "src/hatch/cli/python/__init__.py",
    "content": "import click\n\nfrom hatch.cli.python.find import find\nfrom hatch.cli.python.install import install\nfrom hatch.cli.python.remove import remove\nfrom hatch.cli.python.show import show\nfrom hatch.cli.python.update import update\n\n\n@click.group(short_help=\"Manage Python installations\")\ndef python():\n    pass\n\n\npython.add_command(find)\npython.add_command(install)\npython.add_command(remove)\npython.add_command(show)\npython.add_command(update)\n"
  },
  {
    "path": "src/hatch/cli/python/find.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Locate Python binaries\")\n@click.argument(\"name\")\n@click.option(\"-p\", \"--parent\", is_flag=True, help=\"Show the parent directory of the Python binary\")\n@click.option(\"--dir\", \"-d\", \"directory\", help=\"The directory in which distributions reside\")\n@click.pass_obj\ndef find(app: Application, *, name: str, parent: bool, directory: str | None):\n    \"\"\"Locate Python binaries.\"\"\"\n    manager = app.get_python_manager(directory)\n    installed = manager.get_installed()\n    if name not in installed:\n        app.abort(f\"Distribution not installed: {name}\")\n\n    dist = installed[name]\n    app.display(str(dist.python_path.parent if parent else dist.python_path))\n"
  },
  {
    "path": "src/hatch/cli/python/install.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\ndef ensure_path_public(path: str, shells: list[str]) -> bool:\n    import userpath\n\n    if userpath.in_current_path(path) or userpath.in_new_path(path, shells):\n        return True\n\n    userpath.append(path, shells=shells)\n    return False\n\n\n@click.command(short_help=\"Install Python distributions\")\n@click.argument(\"names\", required=True, nargs=-1)\n@click.option(\"--private\", is_flag=True, help=\"Do not add distributions to the user PATH\")\n@click.option(\"--update\", \"-u\", is_flag=True, help=\"Update existing installations\")\n@click.option(\n    \"--dir\", \"-d\", \"directory\", help=\"The directory in which to install distributions, overriding configuration\"\n)\n@click.pass_obj\ndef install(app: Application, *, names: tuple[str, ...], private: bool, update: bool, directory: str | None):\n    \"\"\"\n    Install Python distributions.\n\n    You may select `all` to install all compatible distributions:\n\n    \\b\n    ```\n    hatch python install all\n    ```\n\n    You can set custom sources for distributions by setting the `HATCH_PYTHON_SOURCE_<NAME>` environment variable\n    where `<NAME>` is the uppercased version of the distribution name with periods replaced by underscores e.g.\n    `HATCH_PYTHON_SOURCE_PYPY3_10`.\n    \"\"\"\n    from hatch.errors import PythonDistributionResolutionError, PythonDistributionUnknownError\n    from hatch.python.distributions import ORDERED_DISTRIBUTIONS\n    from hatch.python.resolve import get_distribution\n\n    shells = []\n    if not private and not app.platform.windows:\n        shell_name, _ = app.shell_data\n        shells.append(shell_name)\n\n    manager = app.get_python_manager(directory)\n    installed = manager.get_installed()\n    selection = ORDERED_DISTRIBUTIONS if \"all\" in names else names\n    unknown = []\n    compatible = []\n    incompatible = []\n    for name in selection:\n        if name in installed:\n            compatible.append(name)\n            continue\n\n        try:\n            get_distribution(name)\n        except PythonDistributionUnknownError:\n            unknown.append(name)\n        except PythonDistributionResolutionError:\n            incompatible.append(name)\n        else:\n            compatible.append(name)\n\n    if unknown:\n        app.abort(f\"Unknown distributions: {', '.join(unknown)}\")\n    elif incompatible and (not compatible or \"all\" not in names):\n        app.abort(f\"Incompatible distributions: {', '.join(incompatible)}\")\n\n    directories_made_public = []\n    for name in compatible:\n        needs_update = False\n        if name in installed:\n            installed_dist = installed[name]\n            needs_update = installed_dist.needs_update()\n            if not needs_update:\n                app.display_warning(f\"The latest version is already installed: {installed_dist.version}\")\n                continue\n\n            if not (update or app.confirm(f\"Update {name}?\")):\n                app.abort(f\"Distribution is already installed: {name}\")\n\n        with app.status(f\"{'Updating' if needs_update else 'Installing'} {name}\"):\n            dist = manager.install(name)\n            if not private:\n                python_directory = str(dist.python_path.parent)\n                if not ensure_path_public(python_directory, shells=shells):\n                    directories_made_public.append(python_directory)\n\n        app.display_success(f\"{'Updated' if needs_update else 'Installed'} {name} @ {dist.path}\")\n\n    if directories_made_public:\n        multiple = len(directories_made_public) > 1\n        app.display(\n            f\"\\nThe following director{'ies' if multiple else 'y'} ha{'ve' if multiple else 's'} \"\n            f\"been added to your PATH (pending a shell restart):\\n\"\n        )\n        for public_directory in directories_made_public:\n            app.display(public_directory)\n"
  },
  {
    "path": "src/hatch/cli/python/remove.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Remove Python distributions\")\n@click.argument(\"names\", required=True, nargs=-1)\n@click.option(\"--dir\", \"-d\", \"directory\", help=\"The directory in which distributions reside\")\n@click.pass_obj\ndef remove(app: Application, *, names: tuple[str, ...], directory: str | None):\n    \"\"\"\n    Remove Python distributions.\n\n    You may select `all` to remove all installed distributions:\n\n    \\b\n    ```\n    hatch python remove all\n    ```\n    \"\"\"\n    manager = app.get_python_manager(directory)\n    installed = manager.get_installed()\n    selection = tuple(installed) if \"all\" in names else names\n    for name in selection:\n        if name not in installed:\n            app.display_warning(f\"Distribution is not installed: {name}\")\n            continue\n\n        dist = installed[name]\n        with app.status(f\"Removing {name}\"):\n            manager.remove(dist)\n"
  },
  {
    "path": "src/hatch/cli/python/show.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Show the available Python distributions\")\n@click.option(\"--ascii\", \"force_ascii\", is_flag=True, help=\"Whether or not to only use ASCII characters\")\n@click.option(\"--dir\", \"-d\", \"directory\", help=\"The directory in which distributions reside\")\n@click.pass_obj\ndef show(app: Application, *, force_ascii: bool, directory: str | None):\n    \"\"\"Show the available Python distributions.\"\"\"\n    from hatch.python.resolve import get_compatible_distributions\n\n    manager = app.get_python_manager(directory)\n    installed = manager.get_installed()\n\n    installed_columns: dict[str, dict[int, str]] = {\"Name\": {}, \"Version\": {}, \"Status\": {}}\n    for i, (name, installed_dist) in enumerate(installed.items()):\n        installed_columns[\"Name\"][i] = name\n        installed_columns[\"Version\"][i] = installed_dist.version\n        if installed_dist.needs_update():\n            installed_columns[\"Status\"][i] = \"Update available\"\n\n    available_columns: dict[str, dict[int, str]] = {\"Name\": {}, \"Version\": {}}\n    for i, (name, dist) in enumerate(get_compatible_distributions().items()):\n        if name in installed:\n            continue\n\n        available_columns[\"Name\"][i] = name\n        available_columns[\"Version\"][i] = dist.version.base_version\n\n    app.display_table(\"Installed\", installed_columns, show_lines=True, force_ascii=force_ascii)\n    app.display_table(\"Available\", available_columns, show_lines=True, force_ascii=force_ascii)\n"
  },
  {
    "path": "src/hatch/cli/python/update.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nfrom hatch.cli.python.install import install\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Update Python distributions\")\n@click.argument(\"names\", required=True, nargs=-1)\n@click.option(\"--dir\", \"-d\", \"directory\", help=\"The directory in which distributions reside\")\n@click.pass_context\ndef update(ctx: click.Context, *, names: tuple[str, ...], directory: str | None):\n    \"\"\"\n    Update Python distributions.\n\n    You may select `all` to update all installed distributions:\n\n    \\b\n    ```\n    hatch python update all\n    ```\n    \"\"\"\n    app: Application = ctx.obj\n\n    manager = app.get_python_manager(directory)\n    installed = manager.get_installed()\n    selection = tuple(installed) if \"all\" in names else names\n\n    not_installed = [name for name in selection if name not in installed]\n    if not_installed:\n        app.abort(f\"Distributions not installed: {', '.join(not_installed)}\")\n\n    ctx.invoke(install, names=selection, directory=directory, private=True, update=True)\n"
  },
  {
    "path": "src/hatch/cli/run/__init__.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(\n    short_help=\"Run commands within project environments\",\n    context_settings={\"help_option_names\": [], \"ignore_unknown_options\": True},\n)\n@click.argument(\"args\", metavar=\"[ENV:]ARGS...\", required=True, nargs=-1)\n@click.pass_context\ndef run(ctx: click.Context, args: tuple[str, ...]):\n    \"\"\"\n    Run commands within project environments.\n    This is a convenience wrapper around the [`env run`](#hatch-env-run) command.\n\n    If the first argument contains a colon, then the preceding component will be\n    interpreted as the name of the environment to target, overriding the `-e`/`--env`\n    [root option](#hatch) and the `HATCH_ENV` environment variable.\n\n    If the environment provides matrices, then you may also provide leading arguments\n    starting with a `+` or `-` to select or exclude certain variables, optionally\n    followed by specific comma-separated values. For example, if you have the\n    following configuration:\n\n    \\b\n    ```toml tab=\"pyproject.toml\"\n    [[tool.hatch.envs.test.matrix]]\n    python = [\"3.9\", \"3.10\"]\n    version = [\"42\", \"3.14\", \"9000\"]\n    ```\n\n    ```toml tab=\"hatch.toml\"\n    [[envs.test.matrix]]\n    python = [\"3.9\", \"3.10\"]\n    version = [\"42\", \"3.14\", \"9000\"]\n    ```\n\n    then running:\n\n    \\b\n    ```\n    hatch run +py=3.10 -version=9000 test:pytest\n    ```\n\n    would execute `pytest` in the environments `test.py3.10-42` and `test.py3.10-3.14`.\n    Note that `py` may be used as an alias for `python`.\n\n    \\b\n    !!! note\n        Inclusions are treated as an intersection while exclusions are treated as a union i.e.\n        an environment must match all of the included variables to be selected while matching\n        any of the excluded variables will prevent selection.\n    \"\"\"\n    app: Application = ctx.obj\n\n    first_arg = args[0]\n    if first_arg in {\"-h\", \"--help\"}:\n        app.display_info(ctx.get_help())\n        return\n\n    from hatch.utils.fs import Path\n\n    if first_arg.endswith(\".py\") and (script := Path(first_arg)).is_file():\n        from hatch.project.utils import parse_inline_script_metadata\n\n        # Ensure consistent IDs for storage\n        script = script.resolve()\n\n        try:\n            metadata = parse_inline_script_metadata(script.read_text())\n        except ValueError as e:\n            app.abort(f\"{e}, {first_arg}\")\n\n        # Ignore scripts that don't define metadata blocks or define empty metadata blocks\n        if metadata:\n            from hatch.env.utils import ensure_valid_environment\n\n            config = metadata.get(\"tool\", {}).get(\"hatch\", {})\n            config[\"skip-install\"] = True\n            config.setdefault(\"installer\", \"uv\")\n            config.setdefault(\"dependencies\", [])\n            config[\"dependencies\"].extend(metadata.get(\"dependencies\", []))\n\n            if \"python\" not in config and (requires_python := metadata.get(\"requires-python\")) is not None:\n                import re\n                import sys\n                import sysconfig\n\n                from packaging.specifiers import SpecifierSet\n\n                from hatch.python.distributions import DISTRIBUTIONS\n\n                current_version = \".\".join(map(str, sys.version_info[:2]))\n                if bool(sysconfig.get_config_var(\"Py_GIL_DISABLED\")):\n                    current_version += \"t\"\n\n                # Strip \"t\" suffix for distribution lookup since DISTRIBUTIONS keys don't include it\n                current_version_base = current_version.rstrip(\"t\")\n                distributions = [name for name in DISTRIBUTIONS if re.match(r\"^\\d+\\.\\d+$\", name)]\n                distributions.sort(key=lambda name: name != current_version_base)\n\n                python_constraint = SpecifierSet(requires_python)\n                for distribution in distributions:\n                    # Try an artificially high patch version to account for\n                    # common cases like `>=3.11.4` or `>=3.10,<3.11`\n                    if python_constraint.contains(f\"{distribution}.100\"):\n                        # Only set config[\"python\"] if it doesn't match the current Python's base version\n                        # This allows free-threaded builds (e.g. 3.14t) to match their base version (3.14)\n                        if distribution != current_version_base:\n                            config[\"python\"] = distribution\n                        break\n                else:\n                    app.abort(f\"Unable to satisfy Python version constraint: {requires_python}\")\n\n            ensure_valid_environment(config)\n            app.project.config.envs[script.id] = config\n            app.project.set_path(script)\n            for context in app.runner_context([script.id]):\n                context.add_shell_command([\"python\", first_arg, *args[1:]])\n\n            return\n\n    from hatch.cli.env.run import run as run_command\n\n    command_start = 0\n    included_variables = []\n    excluded_variables = []\n    for i, arg in enumerate(args):\n        command_start = i\n        if arg.startswith(\"+\"):\n            included_variables.append(arg[1:])\n        elif arg.startswith(\"-\"):\n            excluded_variables.append(arg[1:])\n        else:\n            break\n    else:\n        command_start += 1\n\n    args = args[command_start:]\n    if not args:\n        app.abort(\"Missing argument `MATRIX:ARGS...`\")\n\n    command, *final_args = args\n    env_name, separator, command = command.rpartition(\":\")\n    if not separator:\n        env_name = app.env\n    elif not env_name:\n        env_name = \"system\"\n\n    ctx.invoke(\n        run_command,\n        args=[command, *final_args],\n        env_names=[env_name],\n        included_variable_specs=included_variables,\n        excluded_variable_specs=excluded_variables,\n    )\n"
  },
  {
    "path": "src/hatch/cli/self/__init__.py",
    "content": "import os\n\nimport click\n\nfrom hatch.cli.self.report import report\n\n__management_command = os.environ.get(\"PYAPP_COMMAND_NAME\", \"self\")\n\n\n@click.group(name=__management_command, short_help=\"Manage Hatch\")\ndef self_command():\n    pass\n\n\nself_command.add_command(report)\n\nif __management_command:\n    from hatch.cli.self.restore import restore\n    from hatch.cli.self.update import update\n\n    self_command.add_command(restore)\n    self_command.add_command(update)\n"
  },
  {
    "path": "src/hatch/cli/self/report.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\ndef get_install_source(platform_name: str) -> str:\n    import os\n    import sys\n    import sysconfig\n\n    from platformdirs import user_data_dir\n\n    from hatch.utils.fs import Path\n\n    default_source = \"pip\"\n\n    python_path = Path(sys.executable).resolve()\n    parent_paths = python_path.parents\n\n    # https://github.com/ofek/pyapp/blob/v0.13.0/src/app.rs#L27\n    if Path(user_data_dir(\"pyapp\", appauthor=False)) in parent_paths:\n        return \"binary\"\n\n    # https://pypa.github.io/pipx/how-pipx-works/\n    if (Path.home() / \".local\" / \"pipx\" / \"venvs\") in parent_paths:\n        return \"pipx\"\n\n    # https://packaging.python.org/en/latest/specifications/externally-managed-environments/#marking-an-interpreter-as-using-an-external-package-manager\n    try:\n        stdlib_path_config = sysconfig.get_path(\"stdlib\")\n    # https://docs.python.org/3/library/sysconfig.html#sysconfig.get_path\n    except KeyError:\n        stdlib_path_config = \"\"\n\n    if (\n        # This does not work on NixOS, see: https://github.com/NixOS/nixpkgs/issues/201037\n        sys.prefix == sys.base_prefix\n        and stdlib_path_config\n        and (stdlib_path := Path(stdlib_path_config)).is_dir()\n        and any(p.name == \"EXTERNALLY-MANAGED\" and p.is_file() for p in stdlib_path.iterdir())\n    ):\n        return \"system\"\n\n    if platform_name == \"windows\":\n        if sys.executable.endswith(\"WindowsApps\\\\python.exe\"):\n            return \"Windows Store\"\n\n        # Break early because nothing after is applicable\n        return default_source\n\n    if platform_name == \"macos\" and Path(\"/usr/local/Cellar\") in parent_paths:  # no cov\n        return \"Homebrew\"\n\n    # https://github.com/pyenv/pyenv/tree/v2.3.35#set-up-your-shell-environment-for-pyenv\n    if Path(os.environ.get(\"PYENV_ROOT\", \"~/.pyenv\")).expand() in parent_paths:\n        return \"Pyenv\"\n\n    return default_source\n\n\n@click.command(short_help=\"Generate a pre-populated GitHub issue\")\n@click.option(\"--no-open\", \"-n\", is_flag=True, help=\"Show the URL instead of opening it\")\n@click.pass_obj\ndef report(app: Application, *, no_open: bool) -> None:\n    \"\"\"Generate a pre-populated GitHub issue.\"\"\"\n    import sys\n    import webbrowser\n    from textwrap import indent\n    from urllib.parse import quote_plus\n\n    import tomlkit\n\n    from hatch._version import __version__\n    from hatch.utils.toml import load_toml_data\n\n    # Retain the config that would be most useful\n    full_config = load_toml_data(app.config_file.read_scrubbed())\n    relevant_config = {}\n    for setting in (\"mode\", \"shell\"):\n        if setting in full_config:\n            relevant_config[setting] = full_config[setting]\n\n    if env_dirs := relevant_config.get(\"dirs\", {}).get(\"envs\"):\n        relevant_config[\"dirs\"] = {\"envs\": env_dirs}\n\n    # Try to determine how Hatch was installed\n    source = get_install_source(app.platform.name)\n\n    element_padding = \" \" * 4\n    body = f\"\"\"\\\n## Current behavior\n<!-- A clear and concise description of the behavior. -->\n\n## Expected behavior\n<!-- A clear and concise description of what you expected to happen. -->\n\n## Additional context\n<!-- Add any other context about the problem here. If applicable, add screenshots to help explain. -->\n\n## Debug\n\n### Installation\n\n- Source: {source}\n- Version: {__version__}\n- Platform: {app.platform.display_name}\n- Python version:\n{element_padding}```\n{indent(sys.version, element_padding)}\n{element_padding}```\n\n### Configuration\n\n```toml\n{tomlkit.dumps(relevant_config).rstrip()}\n```\n\"\"\"\n\n    url = f\"https://github.com/pypa/hatch/issues/new?body={quote_plus(body)}\"\n    if no_open:\n        app.display(url)\n    else:\n        webbrowser.open_new_tab(url)\n"
  },
  {
    "path": "src/hatch/cli/self/restore.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(\n    short_help=\"Restore the installation\", context_settings={\"help_option_names\": [], \"ignore_unknown_options\": True}\n)\n@click.argument(\"args\", nargs=-1)\n@click.pass_obj\ndef restore(app: Application, *, args: tuple[str, ...]):  # noqa: ARG001\n    app.abort(\"Hatch is not installed as a binary\")\n"
  },
  {
    "path": "src/hatch/cli/self/update.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(\n    short_help=\"Install the latest version\", context_settings={\"help_option_names\": [], \"ignore_unknown_options\": True}\n)\n@click.argument(\"args\", nargs=-1)\n@click.pass_obj\ndef update(app: Application, *, args: tuple[str, ...]):  # noqa: ARG001\n    app.abort(\"Hatch is not installed as a binary\")\n"
  },
  {
    "path": "src/hatch/cli/shell/__init__.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom typing import TYPE_CHECKING\n\nimport click\n\nfrom hatch.config.constants import AppEnvVars\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"Enter a shell within a project's environment\")\n@click.argument(\"env_name\", required=False)\n@click.option(\"--name\")\n@click.option(\"--path\")\n@click.pass_obj\ndef shell(app: Application, env_name: str | None, name: str, path: str):  # no cov\n    \"\"\"Enter a shell within a project's environment.\"\"\"\n    app.ensure_environment_plugin_dependencies()\n\n    chosen_env = env_name or app.env\n    if chosen_env == app.env_active:\n        app.abort(f\"Already in environment: {chosen_env}\")\n\n    for matrices in (app.project.config.matrices, app.project.config.internal_matrices):\n        if chosen_env in matrices:\n            app.display_error(f\"Environment `{chosen_env}` defines a matrix, choose one of the following instead:\\n\")\n            for generated_name in matrices[chosen_env][\"envs\"]:\n                app.display_error(generated_name)\n\n            app.abort()\n\n    if not name:\n        name = app.config.shell.name\n    if not path:\n        path = app.config.shell.path\n\n    args = app.config.shell.args\n    if not path:\n        name, path = app.shell_data\n        if not app.platform.windows:\n            path, *args = app.platform.modules.shlex.split(path)\n\n    with app.project.ensure_cwd():\n        environment = app.project.get_environment(chosen_env)\n        app.project.prepare_environment(environment, keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)))\n\n        first_run_indicator = app.cache_dir / \"shell\" / \"first_run\"\n        if not first_run_indicator.is_file():\n            app.display_waiting(\n                \"You are about to enter a new shell, exit as you usually would e.g. \"\n                \"by typing `exit` or pressing `ctrl+d`...\"\n            )\n            first_run_indicator.parent.ensure_dir_exists()\n            first_run_indicator.touch()\n\n        environment.enter_shell(name, path, args)\n"
  },
  {
    "path": "src/hatch/cli/status/__init__.py",
    "content": "import click\n\n\n@click.command(short_help=\"Show information about the current environment\")\n@click.pass_obj\ndef status(app):\n    \"\"\"Show information about the current environment.\"\"\"\n\n    def display_pair(key, value, display_func=None, link=None):\n        app.display_info(\"[\", end=\"\")\n        app.display_success(key, end=\"\")\n        app.display_info(\"]\", end=\"\")\n        app.display_info(\" - \", end=\"\")\n        (display_func or app.display_info)(value, link=link)\n\n    if app.project.root is None:\n        if app.project.chosen_name is None:\n            display_pair(\"Project\", \"<no project detected>\", app.display_warning)\n        else:\n            display_pair(\"Project\", f\"{app.project.chosen_name} (not a project)\", app.display_warning)\n    elif app.project.chosen_name is None:\n        display_pair(\"Project\", f\"{app.project.root.name} (current directory)\")\n    else:\n        display_pair(\"Project\", app.project.chosen_name)\n\n    display_pair(\"Location\", str(app.project.location))\n    display_pair(\"Config\", str(app.config_file.path), link=f\"file:///{app.config_file.path}\")\n"
  },
  {
    "path": "src/hatch/cli/terminal.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom abc import ABC, abstractmethod\nfrom functools import cached_property\nfrom textwrap import indent as indent_text\nfrom typing import TYPE_CHECKING\n\nimport click\nfrom rich.console import Console\nfrom rich.style import Style\nfrom rich.text import Text\n\nif TYPE_CHECKING:\n    from collections.abc import Callable\n\n    from rich.status import Status\n\n\nclass TerminalStatus(ABC):\n    @abstractmethod\n    def stop(self) -> None: ...\n\n    def __enter__(self) -> TerminalStatus:  # noqa: PYI034\n        return self\n\n    @abstractmethod\n    def __exit__(self, exc_type, exc_val, exc_tb): ...\n\n\nclass NullStatus(TerminalStatus):\n    def stop(self):\n        pass\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        pass\n\n\nclass BorrowedStatus(TerminalStatus):\n    def __init__(\n        self,\n        console: Console,\n        *,\n        is_interactive: bool,\n        verbosity: int,\n        spinner_style: str,\n        waiting_style: Style | str,\n        success_style: Style | str,\n        initializer: Callable,\n        finalizer: Callable,\n    ):\n        self.__console = console\n        self.__is_interactive = is_interactive\n        self.__verbosity = verbosity\n        self.__spinner_style = spinner_style\n        self.__waiting_style = waiting_style\n        self.__success_style = success_style\n        self.__initializer = initializer\n        self.__finalizer = finalizer\n\n        # This is the possibly active current status\n        self.__status: Status | None = None\n\n        # This is used as a stack to display the current message\n        self.__messages: list[tuple[Text, str]] = []\n\n    def stop(self) -> None:\n        active = self.__active()\n        if self.__status is not None:\n            self.__status.stop()\n            self.__finalizer()\n\n        old_message, final_text = self.__messages[-1]\n        if self.__verbosity > 0 and active:\n            if not final_text:\n                final_text = old_message.plain\n                final_text = f\"Finished {final_text[:1].lower()}{final_text[1:]}\"\n\n            self.__output(Text(final_text, style=self.__success_style))\n\n    def __call__(self, message: str, final_text: str = \"\") -> BorrowedStatus:\n        self.__messages.append((Text(message, style=self.__waiting_style), final_text))\n        return self\n\n    def __enter__(self) -> BorrowedStatus:  # noqa: PYI034\n        if not self.__messages:\n            return self\n\n        message, _ = self.__messages[-1]\n        if not self.__is_interactive:\n            self.__output(message)\n            return self\n\n        if self.__status is None:\n            self.__initializer()\n        else:\n            self.__status.stop()\n\n        self.__status = self.__console.status(message, spinner=self.__spinner_style)\n        self.__status.start()\n\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        old_message, final_text = self.__messages.pop()\n        if self.__verbosity > 0 and self.__active():\n            if not final_text:\n                final_text = old_message.plain\n                final_text = f\"Finished {final_text[:1].lower()}{final_text[1:]}\"\n\n            self.__output(Text(final_text, style=self.__success_style))\n\n        if not self.__is_interactive:\n            return\n\n        self.__status.stop()\n        if not self.__messages:\n            self.__status = None\n            self.__finalizer()\n        else:\n            self.__initializer()\n            message, _ = self.__messages[-1]\n            self.__status = self.__console.status(message, spinner=self.__spinner_style)\n            self.__status.start()\n\n    def __active(self) -> bool:\n        return self.__status is not None and self.__status._live.is_started  # noqa: SLF001\n\n    def __output(self, text):\n        self.__console.stderr = True\n        try:\n            self.__console.print(text, overflow=\"ignore\", no_wrap=True, crop=False)\n        finally:\n            self.__console.stderr = False\n\n\nclass Terminal:\n    def __init__(self, *, verbosity: int, enable_color: bool | None, interactive: bool | None):\n        # Force consistent output for test assertions\n        self.testing = \"HATCH_SELF_TESTING\" in os.environ\n\n        self.verbosity = verbosity\n        self.console = Console(\n            force_terminal=enable_color,\n            force_interactive=interactive,\n            no_color=enable_color is False,\n            markup=False,\n            emoji=False,\n            highlight=False,\n            legacy_windows=False if self.testing else None,\n        )\n\n        # Set defaults so we can pretty print before loading user config\n        self._style_level_success: Style | str = \"bold cyan\"\n        self._style_level_error: Style | str = \"bold red\"\n        self._style_level_warning: Style | str = \"bold yellow\"\n        self._style_level_waiting: Style | str = \"bold magenta\"\n        # Default is simply bold rather than bold white for shells that have been configured with a white background\n        self._style_level_info: Style | str = \"bold\"\n        self._style_level_debug: Style | str = \"bold\"\n\n        # Chosen as the default since it's compatible everywhere and looks nice\n        self._style_spinner = \"simpleDotsScrolling\"\n\n    @cached_property\n    def kv_separator(self) -> Text:\n        return self.style_warning(\"->\")\n\n    def style_success(self, text: str) -> Text:\n        return Text(text, style=self._style_level_success)\n\n    def style_error(self, text: str) -> Text:\n        return Text(text, style=self._style_level_error)\n\n    def style_warning(self, text: str) -> Text:\n        return Text(text, style=self._style_level_warning)\n\n    def style_waiting(self, text: str) -> Text:\n        return Text(text, style=self._style_level_waiting)\n\n    def style_info(self, text: str) -> Text:\n        return Text(text, style=self._style_level_info)\n\n    def style_debug(self, text: str) -> Text:\n        return Text(text, style=self._style_level_debug)\n\n    def initialize_styles(self, styles: dict):  # no cov\n        from rich.errors import StyleSyntaxError\n        from rich.spinner import Spinner\n\n        # Lazily display errors so that they use the correct style\n        errors = []\n\n        for option, style in styles.items():\n            attribute = f\"_style_level_{option}\"\n\n            default_level = getattr(self, attribute, None)\n            if default_level:\n                try:\n                    parsed_style = Style.parse(style)\n                except StyleSyntaxError as e:  # no cov\n                    errors.append(f\"Invalid style definition for `{option}`, defaulting to `{default_level}`: {e}\")\n                    parsed_style = Style.parse(default_level)\n\n                setattr(self, attribute, parsed_style)\n            elif option == \"spinner\":\n                try:\n                    Spinner(style)\n                except KeyError as e:\n                    errors.append(\n                        f\"Invalid style definition for `{option}`, defaulting to `{self._style_spinner}`: {e.args[0]}\"\n                    )\n                else:\n                    self._style_spinner = style\n            else:\n                setattr(self, f\"_style_{option}\", style)\n\n        return errors\n\n    def display(self, text=\"\", **kwargs):\n        self.console.print(text, style=self._style_level_info, overflow=\"ignore\", no_wrap=True, crop=False, **kwargs)\n\n    def display_critical(self, text=\"\", **kwargs):\n        self.console.stderr = True\n        try:\n            self.console.print(\n                text, style=self._style_level_error, overflow=\"ignore\", no_wrap=True, crop=False, **kwargs\n            )\n        finally:\n            self.console.stderr = False\n\n    def display_error(self, text=\"\", *, stderr=True, indent=None, link=None, **kwargs):\n        if self.verbosity < -2:  # noqa: PLR2004\n            return\n\n        self._output(text, self._style_level_error, stderr=stderr, indent=indent, link=link, **kwargs)\n\n    def display_warning(self, text=\"\", *, stderr=True, indent=None, link=None, **kwargs):\n        if self.verbosity < -1:\n            return\n\n        self._output(text, self._style_level_warning, stderr=stderr, indent=indent, link=link, **kwargs)\n\n    def display_info(self, text=\"\", *, stderr=True, indent=None, link=None, **kwargs):\n        if self.verbosity < 0:\n            return\n\n        self._output(text, self._style_level_info, stderr=stderr, indent=indent, link=link, **kwargs)\n\n    def display_success(self, text=\"\", *, stderr=True, indent=None, link=None, **kwargs):\n        if self.verbosity < 0:\n            return\n\n        self._output(text, self._style_level_success, stderr=stderr, indent=indent, link=link, **kwargs)\n\n    def display_waiting(self, text=\"\", *, stderr=True, indent=None, link=None, **kwargs):\n        if self.verbosity < 0:\n            return\n\n        self._output(text, self._style_level_waiting, stderr=stderr, indent=indent, link=link, **kwargs)\n\n    def display_debug(self, text=\"\", level=1, *, stderr=True, indent=None, link=None, **kwargs):\n        if not 1 <= level <= 3:  # noqa: PLR2004\n            error_message = \"Debug output can only have verbosity levels between 1 and 3 (inclusive)\"\n            raise ValueError(error_message)\n\n        if self.verbosity < level:\n            return\n\n        self._output(text, self._style_level_debug, stderr=stderr, indent=indent, link=link, **kwargs)\n\n    def display_mini_header(self, text, *, stderr=False, indent=None, link=None):\n        if self.verbosity < 0:\n            return\n\n        self.display_info(\"[\", stderr=stderr, indent=indent, end=\"\")\n        self.display_success(text, stderr=stderr, link=link, end=\"\")\n        self.display_info(\"]\", stderr=stderr)\n\n    def display_header(self, title=\"\"):\n        self.console.rule(Text(title, self._style_level_success))\n\n    def display_syntax(self, *args, **kwargs):\n        from rich.syntax import Syntax\n\n        kwargs.setdefault(\"background_color\", \"default\" if self.testing else None)\n        self.output(Syntax(*args, **kwargs))\n\n    def display_markdown(self, text, **kwargs):  # no cov\n        from rich.markdown import Markdown\n\n        self.output(Markdown(text), **kwargs)\n\n    def display_pair(self, key, value):\n        self.output(self.style_success(key), self.kv_separator, value)\n\n    def display_table(self, title, columns, *, show_lines=False, column_options=None, force_ascii=False, num_rows=0):\n        from rich.table import Table\n\n        if column_options is None:\n            column_options = {}\n\n        table_options = {}\n        if force_ascii:\n            from rich.box import ASCII_DOUBLE_HEAD\n\n            table_options[\"box\"] = ASCII_DOUBLE_HEAD\n            table_options[\"safe_box\"] = True\n\n        table = Table(title=title, show_lines=show_lines, title_style=\"\", **table_options)\n        columns = dict(columns)\n\n        for column_title, indices in list(columns.items()):\n            if indices:\n                table.add_column(column_title, style=\"bold\", **column_options.get(column_title, {}))\n            else:\n                columns.pop(column_title)\n\n        if not columns:\n            return\n\n        for i in range(num_rows or max(map(max, columns.values())) + 1):\n            row = [indices.get(i, \"\") for indices in columns.values()]\n            if any(row):\n                table.add_row(*row)\n\n        self.output(table)\n\n    @cached_property\n    def status(self) -> BorrowedStatus:\n        return BorrowedStatus(\n            self.console,\n            is_interactive=self.console.is_interactive,\n            verbosity=self.verbosity,\n            spinner_style=self._style_spinner,\n            waiting_style=self._style_level_waiting,\n            success_style=self._style_level_success,\n            initializer=lambda: setattr(self.platform, \"displaying_status\", True),  # type: ignore[attr-defined]\n            finalizer=lambda: setattr(self.platform, \"displaying_status\", False),  # type: ignore[attr-defined]\n        )\n\n    def status_if(self, *args, condition: bool, **kwargs) -> TerminalStatus:\n        return self.status(*args, **kwargs) if condition else NullStatus()\n\n    def _output(self, text=\"\", style=None, *, stderr=False, indent=None, link=None, **kwargs):\n        if indent:\n            text = indent_text(text, indent)\n\n        if link:\n            style = style.update_link(self.platform.format_file_uri(link))\n\n        self.output(text, stderr=stderr, style=style, **kwargs)\n\n    def output(self, *args, stderr=False, **kwargs):\n        kwargs.setdefault(\"overflow\", \"ignore\")\n        kwargs.setdefault(\"no_wrap\", True)\n        kwargs.setdefault(\"crop\", False)\n\n        if not stderr:\n            self.console.print(*args, **kwargs)\n        else:\n            self.console.stderr = True\n            try:\n                self.console.print(*args, **kwargs)\n            finally:\n                self.console.stderr = False\n\n    @staticmethod\n    def prompt(text, **kwargs):\n        return click.prompt(text, **kwargs)\n\n    @staticmethod\n    def confirm(text, **kwargs):\n        return click.confirm(text, **kwargs)\n"
  },
  {
    "path": "src/hatch/cli/test/__init__.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n    from hatch.env.plugin.interface import EnvironmentInterface\n\n\n@click.command(short_help=\"Run tests\", context_settings={\"ignore_unknown_options\": True})\n@click.argument(\"args\", nargs=-1)\n@click.option(\"--randomize\", \"-r\", is_flag=True, help=\"Randomize the order of test execution\")\n@click.option(\"--parallel\", \"-p\", is_flag=True, help=\"Parallelize test execution\")\n@click.option(\"--retries\", type=int, help=\"Number of times to retry failed tests\")\n@click.option(\"--retry-delay\", type=float, help=\"Seconds to wait between retries\")\n@click.option(\"--cover\", \"-c\", is_flag=True, help=\"Measure code coverage\")\n@click.option(\"--cover-quiet\", is_flag=True, help=\"Disable coverage reporting after tests, implicitly enabling --cover\")\n@click.option(\"--all\", \"-a\", \"test_all\", is_flag=True, help=\"Test all environments in the matrix\")\n@click.option(\"--python\", \"-py\", help=\"The Python versions to test, equivalent to: -i py=...\")\n@click.option(\"--include\", \"-i\", \"included_variable_specs\", multiple=True, help=\"The matrix variables to include\")\n@click.option(\"--exclude\", \"-x\", \"excluded_variable_specs\", multiple=True, help=\"The matrix variables to exclude\")\n@click.option(\"--show\", \"-s\", is_flag=True, help=\"Show information about environments in the matrix\")\n@click.pass_context\ndef test(\n    ctx: click.Context,\n    *,\n    args: tuple[str, ...],\n    randomize: bool,\n    parallel: bool,\n    retries: int | None,\n    retry_delay: float | None,\n    cover: bool,\n    cover_quiet: bool,\n    test_all: bool,\n    python: str | None,\n    included_variable_specs: tuple[str, ...],\n    excluded_variable_specs: tuple[str, ...],\n    show: bool,\n):\n    \"\"\"Run tests using the `hatch-test` environment matrix.\n\n    If no filtering options are selected, then tests will be run in the first compatible environment\n    found in the matrix with priority given to those matching the current interpreter.\n\n    The `-i`/`--include` and `-x`/`--exclude` options may be used to include or exclude certain\n    variables, optionally followed by specific comma-separated values, and may be selected multiple\n    times. For example, if you have the following configuration:\n\n    \\b\n    ```toml config-example\n    [[tool.hatch.envs.hatch-test.matrix]]\n    python = [\"3.9\", \"3.10\"]\n    version = [\"42\", \"3.14\", \"9000\"]\n    ```\n\n    then running:\n\n    \\b\n    ```\n    hatch test -i py=3.10 -x version=9000\n    ```\n\n    would run tests in the environments `hatch-test.py3.10-42` and `hatch-test.py3.10-3.14`.\n\n    The `-py`/`--python` option is a shortcut for specifying the inclusion `-i py=...`.\n\n    \\b\n    !!! note\n        The inclusion option is treated as an intersection while the exclusion option is treated as a\n        union i.e. an environment must match all of the included variables to be selected while matching\n        any of the excluded variables will prevent selection.\n    \"\"\"\n    app: Application = ctx.obj\n\n    if show:\n        import os\n\n        from hatch.cli.env.show import show as show_env\n\n        ctx.invoke(\n            show_env,\n            internal=True,\n            hide_titles=True,\n            force_ascii=os.environ.get(\"HATCH_SELF_TESTING\") == \"true\",\n            envs=ctx.obj.project.config.internal_matrices[\"hatch-test\"][\"envs\"] if app.verbose else [\"hatch-test\"],\n        )\n        return\n\n    if cover_quiet:\n        cover = True\n\n    import sys\n\n    from hatch.cli.test.core import PatchedCoverageConfig\n    from hatch.utils.runner import parse_matrix_variables, select_environments\n\n    if python is not None:\n        included_variable_specs = (f\"py={python}\", *included_variable_specs)\n\n    try:\n        included_variables = parse_matrix_variables(included_variable_specs)\n    except ValueError as e:\n        app.abort(f\"Duplicate included variable: {e}\")\n\n    try:\n        excluded_variables = parse_matrix_variables(excluded_variable_specs)\n    except ValueError as e:\n        app.abort(f\"Duplicate excluded variable: {e}\")\n\n    if test_all and (included_variables or excluded_variables):\n        app.abort(\"The --all option cannot be used with the --include or --exclude options.\")\n\n    if retries is None and retry_delay is not None:\n        app.abort(\"The --retry-delay option requires the --retries option to be set as well.\")\n\n    app.ensure_environment_plugin_dependencies()\n\n    test_envs = app.project.config.internal_matrices[\"hatch-test\"][\"envs\"]\n    selected_envs: list[str] = []\n    multiple_possible = True\n    if test_all:\n        selected_envs.extend(test_envs)\n    elif included_variables or excluded_variables:\n        selected_envs.extend(select_environments(test_envs, included_variables, excluded_variables))\n    else:\n        multiple_possible = False\n\n        # Prioritize candidates that seems to match the running interpreter\n        current_version = \".\".join(map(str, sys.version_info[:2]))\n        candidate_names: list[str] = list(test_envs)\n        candidate_names.sort(key=lambda name: test_envs[name].get(\"python\") != current_version)\n\n        candidate_envs: list[EnvironmentInterface] = []\n        for candidate_name in candidate_names:\n            environment = app.project.get_environment(candidate_name)\n            if environment.exists():\n                selected_envs.append(candidate_name)\n                break\n\n            candidate_envs.append(environment)\n\n        if not selected_envs:\n            # If none of the candidates exist, then do a check for compatibility\n            for candidate_env in candidate_envs:\n                try:\n                    candidate_env.check_compatibility()\n                except Exception:  # noqa: BLE001, S110\n                    pass\n                else:\n                    selected_envs.append(candidate_env.name)\n                    break\n            else:\n                app.abort(f\"No compatible environments found: {candidate_envs}\")\n\n    test_script = \"run-cov\" if cover else \"run\"\n    patched_coverage = PatchedCoverageConfig(app.project.location, app.data_dir / \".config\")\n    coverage_config_file = str(patched_coverage.internal_config_path)\n    if cover:\n        patched_coverage.write_config_file()\n\n    for context in app.runner_context(selected_envs, ignore_compat=multiple_possible, display_header=multiple_possible):\n        internal_arguments: list[str] = list(context.env.config.get(\"extra-args\", []))\n\n        if not context.env.config.get(\"randomize\", randomize):\n            internal_arguments.extend([\"-p\", \"no:randomly\"])\n\n        if context.env.config.get(\"parallel\", parallel):\n            internal_arguments.extend([\"-n\", \"logical\"])\n\n        if (num_retries := context.env.config.get(\"retries\", retries)) is not None:\n            if \"-r\" not in args:\n                internal_arguments.extend([\"-r\", \"aR\"])\n\n            internal_arguments.extend([\"--reruns\", str(num_retries)])\n\n        if (seconds_delay := context.env.config.get(\"retry-delay\", retry_delay)) is not None:\n            internal_arguments.extend([\"--reruns-delay\", str(seconds_delay)])\n\n        internal_args = context.env.join_command_args(internal_arguments)\n        if internal_args:\n            # Add an extra space if required\n            internal_args = f\" {internal_args}\"\n\n        arguments: list[str] = []\n        if args:\n            arguments.extend(args)\n        else:\n            arguments.extend(context.env.config.get(\"default-args\", [\"tests\"]))\n\n        context.add_shell_command([test_script, *arguments])\n        context.env_vars[\"HATCH_TEST_ARGS\"] = internal_args\n        if cover:\n            context.env_vars[\"COVERAGE_RCFILE\"] = coverage_config_file\n            context.env_vars[\"COVERAGE_PROCESS_START\"] = coverage_config_file\n\n    if cover:\n        for context in app.runner_context([selected_envs[0]]):\n            context.add_shell_command(\"cov-combine\")\n\n        if not cover_quiet:\n            for context in app.runner_context([selected_envs[0]]):\n                context.add_shell_command(\"cov-report\")\n"
  },
  {
    "path": "src/hatch/cli/test/core.py",
    "content": "from __future__ import annotations\n\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from configparser import ConfigParser\n\n    from hatch.utils.fs import Path\n\n\nclass PatchedCoverageConfig:\n    def __init__(self, project_root: Path, data_dir: Path) -> None:\n        self.project_root = project_root\n        self.data_dir = data_dir\n\n    @cached_property\n    def user_config_path(self) -> Path:\n        # https://coverage.readthedocs.io/en/7.4.4/config.html#sample-file\n        return (\n            dedicated_coverage_file\n            if (dedicated_coverage_file := self.project_root.joinpath(\".coveragerc\")).is_file()\n            else self.project_root.joinpath(\"pyproject.toml\")\n        )\n\n    @cached_property\n    def internal_config_path(self) -> Path:\n        return self.data_dir / \"coverage\" / self.project_root.id / self.user_config_path.name\n\n    def write_config_file(self) -> None:\n        self.internal_config_path.parent.ensure_dir_exists()\n        if self.internal_config_path.name == \".coveragerc\":\n            from configparser import ConfigParser\n\n            cfg = ConfigParser()\n            cfg.read(str(self.user_config_path))\n\n            if \"run\" not in cfg:\n                cfg[\"run\"] = {\"parallel\": \"true\"}\n                self._write_ini(cfg)\n                return\n\n            cfg[\"run\"][\"parallel\"] = \"true\"\n            self._write_ini(cfg)\n        else:\n            import tomli_w\n\n            from hatch.utils.toml import load_toml_data\n\n            project_data = load_toml_data(self.user_config_path.read_text())\n            project_data.setdefault(\"tool\", {}).setdefault(\"coverage\", {}).setdefault(\"run\", {})[\"parallel\"] = True\n            self.internal_config_path.write_text(tomli_w.dumps(project_data))\n\n    def _write_ini(self, cfg: ConfigParser) -> None:\n        with self.internal_config_path.open(\"w\", encoding=\"utf-8\") as f:\n            cfg.write(f)\n"
  },
  {
    "path": "src/hatch/cli/version/__init__.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport click\n\nif TYPE_CHECKING:\n    from hatch.cli.application import Application\n\n\n@click.command(short_help=\"View or set a project's version\")\n@click.argument(\"desired_version\", required=False)\n@click.option(\n    \"--force\",\n    \"-f\",\n    is_flag=True,\n    help=\"Allow an explicit downgrading version to be given\",\n)\n@click.pass_obj\ndef version(app: Application, *, desired_version: str | None, force: bool):\n    \"\"\"View or set a project's version.\"\"\"\n    if app.project.root is None:\n        if app.project.chosen_name is None:\n            app.abort(\"No project detected\")\n        else:\n            app.abort(f\"Project {app.project.chosen_name} (not a project)\")\n\n    if \"version\" in app.project.metadata.config.get(\"project\", {}):\n        if desired_version:\n            app.abort(\"Cannot set version when it is statically defined by the `project.version` field\")\n        else:\n            app.display(app.project.metadata.config[\"project\"][\"version\"])\n            return\n\n    from hatch.config.constants import VersionEnvVars\n    from hatch.project.constants import BUILD_BACKEND\n\n    with app.project.location.as_cwd():\n        if app.project.metadata.build.build_backend != BUILD_BACKEND:\n            if desired_version:\n                app.abort(\"The version can only be set when Hatchling is the build backend\")\n\n            app.ensure_environment_plugin_dependencies()\n            app.project.prepare_build_environment()\n\n            with app.project.location.as_cwd(), app.project.build_env.get_env_vars():\n                project_metadata = app.project.build_frontend.get_core_metadata()\n\n            app.display(project_metadata[\"version\"])\n        else:\n            from hatch.utils.runner import ExecutionContext\n\n            app.ensure_environment_plugin_dependencies()\n            app.project.prepare_build_environment()\n\n            context = ExecutionContext(app.project.build_env)\n            command = [\"python\", \"-u\", \"-m\", \"hatchling\", \"version\"]\n            if desired_version:\n                command.append(desired_version)\n                if force:\n                    context.env_vars[VersionEnvVars.VALIDATE_BUMP] = \"false\"\n\n            context.add_shell_command(command)\n            app.execute_context(context)\n"
  },
  {
    "path": "src/hatch/config/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/config/constants.py",
    "content": "class AppEnvVars:\n    ENV = \"HATCH_ENV\"\n    ENV_ACTIVE = \"HATCH_ENV_ACTIVE\"\n    ENV_OPTION_PREFIX = \"HATCH_ENV_TYPE_\"\n    QUIET = \"HATCH_QUIET\"\n    VERBOSE = \"HATCH_VERBOSE\"\n    INTERACTIVE = \"HATCH_INTERACTIVE\"\n    PYTHON = \"HATCH_PYTHON\"\n    # https://no-color.org\n    NO_COLOR = \"NO_COLOR\"\n    FORCE_COLOR = \"FORCE_COLOR\"\n    KEEP_ENV = \"HATCH_KEEP_ENV\"\n\n\nclass ConfigEnvVars:\n    PROJECT = \"HATCH_PROJECT\"\n    DATA = \"HATCH_DATA_DIR\"\n    CACHE = \"HATCH_CACHE_DIR\"\n    CONFIG = \"HATCH_CONFIG\"\n\n\nclass PublishEnvVars:\n    USER = \"HATCH_INDEX_USER\"\n    AUTH = \"HATCH_INDEX_AUTH\"\n    REPO = \"HATCH_INDEX_REPO\"\n    CA_CERT = \"HATCH_INDEX_CA_CERT\"\n    CLIENT_CERT = \"HATCH_INDEX_CLIENT_CERT\"\n    CLIENT_KEY = \"HATCH_INDEX_CLIENT_KEY\"\n    PUBLISHER = \"HATCH_PUBLISHER\"\n    OPTIONS = \"HATCH_PUBLISHER_OPTIONS\"\n\n\nclass PythonEnvVars:\n    CUSTOM_SOURCE_PREFIX = \"HATCH_PYTHON_CUSTOM_SOURCE_\"\n    CUSTOM_PATH_PREFIX = \"HATCH_PYTHON_CUSTOM_PATH_\"\n    CUSTOM_VERSION_PREFIX = \"HATCH_PYTHON_CUSTOM_VERSION_\"\n\n\nclass VersionEnvVars:\n    VALIDATE_BUMP = \"HATCH_VERSION_VALIDATE_BUMP\"\n"
  },
  {
    "path": "src/hatch/config/model.py",
    "content": "import os\n\nFIELD_TO_PARSE = object()\n\n\nclass ConfigurationError(Exception):\n    def __init__(self, *args, location):\n        self.location = location\n        super().__init__(*args)\n\n    def __str__(self):\n        return f\"Error parsing config:\\n{self.location}\\n  {super().__str__()}\"\n\n\ndef parse_config(obj):\n    if isinstance(obj, LazilyParsedConfig):\n        obj.parse_fields()\n    elif isinstance(obj, list):\n        for o in obj:\n            parse_config(o)\n    elif isinstance(obj, dict):\n        for o in obj.values():\n            parse_config(o)\n\n\nclass LazilyParsedConfig:\n    def __init__(self, config: dict, steps: tuple = ()):\n        self.raw_data = config\n        self.steps = steps\n\n    def parse_fields(self):\n        for attribute in self.__dict__:\n            _, prefix, name = attribute.partition(\"_field_\")\n            if prefix:\n                parse_config(getattr(self, name))\n\n    def raise_error(self, message, *, extra_steps=()):\n        import inspect\n\n        field = inspect.currentframe().f_back.f_code.co_name\n        raise ConfigurationError(message, location=\" -> \".join([*self.steps, field, *extra_steps]))\n\n\nclass RootConfig(LazilyParsedConfig):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._field_mode = FIELD_TO_PARSE\n        self._field_project = FIELD_TO_PARSE\n        self._field_shell = FIELD_TO_PARSE\n        self._field_dirs = FIELD_TO_PARSE\n        self._field_projects = FIELD_TO_PARSE\n        self._field_publish = FIELD_TO_PARSE\n        self._field_template = FIELD_TO_PARSE\n        self._field_terminal = FIELD_TO_PARSE\n\n    @property\n    def mode(self):\n        if self._field_mode is FIELD_TO_PARSE:\n            if \"mode\" in self.raw_data:\n                mode = self.raw_data[\"mode\"]\n                if not isinstance(mode, str):\n                    self.raise_error(\"must be a string\")\n\n                valid_modes = (\"aware\", \"local\", \"project\")\n                if mode not in valid_modes:\n                    self.raise_error(f\"must be one of: {', '.join(valid_modes)}\")\n\n                self._field_mode = mode\n            else:\n                self._field_mode = self.raw_data[\"mode\"] = \"local\"\n\n        return self._field_mode\n\n    @mode.setter\n    def mode(self, value):\n        self.raw_data[\"mode\"] = value\n        self._field_mode = FIELD_TO_PARSE\n\n    @property\n    def project(self):\n        if self._field_project is FIELD_TO_PARSE:\n            if \"project\" in self.raw_data:\n                project = self.raw_data[\"project\"]\n                if not isinstance(project, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_project = project\n            else:\n                self._field_project = self.raw_data[\"project\"] = \"\"\n\n        return self._field_project\n\n    @project.setter\n    def project(self, value):\n        self.raw_data[\"project\"] = value\n        self._field_project = FIELD_TO_PARSE\n\n    @property\n    def shell(self):\n        if self._field_shell is FIELD_TO_PARSE:\n            if \"shell\" in self.raw_data:\n                shell = self.raw_data[\"shell\"]\n                if isinstance(shell, str):\n                    self._field_shell = ShellConfig({\"name\": shell}, (\"shell\",))\n                elif isinstance(shell, dict):\n                    self._field_shell = ShellConfig(shell, (\"shell\",))\n                else:\n                    self.raise_error(\"must be a string or table\")\n            else:\n                self.raw_data[\"shell\"] = \"\"\n                self._field_shell = ShellConfig({\"name\": \"\"}, (\"shell\",))\n\n        return self._field_shell\n\n    @shell.setter\n    def shell(self, value):\n        self.raw_data[\"shell\"] = value\n        self._field_shell = FIELD_TO_PARSE\n\n    @property\n    def dirs(self):\n        if self._field_dirs is FIELD_TO_PARSE:\n            if \"dirs\" in self.raw_data:\n                dirs = self.raw_data[\"dirs\"]\n                if not isinstance(dirs, dict):\n                    self.raise_error(\"must be a table\")\n\n                self._field_dirs = DirsConfig(dirs, (\"dirs\",))\n            else:\n                dirs = {}\n                self.raw_data[\"dirs\"] = dirs\n                self._field_dirs = DirsConfig(dirs, (\"dirs\",))\n\n        return self._field_dirs\n\n    @dirs.setter\n    def dirs(self, value):\n        self.raw_data[\"dirs\"] = value\n        self._field_dirs = FIELD_TO_PARSE\n\n    @property\n    def projects(self):\n        if self._field_projects is FIELD_TO_PARSE:\n            if \"projects\" in self.raw_data:\n                projects = self.raw_data[\"projects\"]\n                if not isinstance(projects, dict):\n                    self.raise_error(\"must be a table\")\n\n                project_data = {}\n                for name, data in projects.items():\n                    if isinstance(data, str):\n                        project_data[name] = ProjectConfig({\"location\": data}, (\"projects\", name))\n                    elif isinstance(data, dict):\n                        project_data[name] = ProjectConfig(data, (\"projects\", name))\n                    else:\n                        self.raise_error(\"must be a string or table\", extra_steps=(name,))\n\n                self._field_projects = project_data\n            else:\n                self._field_projects = self.raw_data[\"projects\"] = {}\n\n        return self._field_projects\n\n    @projects.setter\n    def projects(self, value):\n        self.raw_data[\"projects\"] = value\n        self._field_projects = FIELD_TO_PARSE\n\n    @property\n    def publish(self):\n        if self._field_publish is FIELD_TO_PARSE:\n            if \"publish\" in self.raw_data:\n                publish = self.raw_data[\"publish\"]\n                if not isinstance(publish, dict):\n                    self.raise_error(\"must be a table\")\n\n                for name, data in publish.items():\n                    if not isinstance(data, dict):\n                        self.raise_error(\"must be a table\", extra_steps=(name,))\n\n                self._field_publish = publish\n            else:\n                self._field_publish = self.raw_data[\"publish\"] = {\"index\": {\"repo\": \"main\"}}\n\n        return self._field_publish\n\n    @publish.setter\n    def publish(self, value):\n        self.raw_data[\"publish\"] = value\n        self._field_publish = FIELD_TO_PARSE\n\n    @property\n    def template(self):\n        if self._field_template is FIELD_TO_PARSE:\n            if \"template\" in self.raw_data:\n                template = self.raw_data[\"template\"]\n                if not isinstance(template, dict):\n                    self.raise_error(\"must be a table\")\n\n                self._field_template = TemplateConfig(template, (\"template\",))\n            else:\n                template = {}\n                self.raw_data[\"template\"] = template\n                self._field_template = TemplateConfig(template, (\"template\",))\n\n        return self._field_template\n\n    @template.setter\n    def template(self, value):\n        self.raw_data[\"template\"] = value\n        self._field_template = FIELD_TO_PARSE\n\n    @property\n    def terminal(self):\n        if self._field_terminal is FIELD_TO_PARSE:\n            if \"terminal\" in self.raw_data:\n                terminal = self.raw_data[\"terminal\"]\n                if not isinstance(terminal, dict):\n                    self.raise_error(\"must be a table\")\n\n                self._field_terminal = TerminalConfig(terminal, (\"terminal\",))\n            else:\n                terminal = {}\n                self.raw_data[\"terminal\"] = terminal\n                self._field_terminal = TerminalConfig(terminal, (\"terminal\",))\n\n        return self._field_terminal\n\n    @terminal.setter\n    def terminal(self, value):\n        self.raw_data[\"terminal\"] = value\n        self._field_terminal = FIELD_TO_PARSE\n\n\nclass ShellConfig(LazilyParsedConfig):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._field_name = FIELD_TO_PARSE\n        self._field_path = FIELD_TO_PARSE\n        self._field_args = FIELD_TO_PARSE\n\n    @property\n    def name(self):\n        if self._field_name is FIELD_TO_PARSE:\n            if \"name\" in self.raw_data:\n                name = self.raw_data[\"name\"]\n                if not isinstance(name, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_name = name\n            else:\n                self.raise_error(\"required field\")\n\n        return self._field_name\n\n    @name.setter\n    def name(self, value):\n        self.raw_data[\"name\"] = value\n        self._field_name = FIELD_TO_PARSE\n\n    @property\n    def path(self):\n        if self._field_path is FIELD_TO_PARSE:\n            if \"path\" in self.raw_data:\n                path = self.raw_data[\"path\"]\n                if not isinstance(path, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_path = path\n            else:\n                self._field_path = self.raw_data[\"path\"] = self.name\n\n        return self._field_path\n\n    @path.setter\n    def path(self, value):\n        self.raw_data[\"path\"] = value\n        self._field_path = FIELD_TO_PARSE\n\n    @property\n    def args(self):\n        if self._field_args is FIELD_TO_PARSE:\n            if \"args\" in self.raw_data:\n                args = self.raw_data[\"args\"]\n                if not isinstance(args, list):\n                    self.raise_error(\"must be an array\")\n\n                for i, entry in enumerate(args, 1):\n                    if not isinstance(entry, str):\n                        self.raise_error(\"must be a string\", extra_steps=(str(i),))\n\n                self._field_args = args\n            else:\n                self._field_args = self.raw_data[\"args\"] = []\n\n        return self._field_args\n\n    @args.setter\n    def args(self, value):\n        self.raw_data[\"args\"] = value\n        self._field_args = FIELD_TO_PARSE\n\n\nclass DirsConfig(LazilyParsedConfig):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._field_project = FIELD_TO_PARSE\n        self._field_env = FIELD_TO_PARSE\n        self._field_python = FIELD_TO_PARSE\n        self._field_data = FIELD_TO_PARSE\n        self._field_cache = FIELD_TO_PARSE\n\n    @property\n    def project(self):\n        if self._field_project is FIELD_TO_PARSE:\n            if \"project\" in self.raw_data:\n                project = self.raw_data[\"project\"]\n                if not isinstance(project, list):\n                    self.raise_error(\"must be an array\")\n\n                for i, entry in enumerate(project, 1):\n                    if not isinstance(entry, str):\n                        self.raise_error(\"must be a string\", extra_steps=(str(i),))\n\n                self._field_project = project\n            else:\n                self._field_project = self.raw_data[\"project\"] = []\n\n        return self._field_project\n\n    @project.setter\n    def project(self, value):\n        self.raw_data[\"project\"] = value\n        self._field_project = FIELD_TO_PARSE\n\n    @property\n    def env(self):\n        if self._field_env is FIELD_TO_PARSE:\n            if \"env\" in self.raw_data:\n                env = self.raw_data[\"env\"]\n                if not isinstance(env, dict):\n                    self.raise_error(\"must be a table\")\n\n                for key, value in env.items():\n                    if not isinstance(value, str):\n                        self.raise_error(\"must be a string\", extra_steps=(key,))\n\n                self._field_env = env\n            else:\n                self._field_env = self.raw_data[\"env\"] = {}\n\n        return self._field_env\n\n    @env.setter\n    def env(self, value):\n        self.raw_data[\"env\"] = value\n        self._field_env = FIELD_TO_PARSE\n\n    @property\n    def python(self):\n        if self._field_python is FIELD_TO_PARSE:\n            if \"python\" in self.raw_data:\n                python = self.raw_data[\"python\"]\n                if not isinstance(python, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_python = python\n            else:\n                self._field_python = self.raw_data[\"python\"] = \"isolated\"\n\n        return self._field_python\n\n    @python.setter\n    def python(self, value):\n        self.raw_data[\"python\"] = value\n        self._field_python = FIELD_TO_PARSE\n\n    @property\n    def data(self):\n        if self._field_data is FIELD_TO_PARSE:\n            if \"data\" in self.raw_data:\n                data = self.raw_data[\"data\"]\n                if not isinstance(data, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_data = data\n            else:\n                from platformdirs import user_data_dir\n\n                self._field_data = self.raw_data[\"data\"] = user_data_dir(\"hatch\", appauthor=False)\n\n        return self._field_data\n\n    @data.setter\n    def data(self, value):\n        self.raw_data[\"data\"] = value\n        self._field_data = FIELD_TO_PARSE\n\n    @property\n    def cache(self):\n        if self._field_cache is FIELD_TO_PARSE:\n            if \"cache\" in self.raw_data:\n                cache = self.raw_data[\"cache\"]\n                if not isinstance(cache, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_cache = cache\n            else:\n                from platformdirs import user_cache_dir\n\n                self._field_cache = self.raw_data[\"cache\"] = user_cache_dir(\"hatch\", appauthor=False)\n\n        return self._field_cache\n\n    @cache.setter\n    def cache(self, value):\n        self.raw_data[\"cache\"] = value\n        self._field_cache = FIELD_TO_PARSE\n\n\nclass ProjectConfig(LazilyParsedConfig):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._field_location = FIELD_TO_PARSE\n\n    @property\n    def location(self):\n        if self._field_location is FIELD_TO_PARSE:\n            if \"location\" in self.raw_data:\n                location = self.raw_data[\"location\"]\n                if not isinstance(location, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_location = location\n            else:\n                self.raise_error(\"required field\")\n\n        return self._field_location\n\n    @location.setter\n    def location(self, value):\n        self.raw_data[\"location\"] = value\n        self._field_location = FIELD_TO_PARSE\n\n\nclass TemplateConfig(LazilyParsedConfig):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._field_name = FIELD_TO_PARSE\n        self._field_email = FIELD_TO_PARSE\n        self._field_licenses = FIELD_TO_PARSE\n        self._field_plugins = FIELD_TO_PARSE\n\n    @property\n    def name(self):\n        if self._field_name is FIELD_TO_PARSE:\n            if \"name\" in self.raw_data:\n                name = self.raw_data[\"name\"]\n                if not isinstance(name, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_name = name\n            else:\n                name = os.environ.get(\"GIT_AUTHOR_NAME\")\n                if name is None:\n                    import subprocess\n\n                    try:\n                        name = subprocess.check_output(\n                            [\"git\", \"config\", \"--get\", \"user.name\"],  # noqa: S607\n                            text=True,\n                        ).strip()\n                    except Exception:  # noqa: BLE001\n                        name = \"U.N. Owen\"\n\n                self._field_name = self.raw_data[\"name\"] = name\n\n        return self._field_name\n\n    @name.setter\n    def name(self, value):\n        self.raw_data[\"name\"] = value\n        self._field_name = FIELD_TO_PARSE\n\n    @property\n    def email(self):\n        if self._field_email is FIELD_TO_PARSE:\n            if \"email\" in self.raw_data:\n                email = self.raw_data[\"email\"]\n                if not isinstance(email, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_email = email\n            else:\n                email = os.environ.get(\"GIT_AUTHOR_EMAIL\")\n                if email is None:\n                    import subprocess\n\n                    try:\n                        email = subprocess.check_output(\n                            [\"git\", \"config\", \"--get\", \"user.email\"],  # noqa: S607\n                            text=True,\n                        ).strip()\n                    except Exception:  # noqa: BLE001\n                        email = \"void@some.where\"\n\n                self._field_email = self.raw_data[\"email\"] = email\n\n        return self._field_email\n\n    @email.setter\n    def email(self, value):\n        self.raw_data[\"email\"] = value\n        self._field_email = FIELD_TO_PARSE\n\n    @property\n    def licenses(self):\n        if self._field_licenses is FIELD_TO_PARSE:\n            if \"licenses\" in self.raw_data:\n                licenses = self.raw_data[\"licenses\"]\n                if not isinstance(licenses, dict):\n                    self.raise_error(\"must be a table\")\n\n                self._field_licenses = LicensesConfig(licenses, (*self.steps, \"licenses\"))\n            else:\n                licenses = {}\n                self.raw_data[\"licenses\"] = licenses\n                self._field_licenses = LicensesConfig(licenses, (*self.steps, \"licenses\"))\n\n        return self._field_licenses\n\n    @licenses.setter\n    def licenses(self, value):\n        self.raw_data[\"licenses\"] = value\n        self._field_licenses = FIELD_TO_PARSE\n\n    @property\n    def plugins(self):\n        if self._field_plugins is FIELD_TO_PARSE:\n            if \"plugins\" in self.raw_data:\n                plugins = self.raw_data[\"plugins\"]\n                if not isinstance(plugins, dict):\n                    self.raise_error(\"must be a table\")\n\n                for name, data in plugins.items():\n                    if not isinstance(data, dict):\n                        self.raise_error(\"must be a table\", extra_steps=(name,))\n\n                self._field_plugins = plugins\n            else:\n                self._field_plugins = self.raw_data[\"plugins\"] = {\n                    \"default\": {\"tests\": True, \"ci\": False, \"src-layout\": True}\n                }\n\n        return self._field_plugins\n\n    @plugins.setter\n    def plugins(self, value):\n        self.raw_data[\"plugins\"] = value\n        self._field_plugins = FIELD_TO_PARSE\n\n\nclass LicensesConfig(LazilyParsedConfig):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._field_headers = FIELD_TO_PARSE\n        self._field_default = FIELD_TO_PARSE\n\n    @property\n    def headers(self):\n        if self._field_headers is FIELD_TO_PARSE:\n            if \"headers\" in self.raw_data:\n                headers = self.raw_data[\"headers\"]\n                if not isinstance(headers, bool):\n                    self.raise_error(\"must be a boolean\")\n\n                self._field_headers = headers\n            else:\n                self._field_headers = self.raw_data[\"headers\"] = True\n\n        return self._field_headers\n\n    @headers.setter\n    def headers(self, value):\n        self.raw_data[\"headers\"] = value\n        self._field_headers = FIELD_TO_PARSE\n\n    @property\n    def default(self):\n        if self._field_default is FIELD_TO_PARSE:\n            if \"default\" in self.raw_data:\n                default = self.raw_data[\"default\"]\n                if not isinstance(default, list):\n                    self.raise_error(\"must be an array\")\n\n                for i, entry in enumerate(default, 1):\n                    if not isinstance(entry, str):\n                        self.raise_error(\"must be a string\", extra_steps=(str(i),))\n\n                self._field_default = default\n            else:\n                self._field_default = self.raw_data[\"default\"] = [\"MIT\"]\n\n        return self._field_default\n\n    @default.setter\n    def default(self, value):\n        self.raw_data[\"default\"] = value\n        self._field_default = FIELD_TO_PARSE\n\n\nclass TerminalConfig(LazilyParsedConfig):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._field_styles = FIELD_TO_PARSE\n\n    @property\n    def styles(self):\n        if self._field_styles is FIELD_TO_PARSE:\n            if \"styles\" in self.raw_data:\n                styles = self.raw_data[\"styles\"]\n                if not isinstance(styles, dict):\n                    self.raise_error(\"must be a table\")\n\n                self._field_styles = StylesConfig(styles, (*self.steps, \"styles\"))\n            else:\n                styles = {}\n                self.raw_data[\"styles\"] = styles\n                self._field_styles = StylesConfig(styles, (*self.steps, \"styles\"))\n\n        return self._field_styles\n\n    @styles.setter\n    def styles(self, value):\n        self.raw_data[\"styles\"] = value\n        self._field_styles = FIELD_TO_PARSE\n\n\nclass StylesConfig(LazilyParsedConfig):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._field_info = FIELD_TO_PARSE\n        self._field_success = FIELD_TO_PARSE\n        self._field_error = FIELD_TO_PARSE\n        self._field_warning = FIELD_TO_PARSE\n        self._field_waiting = FIELD_TO_PARSE\n        self._field_debug = FIELD_TO_PARSE\n        self._field_spinner = FIELD_TO_PARSE\n\n    @property\n    def info(self):\n        if self._field_info is FIELD_TO_PARSE:\n            if \"info\" in self.raw_data:\n                info = self.raw_data[\"info\"]\n                if not isinstance(info, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_info = info\n            else:\n                self._field_info = self.raw_data[\"info\"] = \"bold\"\n\n        return self._field_info\n\n    @info.setter\n    def info(self, value):\n        self.raw_data[\"info\"] = value\n        self._field_info = FIELD_TO_PARSE\n\n    @property\n    def success(self):\n        if self._field_success is FIELD_TO_PARSE:\n            if \"success\" in self.raw_data:\n                success = self.raw_data[\"success\"]\n                if not isinstance(success, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_success = success\n            else:\n                self._field_success = self.raw_data[\"success\"] = \"bold cyan\"\n\n        return self._field_success\n\n    @success.setter\n    def success(self, value):\n        self.raw_data[\"success\"] = value\n        self._field_success = FIELD_TO_PARSE\n\n    @property\n    def error(self):\n        if self._field_error is FIELD_TO_PARSE:\n            if \"error\" in self.raw_data:\n                error = self.raw_data[\"error\"]\n                if not isinstance(error, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_error = error\n            else:\n                self._field_error = self.raw_data[\"error\"] = \"bold red\"\n\n        return self._field_error\n\n    @error.setter\n    def error(self, value):\n        self.raw_data[\"error\"] = value\n        self._field_error = FIELD_TO_PARSE\n\n    @property\n    def warning(self):\n        if self._field_warning is FIELD_TO_PARSE:\n            if \"warning\" in self.raw_data:\n                warning = self.raw_data[\"warning\"]\n                if not isinstance(warning, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_warning = warning\n            else:\n                self._field_warning = self.raw_data[\"warning\"] = \"bold yellow\"\n\n        return self._field_warning\n\n    @warning.setter\n    def warning(self, value):\n        self.raw_data[\"warning\"] = value\n        self._field_warning = FIELD_TO_PARSE\n\n    @property\n    def waiting(self):\n        if self._field_waiting is FIELD_TO_PARSE:\n            if \"waiting\" in self.raw_data:\n                waiting = self.raw_data[\"waiting\"]\n                if not isinstance(waiting, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_waiting = waiting\n            else:\n                self._field_waiting = self.raw_data[\"waiting\"] = \"bold magenta\"\n\n        return self._field_waiting\n\n    @waiting.setter\n    def waiting(self, value):\n        self.raw_data[\"waiting\"] = value\n        self._field_waiting = FIELD_TO_PARSE\n\n    @property\n    def debug(self):\n        if self._field_debug is FIELD_TO_PARSE:\n            if \"debug\" in self.raw_data:\n                debug = self.raw_data[\"debug\"]\n                if not isinstance(debug, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_debug = debug\n            else:\n                self._field_debug = self.raw_data[\"debug\"] = \"bold\"\n\n        return self._field_debug\n\n    @debug.setter\n    def debug(self, value):\n        self.raw_data[\"debug\"] = value\n        self._field_debug = FIELD_TO_PARSE\n\n    @property\n    def spinner(self):\n        if self._field_spinner is FIELD_TO_PARSE:\n            if \"spinner\" in self.raw_data:\n                spinner = self.raw_data[\"spinner\"]\n                if not isinstance(spinner, str):\n                    self.raise_error(\"must be a string\")\n\n                self._field_spinner = spinner\n            else:\n                self._field_spinner = self.raw_data[\"spinner\"] = \"simpleDotsScrolling\"\n\n        return self._field_spinner\n\n    @spinner.setter\n    def spinner(self, value):\n        self.raw_data[\"spinner\"] = value\n        self._field_spinner = FIELD_TO_PARSE\n"
  },
  {
    "path": "src/hatch/config/user.py",
    "content": "from __future__ import annotations\n\nfrom typing import cast\n\nfrom hatch.config.model import RootConfig\nfrom hatch.utils.fs import Path\nfrom hatch.utils.toml import load_toml_data\n\n\nclass ConfigFile:\n    def __init__(self, path: Path | None = None):\n        self._path: Path | None = path\n        self.model = cast(RootConfig, None)\n\n    @property\n    def path(self):\n        if self._path is None:\n            self._path = self.get_default_location()\n\n        return self._path\n\n    @path.setter\n    def path(self, value):\n        self._path = value\n\n    def save(self, content=None):\n        import tomli_w\n\n        if not content:\n            content = tomli_w.dumps(self.model.raw_data)\n\n        self.path.ensure_parent_dir_exists()\n        self.path.write_atomic(content, \"w\", encoding=\"utf-8\")\n\n    def load(self):\n        self.model = RootConfig(load_toml_data(self.read()))\n\n    def read(self) -> str:\n        return self.path.read_text(\"utf-8\")\n\n    def read_scrubbed(self) -> str:\n        import tomli_w\n\n        config = RootConfig(load_toml_data(self.read()))\n        config.raw_data.pop(\"publish\", None)\n        return tomli_w.dumps(config.raw_data)\n\n    def restore(self):\n        import tomli_w\n\n        config = RootConfig({})\n        config.parse_fields()\n\n        content = tomli_w.dumps(config.raw_data)\n        self.save(content)\n\n        self.model = config\n\n    def update(self):  # no cov\n        self.model.parse_fields()\n        self.save()\n\n    @classmethod\n    def get_default_location(cls) -> Path:\n        from platformdirs import user_config_dir\n\n        return Path(user_config_dir(\"hatch\", appauthor=False)) / \"config.toml\"\n"
  },
  {
    "path": "src/hatch/config/utils.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport tomlkit\n\nif TYPE_CHECKING:\n    from tomlkit.items import InlineTable\n    from tomlkit.toml_document import TOMLDocument\n\n    from hatch.utils.fs import Path\n\n\ndef save_toml_document(document: TOMLDocument, path: Path):\n    path.ensure_parent_dir_exists()\n    path.write_atomic(tomlkit.dumps(document), \"w\", encoding=\"utf-8\")\n\n\ndef create_toml_document(config: dict) -> InlineTable:\n    return tomlkit.item(config)\n"
  },
  {
    "path": "src/hatch/dep/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/dep/core.py",
    "content": "from __future__ import annotations\n\nfrom functools import cached_property\n\nfrom packaging.requirements import InvalidRequirement, Requirement\n\nfrom hatch.utils.fs import Path\n\nInvalidDependencyError = InvalidRequirement\n\n\nclass Dependency(Requirement):\n    def __init__(self, s: str, *, editable: bool = False) -> None:\n        super().__init__(s)\n\n        if editable and self.url is None:\n            message = f\"Editable dependency must refer to a local path: {s}\"\n            raise InvalidDependencyError(message)\n\n        self.__editable = editable\n\n    @property\n    def editable(self) -> bool:\n        return self.__editable\n\n    @cached_property\n    def path(self) -> Path | None:\n        from urllib.parse import unquote\n\n        if self.url is None:\n            return None\n\n        import hyperlink\n\n        uri = hyperlink.parse(self.url)\n        if uri.scheme != \"file\":\n            return None\n        decoded_url = unquote(self.url)\n        return Path.from_uri(decoded_url)\n"
  },
  {
    "path": "src/hatch/dep/sync.py",
    "content": "from __future__ import annotations\n\nimport re\nimport sys\nfrom importlib.metadata import Distribution, DistributionFinder\n\nfrom packaging.markers import default_environment\n\nfrom hatch.dep.core import Dependency\nfrom hatch.utils.fs import Path\n\n\nclass InstalledDistributions:\n    def __init__(self, *, sys_path: list[str] | None = None, environment: dict[str, str] | None = None) -> None:\n        self.__sys_path: list[str] = sys.path if sys_path is None else sys_path\n        self.__environment: dict[str, str] = (\n            default_environment() if environment is None else environment  # type: ignore[assignment]\n        )\n        self.__resolver = Distribution.discover(context=DistributionFinder.Context(path=self.__sys_path))\n        self.__distributions: dict[str, Distribution] = {}\n        self.__search_exhausted = False\n        self.__canonical_regex = re.compile(r\"[-_.]+\")\n\n    def dependencies_in_sync(self, dependencies: list[Dependency]) -> bool:\n        return all(self.dependency_in_sync(dependency) for dependency in dependencies)\n\n    def missing_dependencies(self, dependencies: list[Dependency]) -> list[Dependency]:\n        return [dependency for dependency in dependencies if not self.dependency_in_sync(dependency)]\n\n    def dependency_in_sync(self, dependency: Dependency, *, environment: dict[str, str] | None = None) -> bool:\n        if environment is None:\n            environment = self.__environment\n\n        if dependency.marker and not dependency.marker.evaluate(environment):\n            return True\n\n        distribution = self[dependency.name]\n        if distribution is None:\n            return False\n\n        extras = dependency.extras\n        if extras:\n            transitive_dependencies: list[str] = distribution.metadata.get_all(\"Requires-Dist\", [])\n            if not transitive_dependencies:\n                return False\n\n            available_extras: list[str] = distribution.metadata.get_all(\"Provides-Extra\", [])\n\n            for dependency_string in transitive_dependencies:\n                transitive_dependency = Dependency(dependency_string)\n                if not transitive_dependency.marker:\n                    continue\n\n                for extra in extras:\n                    # FIXME: This may cause a build to never be ready if newer versions do not provide the desired\n                    # extra and it's just a user error/typo. See: https://github.com/pypa/pip/issues/7122\n                    if extra not in available_extras:\n                        return False\n\n                    extra_environment = dict(environment)\n                    extra_environment[\"extra\"] = extra\n                    if not self.dependency_in_sync(transitive_dependency, environment=extra_environment):\n                        return False\n\n        if dependency.specifier and not dependency.specifier.contains(distribution.version):\n            return False\n\n        # TODO: handle https://discuss.python.org/t/11938\n        if dependency.url:\n            direct_url_file = distribution.read_text(\"direct_url.json\")\n            if direct_url_file is None:\n                return False\n\n            import json\n\n            # https://packaging.python.org/specifications/direct-url/\n            direct_url_data = json.loads(direct_url_file)\n            url = direct_url_data[\"url\"]\n            if \"dir_info\" in direct_url_data:\n                dir_info = direct_url_data[\"dir_info\"]\n                editable = dir_info.get(\"editable\", False)\n                if editable != dependency.editable:\n                    return False\n\n                if Path.from_uri(url) != dependency.path:\n                    return False\n\n            if \"vcs_info\" in direct_url_data:\n                vcs_info = direct_url_data[\"vcs_info\"]\n                vcs = vcs_info[\"vcs\"]\n                commit_id = vcs_info[\"commit_id\"]\n                requested_revision = vcs_info.get(\"requested_revision\")\n\n                # Try a few variations, see https://peps.python.org/pep-0440/#direct-references\n                if (\n                    requested_revision and dependency.url == f\"{vcs}+{url}@{requested_revision}#{commit_id}\"\n                ) or dependency.url == f\"{vcs}+{url}@{commit_id}\":\n                    return True\n\n                if dependency.url in {f\"{vcs}+{url}\", f\"{vcs}+{url}@{requested_revision}\"}:\n                    import subprocess\n\n                    if vcs == \"git\":\n                        vcs_cmd = [vcs, \"ls-remote\", url]\n                        if requested_revision:\n                            vcs_cmd.append(requested_revision)\n                    # TODO: add elifs for hg, svn, and bzr https://github.com/pypa/hatch/issues/760\n                    else:\n                        return False\n                    result = subprocess.run(vcs_cmd, capture_output=True, text=True)  # noqa: PLW1510\n                    if result.returncode or not result.stdout.strip():\n                        return False\n                    latest_commit_id, *_ = result.stdout.split()\n                    return commit_id == latest_commit_id\n\n                return False\n\n        return True\n\n    def __getitem__(self, item: str) -> Distribution | None:\n        item = self.__canonical_regex.sub(\"-\", item).lower()\n        possible_distribution = self.__distributions.get(item)\n        if possible_distribution is not None:\n            return possible_distribution\n\n        if self.__search_exhausted:\n            return None\n\n        for distribution in self.__resolver:\n            name = distribution.metadata[\"Name\"]\n            if name is None:\n                continue\n\n            name = self.__canonical_regex.sub(\"-\", name).lower()\n            self.__distributions[name] = distribution\n            if name == item:\n                return distribution\n\n        self.__search_exhausted = True\n\n        return None\n\n\ndef dependencies_in_sync(\n    dependencies: list[Dependency], sys_path: list[str] | None = None, environment: dict[str, str] | None = None\n) -> bool:  # no cov\n    # This function is unused and only temporarily exists for plugin backwards compatibility.\n    distributions = InstalledDistributions(sys_path=sys_path, environment=environment)\n    return distributions.dependencies_in_sync(dependencies)\n"
  },
  {
    "path": "src/hatch/env/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/env/collectors/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/env/collectors/custom.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom typing import Any\n\nfrom hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\nfrom hatch.plugin.constants import DEFAULT_CUSTOM_SCRIPT\nfrom hatch.plugin.utils import load_plugin_from_script\n\n\nclass CustomEnvironmentCollector:\n    PLUGIN_NAME = \"custom\"\n\n    def __new__(  # type: ignore[misc]\n        cls,\n        root: str,\n        config: dict[str, Any],\n        *args: Any,\n        **kwargs: Any,\n    ) -> EnvironmentCollectorInterface:\n        custom_script = config.get(\"path\", DEFAULT_CUSTOM_SCRIPT)\n        if not isinstance(custom_script, str):\n            message = f\"Option `path` for environment collector `{cls.PLUGIN_NAME}` must be a string\"\n            raise TypeError(message)\n\n        if not custom_script:\n            message = f\"Option `path` for environment collector `{cls.PLUGIN_NAME}` must not be empty if defined\"\n            raise ValueError(message)\n\n        path = os.path.normpath(os.path.join(root, custom_script))\n        if not os.path.isfile(path):\n            message = f\"Plugin script does not exist: {custom_script}\"\n            raise OSError(message)\n\n        hook_class = load_plugin_from_script(\n            path, custom_script, EnvironmentCollectorInterface, \"environment_collector\"\n        )\n        hook = hook_class(root, config, *args, **kwargs)\n\n        # Always keep the name to avoid confusion\n        hook.PLUGIN_NAME = cls.PLUGIN_NAME\n\n        return hook\n"
  },
  {
    "path": "src/hatch/env/collectors/default.py",
    "content": "from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\nfrom hatch.env.internal import get_internal_env_config\nfrom hatch.env.utils import ensure_valid_environment\n\n\nclass DefaultEnvironmentCollector(EnvironmentCollectorInterface):\n    PLUGIN_NAME = \"default\"\n\n    def get_initial_config(self):  # noqa: PLR6301\n        default_config = {}\n        ensure_valid_environment(default_config)\n\n        return {\"default\": default_config, **get_internal_env_config()}\n"
  },
  {
    "path": "src/hatch/env/collectors/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/env/collectors/plugin/hooks.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom hatch.env.collectors.custom import CustomEnvironmentCollector\nfrom hatch.env.collectors.default import DefaultEnvironmentCollector\nfrom hatchling.plugin import hookimpl\n\nif TYPE_CHECKING:\n    from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\n\n\n@hookimpl\ndef hatch_register_environment_collector() -> list[type[EnvironmentCollectorInterface]]:\n    return [CustomEnvironmentCollector, DefaultEnvironmentCollector]\n"
  },
  {
    "path": "src/hatch/env/collectors/plugin/interface.py",
    "content": "from __future__ import annotations\n\n\nclass EnvironmentCollectorInterface:\n    \"\"\"\n    Example usage:\n\n    ```python tab=\"plugin.py\"\n        from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\n\n\n        class SpecialEnvironmentCollector(EnvironmentCollectorInterface):\n            PLUGIN_NAME = 'special'\n            ...\n    ```\n\n    ```python tab=\"hooks.py\"\n        from hatchling.plugin import hookimpl\n\n        from .plugin import SpecialEnvironmentCollector\n\n\n        @hookimpl\n        def hatch_register_environment_collector():\n            return SpecialEnvironmentCollector\n    ```\n    \"\"\"\n\n    PLUGIN_NAME = \"\"\n    \"\"\"The name used for selection.\"\"\"\n\n    def __init__(self, root, config):\n        self.__root = root\n        self.__config = config\n\n    @property\n    def root(self):\n        \"\"\"\n        The root of the project tree as a path-like object.\n        \"\"\"\n        return self.__root\n\n    @property\n    def config(self) -> dict:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.env.collectors.<PLUGIN_NAME>]\n        ```\n        \"\"\"\n        return self.__config\n\n    def get_initial_config(self) -> dict[str, dict]:  # noqa: PLR6301\n        \"\"\"\n        Returns configuration for environments keyed by the environment or matrix name.\n        \"\"\"\n        return {}\n\n    def finalize_config(self, config: dict[str, dict]):\n        \"\"\"\n        Finalizes configuration for environments keyed by the environment or matrix name. This will override\n        any user-defined settings and any collectors that ran before this call.\n\n        This is called before matrices are turned into concrete environments.\n        \"\"\"\n\n    def finalize_environments(self, config: dict[str, dict]):\n        \"\"\"\n        Finalizes configuration for environments keyed by the environment name. This will override\n        any user-defined settings and any collectors that ran before this call.\n\n        This is called after matrices are turned into concrete environments.\n        \"\"\"\n"
  },
  {
    "path": "src/hatch/env/context.py",
    "content": "from abc import ABC, abstractmethod\n\nfrom hatch.env.utils import get_verbosity_flag\nfrom hatchling.utils.context import ContextFormatter\n\n\nclass EnvironmentContextFormatterBase(ContextFormatter, ABC):\n    @abstractmethod\n    def formatters(self):\n        return {}\n\n\nclass EnvironmentContextFormatter(EnvironmentContextFormatterBase):\n    def __init__(self, environment):\n        self.environment = environment\n        self.CONTEXT_NAME = f\"environment_{environment.PLUGIN_NAME}\"\n\n    def formatters(self):  # noqa: PLR6301\n        \"\"\"\n        This returns a mapping of supported field names to their respective formatting functions. Each function\n        accepts 2 arguments:\n\n        - the `value` that was passed to the format call, defaulting to `None`\n        - the modifier `data`, defaulting to an empty string\n        \"\"\"\n        return {}\n\n    def get_formatters(self):\n        formatters = {\n            \"args\": self.__format_args,\n            \"env_name\": self.__format_env_name,\n            \"env_type\": self.__format_env_type,\n            \"matrix\": self.__format_matrix,\n            \"verbosity\": self.__format_verbosity,\n        }\n        formatters.update(self.formatters())\n        return formatters\n\n    def __format_args(self, value, data):  # noqa: PLR6301\n        if value is not None:\n            return value\n\n        return data or \"\"\n\n    def __format_env_name(self, value, data):  # noqa: ARG002\n        return self.environment.name\n\n    def __format_env_type(self, value, data):  # noqa: ARG002\n        return self.environment.PLUGIN_NAME\n\n    def __format_matrix(self, value, data):  # noqa: ARG002\n        if not data:\n            message = \"The `matrix` context formatting field requires a modifier\"\n            raise ValueError(message)\n\n        variable, separator, default = data.partition(\":\")\n        if variable in self.environment.matrix_variables:\n            return self.environment.matrix_variables[variable]\n\n        if not separator:\n            message = f\"Nonexistent matrix variable must set a default: {variable}\"\n            raise ValueError(message)\n\n        return default\n\n    def __format_verbosity(self, value, data):  # noqa: ARG002\n        if not data:\n            return str(self.environment.verbosity)\n\n        modifier, _, adjustment = data.partition(\":\")\n        if modifier != \"flag\":\n            message = f\"Unknown verbosity modifier: {modifier}\"\n            raise ValueError(message)\n\n        if not adjustment:\n            adjustment = \"0\"\n\n        try:\n            adjustment = int(adjustment)\n        except ValueError:\n            message = f\"Verbosity flag adjustment must be an integer: {adjustment}\"\n            raise TypeError(message) from None\n\n        return get_verbosity_flag(self.environment.verbosity, adjustment=adjustment)\n"
  },
  {
    "path": "src/hatch/env/internal/__init__.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\nfrom hatch.env.utils import ensure_valid_environment\n\n\ndef get_internal_env_config() -> dict[str, Any]:\n    from hatch.env.internal import build, static_analysis, test, uv\n\n    internal_config = {}\n    for env_name, env_config in (\n        (\"hatch-build\", build.get_default_config()),\n        (\"hatch-static-analysis\", static_analysis.get_default_config()),\n        (\"hatch-test\", test.get_default_config()),\n        (\"hatch-uv\", uv.get_default_config()),\n    ):\n        env_config[\"template\"] = env_name\n        ensure_valid_environment(env_config)\n        internal_config[env_name] = env_config\n\n    return internal_config\n\n\ndef is_isolated_environment(env_name: str, config: dict[str, Any]) -> bool:\n    # Provide super isolation and immunity to project-level environment removal only when the environment:\n    #\n    # 1. Is not used for builds\n    # 2. Does not require the project being installed\n    # 3. The default configuration is used\n    #\n    # For example, the environment for static analysis depends only on Ruff at a specific default\n    # version. This environment does not require the project and can be reused by every project to\n    # improve responsiveness. However, if the user for some reason chooses to override the dependencies\n    # to use a different version of Ruff, then the project would get its own environment.\n    return (\n        not config.get(\"builder\", False)\n        and config.get(\"skip-install\", False)\n        and is_default_environment(env_name, config)\n    )\n\n\ndef is_default_environment(env_name: str, config: dict[str, Any]) -> bool:\n    # Standalone environment\n    internal_config = get_internal_env_config().get(env_name)\n    if not internal_config:\n        # Environment generated from matrix\n        internal_config = get_internal_env_config().get(env_name.split(\".\")[0])\n        if not internal_config:\n            return False\n\n    # Only consider things that would modify the actual installation, other options like extra scripts don't matter\n    for key in (\"dependencies\", \"extra-dependencies\", \"features\"):\n        if config.get(key) != internal_config.get(key):\n            return False\n\n    return True\n"
  },
  {
    "path": "src/hatch/env/internal/build.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\n\ndef get_default_config() -> dict[str, Any]:\n    return {\n        \"skip-install\": True,\n        \"builder\": True,\n        \"installer\": \"uv\",\n    }\n"
  },
  {
    "path": "src/hatch/env/internal/static_analysis.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\n\ndef get_default_config() -> dict[str, Any]:\n    return {\n        \"skip-install\": True,\n        \"installer\": \"uv\",\n        \"dependencies\": [f\"ruff=={RUFF_DEFAULT_VERSION}\"],\n        \"scripts\": {\n            \"format-check\": \"ruff format{env:HATCH_FMT_ARGS:} --check --diff {args:.}\",\n            \"format-fix\": \"ruff format{env:HATCH_FMT_ARGS:} {args:.}\",\n            \"lint-check\": \"ruff check{env:HATCH_FMT_ARGS:} {args:.}\",\n            \"lint-fix\": \"ruff check{env:HATCH_FMT_ARGS:} --fix {args:.}\",\n        },\n    }\n\n\nRUFF_DEFAULT_VERSION: str = \"0.13.2\"\n"
  },
  {
    "path": "src/hatch/env/internal/test.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\n\ndef get_default_config() -> dict[str, Any]:\n    return {\n        \"installer\": \"uv\",\n        \"dependencies\": [\n            \"coverage-enable-subprocess==1.0\",\n            \"coverage[toml]~=7.11\",\n            \"pytest~=9.0\",\n            \"pytest-mock~=3.12\",\n            \"pytest-randomly~=3.15\",\n            \"pytest-rerunfailures~=14.0\",\n            \"pytest-xdist[psutil]~=3.5\",\n        ],\n        \"scripts\": {\n            \"run\": \"pytest{env:HATCH_TEST_ARGS:} {args}\",\n            \"run-cov\": \"coverage run -m pytest{env:HATCH_TEST_ARGS:} {args}\",\n            \"cov-combine\": \"coverage combine\",\n            \"cov-report\": \"coverage report\",\n        },\n        \"matrix\": [{\"python\": [\"3.14\", \"3.14t\", \"3.13\", \"3.12\", \"3.11\", \"3.10\"]}],\n    }\n"
  },
  {
    "path": "src/hatch/env/internal/uv.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\n\ndef get_default_config() -> dict[str, Any]:\n    return {\n        \"skip-install\": True,\n        \"installer\": \"uv\",\n    }\n"
  },
  {
    "path": "src/hatch/env/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/env/plugin/hooks.py",
    "content": "from hatch.env.system import SystemEnvironment\nfrom hatch.env.virtual import VirtualEnvironment\nfrom hatchling.plugin import hookimpl\n\n\n@hookimpl\ndef hatch_register_environment():\n    return [SystemEnvironment, VirtualEnvironment]\n"
  },
  {
    "path": "src/hatch/env/plugin/interface.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\nfrom abc import ABC, abstractmethod\nfrom contextlib import contextmanager\nfrom functools import cached_property\nfrom os.path import isabs\nfrom typing import TYPE_CHECKING, Any\n\nfrom hatch.config.constants import AppEnvVars\nfrom hatch.env.utils import add_verbosity_flag, get_env_var_option\nfrom hatch.project.utils import format_script_commands, parse_script_command\nfrom hatch.utils.structures import EnvVars\n\nif TYPE_CHECKING:\n    from collections.abc import Generator, Iterable\n\n    from hatch.dep.core import Dependency\n    from hatch.project.core import Project\n    from hatch.utils.fs import Path\n\n\nclass EnvironmentInterface(ABC):\n    \"\"\"\n    Example usage:\n\n    ```python tab=\"plugin.py\"\n    from hatch.env.plugin.interface import EnvironmentInterface\n\n\n    class SpecialEnvironment(EnvironmentInterface):\n        PLUGIN_NAME = \"special\"\n        ...\n    ```\n\n    ```python tab=\"hooks.py\"\n    from hatchling.plugin import hookimpl\n\n    from .plugin import SpecialEnvironment\n\n\n    @hookimpl\n    def hatch_register_environment():\n        return SpecialEnvironment\n    ```\n    \"\"\"\n\n    PLUGIN_NAME = \"\"\n    \"\"\"The name used for selection.\"\"\"\n\n    def __init__(\n        self,\n        root,\n        metadata,\n        name,\n        config,\n        matrix_variables,\n        data_directory,\n        isolated_data_directory,\n        platform,\n        verbosity,\n        app,\n    ):\n        self.__root = root\n        self.__metadata = metadata\n        self.__name = name\n        self.__config = config\n        self.__matrix_variables = matrix_variables\n        self.__data_directory = data_directory\n        self.__isolated_data_directory = isolated_data_directory\n        self.__platform = platform\n        self.__verbosity = verbosity\n        self.__app = app\n\n        self.additional_dependencies = []\n\n    @property\n    def matrix_variables(self):\n        return self.__matrix_variables\n\n    @property\n    def app(self):\n        \"\"\"\n        An instance of [Application](../utilities.md#hatchling.bridge.app.Application).\n        \"\"\"\n        return self.__app\n\n    @cached_property\n    def context(self):\n        return self.get_context()\n\n    @property\n    def verbosity(self):\n        return self.__verbosity\n\n    @property\n    def root(self):\n        \"\"\"\n        The root of the local project tree as a path-like object.\n        \"\"\"\n        return self.__root\n\n    @property\n    def metadata(self):\n        return self.__metadata\n\n    @property\n    def name(self) -> str:\n        \"\"\"\n        The name of the environment.\n        \"\"\"\n        return self.__name\n\n    @property\n    def platform(self):\n        \"\"\"\n        An instance of [Platform](../utilities.md#hatch.utils.platform.Platform).\n        \"\"\"\n        return self.__platform\n\n    @property\n    def data_directory(self):\n        \"\"\"\n        The [directory](../../config/hatch.md#environments) this plugin should use for storage as a path-like object.\n        If the user has not configured one then this will be the same as the\n        [isolated data directory](reference.md#hatch.env.plugin.interface.EnvironmentInterface.isolated_data_directory).\n        \"\"\"\n        return self.__data_directory\n\n    @property\n    def isolated_data_directory(self):\n        \"\"\"\n        The default [directory](../../config/hatch.md#environments) reserved exclusively for this plugin as a path-like\n        object.\n        \"\"\"\n        return self.__isolated_data_directory\n\n    @property\n    def config(self) -> dict:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.envs.<ENV_NAME>]\n        ```\n        \"\"\"\n        return self.__config\n\n    @cached_property\n    def project_root(self) -> str:\n        \"\"\"\n        The root of the project tree as a string. If the environment is not running locally,\n        this should be the remote path to the project.\n        \"\"\"\n        return str(self.root)\n\n    @cached_property\n    def sep(self) -> str:\n        \"\"\"\n        The character used to separate directories in paths. By default, this is `\\\\` on Windows and `/` otherwise.\n        \"\"\"\n        return os.sep\n\n    @cached_property\n    def pathsep(self) -> str:\n        \"\"\"\n        The character used to separate paths. By default, this is `;` on Windows and `:` otherwise.\n        \"\"\"\n        return os.pathsep\n\n    @cached_property\n    def system_python(self) -> str:\n        system_python = os.environ.get(AppEnvVars.PYTHON)\n        if system_python == \"self\":\n            system_python = sys.executable\n\n        system_python = (\n            system_python\n            or self.platform.modules.shutil.which(\"python\")\n            or self.platform.modules.shutil.which(\"python3\")\n            or sys.executable\n        )\n        if not isabs(system_python):\n            system_python = self.platform.modules.shutil.which(system_python)\n\n        return system_python\n\n    @cached_property\n    def env_vars(self) -> dict[str, str]:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.envs.<ENV_NAME>.env-vars]\n        ```\n        \"\"\"\n        env_vars = self.config.get(\"env-vars\", {})\n        if not isinstance(env_vars, dict):\n            message = f\"Field `tool.hatch.envs.{self.name}.env-vars` must be a mapping\"\n            raise TypeError(message)\n\n        for key, value in env_vars.items():\n            if not isinstance(value, str):\n                message = (\n                    f\"Environment variable `{key}` of field `tool.hatch.envs.{self.name}.env-vars` must be a string\"\n                )\n                raise TypeError(message)\n\n        new_env_vars = {}\n        with self.metadata.context.apply_context(self.context):\n            for key, value in env_vars.items():\n                new_env_vars[key] = self.metadata.context.format(value)\n\n        new_env_vars[AppEnvVars.ENV_ACTIVE] = self.name\n        return new_env_vars\n\n    @cached_property\n    def env_include(self) -> list[str]:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.envs.<ENV_NAME>]\n        env-include = [...]\n        ```\n        \"\"\"\n        env_include = self.config.get(\"env-include\", [])\n        if not isinstance(env_include, list):\n            message = f\"Field `tool.hatch.envs.{self.name}.env-include` must be an array\"\n            raise TypeError(message)\n\n        for i, pattern in enumerate(env_include, 1):\n            if not isinstance(pattern, str):\n                message = f\"Pattern #{i} of field `tool.hatch.envs.{self.name}.env-include` must be a string\"\n                raise TypeError(message)\n\n        return [\"HATCH_BUILD_*\", *env_include] if env_include else env_include\n\n    @cached_property\n    def env_exclude(self) -> list[str]:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.envs.<ENV_NAME>]\n        env-exclude = [...]\n        ```\n        \"\"\"\n        env_exclude = self.config.get(\"env-exclude\", [])\n        if not isinstance(env_exclude, list):\n            message = f\"Field `tool.hatch.envs.{self.name}.env-exclude` must be an array\"\n            raise TypeError(message)\n\n        for i, pattern in enumerate(env_exclude, 1):\n            if not isinstance(pattern, str):\n                message = f\"Pattern #{i} of field `tool.hatch.envs.{self.name}.env-exclude` must be a string\"\n                raise TypeError(message)\n\n        return env_exclude\n\n    @cached_property\n    def environment_dependencies_complex(self) -> list[Dependency]:\n        from hatch.dep.core import Dependency, InvalidDependencyError\n\n        dependencies_complex: list[Dependency] = []\n        with self.apply_context():\n            for option in (\"dependencies\", \"extra-dependencies\"):\n                dependencies = self.config.get(option, [])\n                if not isinstance(dependencies, list):\n                    message = f\"Field `tool.hatch.envs.{self.name}.{option}` must be an array\"\n                    raise TypeError(message)\n\n                for i, entry in enumerate(dependencies, 1):\n                    if not isinstance(entry, str):\n                        message = f\"Dependency #{i} of field `tool.hatch.envs.{self.name}.{option}` must be a string\"\n                        raise TypeError(message)\n\n                    try:\n                        dependencies_complex.append(Dependency(self.metadata.context.format(entry)))\n                    except InvalidDependencyError as e:\n                        message = f\"Dependency #{i} of field `tool.hatch.envs.{self.name}.{option}` is invalid: {e}\"\n                        raise ValueError(message) from None\n\n        return dependencies_complex\n\n    @cached_property\n    def environment_dependencies(self) -> list[str]:\n        \"\"\"\n        The list of all [environment dependencies](../../config/environment/overview.md#dependencies).\n        \"\"\"\n        return [str(dependency) for dependency in self.environment_dependencies_complex]\n\n    @cached_property\n    def project_dependencies_complex(self) -> list[Dependency]:\n        workspace_dependencies = self.workspace.get_dependencies()\n        if self.skip_install and not self.features and not self.dependency_groups and not workspace_dependencies:\n            return []\n\n        from hatch.dep.core import Dependency\n        from hatch.utils.dep import get_complex_dependencies, get_complex_dependency_group, get_complex_features\n\n        all_dependencies_complex = list(map(Dependency, workspace_dependencies))\n        dependencies, optional_dependencies = self.app.project.get_dependencies()\n\n        # Format dependencies with context before creating Dependency objects\n        with self.apply_context():\n            formatted_dependencies = [self.metadata.context.format(dep) for dep in dependencies]\n            formatted_optional_dependencies = {\n                feature: [self.metadata.context.format(dep) for dep in deps]\n                for feature, deps in optional_dependencies.items()\n            }\n\n        dependencies_complex = get_complex_dependencies(formatted_dependencies)\n        optional_dependencies_complex = get_complex_features(formatted_optional_dependencies)\n\n        if not self.skip_install:\n            all_dependencies_complex.extend(dependencies_complex.values())\n\n        for feature in self.features:\n            if feature not in optional_dependencies_complex:\n                message = (\n                    f\"Feature `{feature}` of field `tool.hatch.envs.{self.name}.features` is not \"\n                    f\"defined in the dynamic field `project.optional-dependencies`\"\n                )\n                raise ValueError(message)\n\n            all_dependencies_complex.extend([\n                dep if isinstance(dep, Dependency) else Dependency(str(dep))\n                for dep in optional_dependencies_complex[feature]\n            ])\n\n        for dependency_group in self.dependency_groups:\n            all_dependencies_complex.extend(\n                get_complex_dependency_group(self.app.project.dependency_groups, dependency_group)\n            )\n\n        return all_dependencies_complex\n\n    @cached_property\n    def project_dependencies(self) -> list[str]:\n        \"\"\"\n        The list of all [project dependencies](../../config/metadata.md#dependencies) (if\n        [installed](../../config/environment/overview.md#skip-install)), selected\n        [optional dependencies](../../config/environment/overview.md#features), and\n        workspace dependencies.\n        \"\"\"\n        return [str(dependency) for dependency in self.project_dependencies_complex]\n\n    @cached_property\n    def local_dependencies_complex(self) -> list[Dependency]:\n        from hatch.dep.core import Dependency\n\n        local_dependencies_complex = []\n        if not self.skip_install:\n            local_dependencies_complex.append(\n                Dependency(f\"{self.metadata.name} @ {self.root.as_uri()}\", editable=self.dev_mode)\n            )\n        if self.workspace.members:\n            local_dependencies_complex.extend(\n                Dependency(f\"{member.project.metadata.name} @ {member.project.location.as_uri()}\", editable=True)\n                for member in self.workspace.members\n            )\n\n        return local_dependencies_complex\n\n    @cached_property\n    def dependencies_complex(self) -> list[Dependency]:\n        from hatch.dep.core import Dependency\n\n        all_dependencies_complex = list(self.environment_dependencies_complex)\n\n        # Convert additional_dependencies to Dependency objects\n        for dep in self.additional_dependencies:\n            if isinstance(dep, Dependency):\n                all_dependencies_complex.append(dep)\n            else:\n                all_dependencies_complex.append(Dependency(str(dep)))\n\n        if self.dependency_groups and not self.skip_install:\n            from hatch.utils.dep import get_complex_dependency_group\n\n            for dependency_group in self.dependency_groups:\n                all_dependencies_complex.extend(\n                    get_complex_dependency_group(self.app.project.dependency_groups, dependency_group)\n                )\n\n        if self.builder:\n            from hatch.project.constants import BuildEnvVars\n\n            # Convert build requirements to Dependency objects\n            for req in self.metadata.build.requires_complex:\n                if isinstance(req, Dependency):\n                    all_dependencies_complex.append(req)\n                else:\n                    all_dependencies_complex.append(Dependency(str(req)))\n\n            for target in os.environ.get(BuildEnvVars.REQUESTED_TARGETS, \"\").split():\n                target_config = self.app.project.config.build.target(target)\n                all_dependencies_complex.extend(map(Dependency, target_config.dependencies))\n\n            return all_dependencies_complex\n\n        # Ensure these are checked last to speed up initial environment creation since\n        # they will already be installed along with the project\n        if self.dev_mode or self.features or self.dependency_groups:\n            all_dependencies_complex.extend(self.project_dependencies_complex)\n\n        return all_dependencies_complex\n\n    @cached_property\n    def dependencies(self) -> list[str]:\n        \"\"\"\n        The list of all\n        [project dependencies](reference.md#hatch.env.plugin.interface.EnvironmentInterface.project_dependencies)\n        (if in [dev mode](../../config/environment/overview.md#dev-mode)) and\n        [environment dependencies](../../config/environment/overview.md#dependencies).\n        \"\"\"\n        return [str(dependency) for dependency in self.dependencies_complex]\n\n    @cached_property\n    def all_dependencies_complex(self) -> list[Dependency]:\n        from hatch.dep.core import Dependency\n\n        local_deps = list(self.local_dependencies_complex)\n        workspace_names = {dep.name.lower() for dep in local_deps}\n\n        filtered_deps: list[Dependency] = []\n        for dep in self.dependencies_complex:\n            dep_obj = dep if isinstance(dep, Dependency) else Dependency(str(dep))\n\n            if dep_obj.name.lower() in workspace_names and dep_obj.extras:\n                # Only expand if we have static optional dependencies to avoid recursion\n                if not self.metadata.hatch.metadata.hook_config:\n                    optional_dependencies = self.metadata.core.optional_dependencies\n                    for extra in dep_obj.extras:\n                        if extra in optional_dependencies:\n                            filtered_deps.extend(Dependency(d) for d in optional_dependencies[extra])\n            elif dep_obj.name.lower() not in workspace_names:\n                filtered_deps.append(dep_obj)\n\n        return local_deps + filtered_deps\n\n    @cached_property\n    def all_dependencies(self) -> list[str]:\n        return [str(dependency) for dependency in self.all_dependencies_complex]\n\n    @cached_property\n    def platforms(self) -> list[str]:\n        \"\"\"\n        All names are stored as their lower-cased version.\n\n        ```toml config-example\n        [tool.hatch.envs.<ENV_NAME>]\n        platforms = [...]\n        ```\n        \"\"\"\n        platforms = self.config.get(\"platforms\", [])\n        if not isinstance(platforms, list):\n            message = f\"Field `tool.hatch.envs.{self.name}.platforms` must be an array\"\n            raise TypeError(message)\n\n        for i, command in enumerate(platforms, 1):\n            if not isinstance(command, str):\n                message = f\"Platform #{i} of field `tool.hatch.envs.{self.name}.platforms` must be a string\"\n                raise TypeError(message)\n\n        return [platform.lower() for platform in platforms]\n\n    @cached_property\n    def skip_install(self) -> bool:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.envs.<ENV_NAME>]\n        skip-install = ...\n        ```\n        \"\"\"\n        skip_install = self.config.get(\"skip-install\", not self.metadata.has_project_file())\n        if not isinstance(skip_install, bool):\n            message = f\"Field `tool.hatch.envs.{self.name}.skip-install` must be a boolean\"\n            raise TypeError(message)\n\n        return skip_install\n\n    @cached_property\n    def dev_mode(self) -> bool:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.envs.<ENV_NAME>]\n        dev-mode = ...\n        ```\n        \"\"\"\n        dev_mode = self.config.get(\"dev-mode\", True)\n        if not isinstance(dev_mode, bool):\n            message = f\"Field `tool.hatch.envs.{self.name}.dev-mode` must be a boolean\"\n            raise TypeError(message)\n\n        return dev_mode\n\n    @cached_property\n    def builder(self) -> bool:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.envs.<ENV_NAME>]\n        builder = ...\n        ```\n        \"\"\"\n        builder = self.config.get(\"builder\", False)\n        if not isinstance(builder, bool):\n            message = f\"Field `tool.hatch.envs.{self.name}.builder` must be a boolean\"\n            raise TypeError(message)\n\n        return builder\n\n    @cached_property\n    def features(self):\n        from hatch.utils.metadata import normalize_project_name\n\n        features = self.config.get(\"features\", [])\n        if not isinstance(features, list):\n            message = f\"Field `tool.hatch.envs.{self.name}.features` must be an array of strings\"\n            raise TypeError(message)\n\n        all_features = set()\n        for i, feature in enumerate(features, 1):\n            if not isinstance(feature, str):\n                message = f\"Feature #{i} of field `tool.hatch.envs.{self.name}.features` must be a string\"\n                raise TypeError(message)\n\n            if not feature:\n                message = f\"Feature #{i} of field `tool.hatch.envs.{self.name}.features` cannot be an empty string\"\n                raise ValueError(message)\n\n            normalized_feature = (\n                feature if self.metadata.hatch.metadata.allow_ambiguous_features else normalize_project_name(feature)\n            )\n            if (\n                not self.metadata.hatch.metadata.hook_config\n                and normalized_feature not in self.metadata.core.optional_dependencies\n            ):\n                message = (\n                    f\"Feature `{normalized_feature}` of field `tool.hatch.envs.{self.name}.features` is not \"\n                    f\"defined in field `project.optional-dependencies`\"\n                )\n                raise ValueError(message)\n\n            all_features.add(normalized_feature)\n\n        return sorted(all_features)\n\n    @cached_property\n    def dependency_groups(self):\n        from hatch.utils.metadata import normalize_project_name\n\n        dependency_groups = self.config.get(\"dependency-groups\", [])\n        if not isinstance(dependency_groups, list):\n            message = f\"Field `tool.hatch.envs.{self.name}.dependency-groups` must be an array of strings\"\n            raise TypeError(message)\n\n        all_dependency_groups = set()\n        for i, dependency_group in enumerate(dependency_groups, 1):\n            if not isinstance(dependency_group, str):\n                message = (\n                    f\"Dependency Group #{i} of field `tool.hatch.envs.{self.name}.dependency-groups` must be a string\"\n                )\n                raise TypeError(message)\n\n            if not dependency_group:\n                message = f\"Dependency Group #{i} of field `tool.hatch.envs.{self.name}.dependency-groups` cannot be an empty string\"\n                raise ValueError(message)\n\n            normalized_dependency_group = normalize_project_name(dependency_group)\n            if (\n                not self.metadata.hatch.metadata.hook_config\n                and normalized_dependency_group not in self.app.project.dependency_groups\n            ):\n                message = (\n                    f\"Dependency Group `{normalized_dependency_group}` of field `tool.hatch.envs.{self.name}.dependency-groups` is not \"\n                    f\"defined in field `dependency-groups`\"\n                )\n                raise ValueError(message)\n\n            all_dependency_groups.add(normalized_dependency_group)\n\n        return sorted(all_dependency_groups)\n\n    @cached_property\n    def description(self) -> str:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.envs.<ENV_NAME>]\n        description = ...\n        ```\n        \"\"\"\n        description = self.config.get(\"description\", \"\")\n        if not isinstance(description, str):\n            message = f\"Field `tool.hatch.envs.{self.name}.description` must be a string\"\n            raise TypeError(message)\n\n        return description\n\n    @cached_property\n    def scripts(self):\n        config = {}\n\n        # Extra scripts should come first to give less precedence\n        for field in (\"extra-scripts\", \"scripts\"):\n            script_config = self.config.get(field, {})\n            if not isinstance(script_config, dict):\n                message = f\"Field `tool.hatch.envs.{self.name}.{field}` must be a table\"\n                raise TypeError(message)\n\n            for name, data in script_config.items():\n                if \" \" in name:\n                    message = (\n                        f\"Script name `{name}` in field `tool.hatch.envs.{self.name}.{field}` must not contain spaces\"\n                    )\n                    raise ValueError(message)\n\n                commands = []\n\n                if isinstance(data, str):\n                    commands.append(data)\n                elif isinstance(data, list):\n                    for i, command in enumerate(data, 1):\n                        if not isinstance(command, str):\n                            message = (\n                                f\"Command #{i} in field `tool.hatch.envs.{self.name}.{field}.{name}` must be a string\"\n                            )\n                            raise TypeError(message)\n\n                        commands.append(command)\n                else:\n                    message = (\n                        f\"Field `tool.hatch.envs.{self.name}.{field}.{name}` must be a string or an array of strings\"\n                    )\n                    raise TypeError(message)\n\n                config[name] = commands\n\n        seen = {}\n        active = []\n        for script_name, commands in config.items():\n            commands[:] = expand_script_commands(self.name, script_name, commands, config, seen, active)\n\n        return config\n\n    @cached_property\n    def pre_install_commands(self):\n        pre_install_commands = self.config.get(\"pre-install-commands\", [])\n        if not isinstance(pre_install_commands, list):\n            message = f\"Field `tool.hatch.envs.{self.name}.pre-install-commands` must be an array\"\n            raise TypeError(message)\n\n        for i, command in enumerate(pre_install_commands, 1):\n            if not isinstance(command, str):\n                message = f\"Command #{i} of field `tool.hatch.envs.{self.name}.pre-install-commands` must be a string\"\n                raise TypeError(message)\n\n        return list(pre_install_commands)\n\n    @cached_property\n    def post_install_commands(self):\n        post_install_commands = self.config.get(\"post-install-commands\", [])\n        if not isinstance(post_install_commands, list):\n            message = f\"Field `tool.hatch.envs.{self.name}.post-install-commands` must be an array\"\n            raise TypeError(message)\n\n        for i, command in enumerate(post_install_commands, 1):\n            if not isinstance(command, str):\n                message = f\"Command #{i} of field `tool.hatch.envs.{self.name}.post-install-commands` must be a string\"\n                raise TypeError(message)\n\n        return list(post_install_commands)\n\n    @cached_property\n    def workspace(self) -> Workspace:\n        env_config = self.config.get(\"workspace\", {})\n        if not isinstance(env_config, dict):\n            message = f\"Field `tool.hatch.envs.{self.name}.workspace` must be a table\"\n            raise TypeError(message)\n\n        return Workspace(self, env_config)\n\n    def activate(self):\n        \"\"\"\n        A convenience method called when using the environment as a context manager:\n\n        ```python\n        with environment:\n            ...\n        ```\n        \"\"\"\n\n    def deactivate(self):\n        \"\"\"\n        A convenience method called after using the environment as a context manager:\n\n        ```python\n        with environment:\n            ...\n        ```\n        \"\"\"\n\n    @abstractmethod\n    def find(self):\n        \"\"\"\n        :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right:\n\n        This should return information about how to locate the environment or represent its ID in\n        some way. Additionally, this is expected to return something even if the environment is\n        [incompatible](reference.md#hatch.env.plugin.interface.EnvironmentInterface.check_compatibility).\n        \"\"\"\n\n    @abstractmethod\n    def create(self):\n        \"\"\"\n        :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right:\n\n        This should perform the necessary steps to set up the environment.\n        \"\"\"\n\n    @abstractmethod\n    def remove(self):\n        \"\"\"\n        :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right:\n\n        This should perform the necessary steps to completely remove the environment from the system and will only\n        be triggered manually by users with the [`env remove`](../../cli/reference.md#hatch-env-remove) or\n        [`env prune`](../../cli/reference.md#hatch-env-prune) commands.\n        \"\"\"\n\n    @abstractmethod\n    def exists(self) -> bool:\n        \"\"\"\n        :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right:\n\n        This should indicate whether or not the environment has already been created.\n        \"\"\"\n\n    @abstractmethod\n    def install_project(self):\n        \"\"\"\n        :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right:\n\n        This should install the project in the environment.\n        \"\"\"\n\n    @abstractmethod\n    def install_project_dev_mode(self):\n        \"\"\"\n        :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right:\n\n        This should install the project in the environment such that the environment\n        always reflects the current state of the project.\n        \"\"\"\n\n    @abstractmethod\n    def dependencies_in_sync(self) -> bool:\n        \"\"\"\n        :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right:\n\n        This should indicate whether or not the environment is compatible with the current\n        [dependencies](reference.md#hatch.env.plugin.interface.EnvironmentInterface.dependencies).\n        \"\"\"\n\n    @abstractmethod\n    def sync_dependencies(self):\n        \"\"\"\n        :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right:\n\n        This should install the\n        [dependencies](reference.md#hatch.env.plugin.interface.EnvironmentInterface.dependencies)\n        in the environment.\n        \"\"\"\n\n    def dependency_hash(self):\n        \"\"\"\n        This should return a hash of the environment's\n        [dependencies](reference.md#hatch.env.plugin.interface.EnvironmentInterface.dependencies)\n        and any other data that is handled by the\n        [sync_dependencies](reference.md#hatch.env.plugin.interface.EnvironmentInterface.sync_dependencies)\n        and\n        [dependencies_in_sync](reference.md#hatch.env.plugin.interface.EnvironmentInterface.dependencies_in_sync)\n        methods.\n        \"\"\"\n        from hatch.utils.dep import hash_dependencies\n\n        return hash_dependencies(self.all_dependencies_complex)\n\n    @contextmanager\n    def app_status_creation(self):\n        \"\"\"\n        See the [life cycle of environments](reference.md#life-cycle).\n        \"\"\"\n        with self.app.status(f\"Creating environment: {self.name}\"):\n            yield\n\n    @contextmanager\n    def app_status_pre_installation(self):\n        \"\"\"\n        See the [life cycle of environments](reference.md#life-cycle).\n        \"\"\"\n        with self.app.status(\"Running pre-installation commands\"):\n            yield\n\n    @contextmanager\n    def app_status_post_installation(self):\n        \"\"\"\n        See the [life cycle of environments](reference.md#life-cycle).\n        \"\"\"\n        with self.app.status(\"Running post-installation commands\"):\n            yield\n\n    @contextmanager\n    def app_status_project_installation(self):\n        \"\"\"\n        See the [life cycle of environments](reference.md#life-cycle).\n        \"\"\"\n        if self.dev_mode:\n            with self.app.status(\"Installing project in development mode\"):\n                yield\n        else:\n            with self.app.status(\"Installing project\"):\n                yield\n\n    @contextmanager\n    def app_status_dependency_state_check(self):\n        \"\"\"\n        See the [life cycle of environments](reference.md#life-cycle).\n        \"\"\"\n        if not self.skip_install and (\n            \"dependencies\" in self.metadata.dynamic or \"optional-dependencies\" in self.metadata.dynamic\n        ):\n            with self.app.status(\"Polling dependency state\"):\n                yield\n        else:\n            yield\n\n    @contextmanager\n    def app_status_dependency_installation_check(self):\n        \"\"\"\n        See the [life cycle of environments](reference.md#life-cycle).\n        \"\"\"\n        with self.app.status(\"Checking dependencies\"):\n            yield\n\n    @contextmanager\n    def app_status_dependency_synchronization(self):\n        \"\"\"\n        See the [life cycle of environments](reference.md#life-cycle).\n        \"\"\"\n        with self.app.status(\"Syncing dependencies\"):\n            yield\n\n    @contextmanager\n    def fs_context(self) -> Generator[FileSystemContext, None, None]:\n        \"\"\"\n        A context manager that must yield a subclass of\n        [FileSystemContext](../utilities.md#hatch.env.plugin.interface.FileSystemContext).\n        \"\"\"\n        from hatch.utils.fs import temp_directory\n\n        with temp_directory() as temp_dir:\n            yield FileSystemContext(self, local_path=temp_dir, env_path=str(temp_dir))\n\n    def enter_shell(\n        self,\n        name: str,  # noqa: ARG002\n        path: str,\n        args: Iterable[str],\n    ):\n        \"\"\"\n        Spawn a [shell](../../config/hatch.md#shell) within the environment.\n\n        This should either use\n        [command_context](reference.md#hatch.env.plugin.interface.EnvironmentInterface.command_context)\n        directly or provide the same guarantee.\n        \"\"\"\n        with self.command_context():\n            self.platform.exit_with_command([path, *args])\n\n    def run_shell_command(self, command: str, **kwargs):\n        \"\"\"\n        This should return the standard library's\n        [subprocess.CompletedProcess](https://docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess)\n        and will always be called when the\n        [command_context](reference.md#hatch.env.plugin.interface.EnvironmentInterface.command_context)\n        is active, with the expectation of providing the same guarantee.\n        \"\"\"\n        kwargs.setdefault(\"shell\", True)\n        return self.platform.run_command(command, **kwargs)\n\n    @contextmanager\n    def command_context(self):\n        \"\"\"\n        A context manager that when active should make executed shell commands reflect any\n        [environment variables](reference.md#hatch.env.plugin.interface.EnvironmentInterface.get_env_vars)\n        the user defined either currently or at the time of\n        [creation](reference.md#hatch.env.plugin.interface.EnvironmentInterface.create).\n\n        For an example, open the default implementation below:\n        \"\"\"\n        with self.get_env_vars():\n            yield\n\n    def resolve_commands(self, commands: list[str]):\n        \"\"\"\n        This expands each command into one or more commands based on any\n        [scripts](../../config/environment/overview.md#scripts) that the user defined.\n        \"\"\"\n        for command in commands:\n            yield from self.expand_command(command)\n\n    def expand_command(self, command):\n        possible_script, args, _ignore_exit_code = parse_script_command(command)\n\n        # Indicate undefined\n        if not args:\n            args = None\n\n        with self.apply_context():\n            if possible_script in self.scripts:\n                if args is not None:\n                    args = self.metadata.context.format(args)\n\n                for cmd in self.scripts[possible_script]:\n                    yield self.metadata.context.format(cmd, args=args).strip()\n            else:\n                yield self.metadata.context.format(command, args=args).strip()\n\n    def construct_pip_install_command(self, args: list[str]):\n        \"\"\"\n        A convenience method for constructing a [`pip install`](https://pip.pypa.io/en/stable/cli/pip_install/)\n        command with the given verbosity. The default verbosity is set to one less than Hatch's verbosity.\n        \"\"\"\n        command = [\"python\", \"-u\", \"-m\", \"pip\", \"install\", \"--disable-pip-version-check\"]\n\n        # Default to -1 verbosity\n        add_verbosity_flag(command, self.verbosity, adjustment=-1)\n\n        command.extend(args)\n        return command\n\n    def join_command_args(self, args: list[str]):\n        \"\"\"\n        This is used by the [`run`](../../cli/reference.md#hatch-run) command to construct the root command string\n        from the received arguments.\n        \"\"\"\n        return self.platform.join_command_args(args)\n\n    def apply_features(self, requirement: str):\n        \"\"\"\n        A convenience method that applies any user defined [features](../../config/environment/overview.md#features)\n        to the given requirement.\n        \"\"\"\n        if self.features:\n            features = \",\".join(self.features)\n            return f\"{requirement}[{features}]\"\n\n        return requirement\n\n    def check_compatibility(self):\n        \"\"\"\n        This raises an exception if the environment is not compatible with the user's setup. The default behavior\n        checks for [platform compatibility](../../config/environment/overview.md#supported-platforms)\n        and any method override should keep this check.\n\n        This check is never performed if the environment has been\n        [created](reference.md#hatch.env.plugin.interface.EnvironmentInterface.create).\n        \"\"\"\n        if self.platforms and self.platform.name not in self.platforms:\n            message = \"unsupported platform\"\n            raise OSError(message)\n\n    def get_env_vars(self) -> EnvVars:\n        \"\"\"\n        Returns a mapping of environment variables that should be available to the environment. The object can\n        be used as a context manager to temporarily apply the environment variables to the current process.\n\n        !!! note\n            The environment variable `HATCH_ENV_ACTIVE` will always be set to the name of the environment.\n        \"\"\"\n        return EnvVars(self.env_vars, self.env_include, self.env_exclude)\n\n    def get_env_var_option(self, option: str) -> str:\n        \"\"\"\n        Returns the value of the upper-cased environment variable `HATCH_ENV_TYPE_<PLUGIN_NAME>_<option>`.\n        \"\"\"\n        return get_env_var_option(plugin_name=self.PLUGIN_NAME, option=option)\n\n    def get_context(self):\n        \"\"\"\n        Returns a subclass of\n        [EnvironmentContextFormatter](../utilities.md#hatch.env.context.EnvironmentContextFormatter).\n        \"\"\"\n        from hatch.env.context import EnvironmentContextFormatter\n\n        return EnvironmentContextFormatter(self)\n\n    @staticmethod\n    def get_option_types() -> dict:\n        \"\"\"\n        Returns a mapping of supported options to their respective types so that they can be used by\n        [overrides](../../config/environment/advanced.md#option-overrides).\n        \"\"\"\n        return {}\n\n    @contextmanager\n    def apply_context(self):\n        with self.get_env_vars(), self.metadata.context.apply_context(self.context):\n            yield\n\n    def __enter__(self):\n        self.activate()\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        self.deactivate()\n\n\nclass FileSystemContext:\n    \"\"\"\n    This class represents a synchronized path between the local file system and a potentially remote environment.\n    \"\"\"\n\n    def __init__(self, env: EnvironmentInterface, *, local_path: Path, env_path: str):\n        self.__env = env\n        self.__local_path = local_path\n        self.__env_path = env_path\n\n    @property\n    def env(self) -> EnvironmentInterface:\n        \"\"\"\n        Returns the environment to which this context belongs.\n        \"\"\"\n        return self.__env\n\n    @property\n    def local_path(self) -> Path:\n        \"\"\"\n        Returns the local path to which this context refers as a path-like object.\n        \"\"\"\n        return self.__local_path\n\n    @property\n    def env_path(self) -> str:\n        \"\"\"\n        Returns the environment path to which this context refers as a string. The environment\n        may not be on the local file system.\n        \"\"\"\n        return self.__env_path\n\n    def join(self, relative_path: str) -> FileSystemContext:\n        \"\"\"\n        Returns a new instance of this class with the given relative path appended to the local and\n        environment paths.\n\n        This method should not need overwriting.\n        \"\"\"\n        local_path = self.local_path / relative_path\n        env_path = f\"{self.env_path}{self.__env.sep.join(['', *os.path.normpath(relative_path).split(os.sep)])}\"\n        return FileSystemContext(self.__env, local_path=local_path, env_path=env_path)\n\n    def sync_env(self):\n        \"\"\"\n        Synchronizes the [environment path](utilities.md#hatch.env.plugin.interface.FileSystemContext.env_path)\n        with the [local path](utilities.md#hatch.env.plugin.interface.FileSystemContext.local_path) as the source.\n        \"\"\"\n\n    def sync_local(self):\n        \"\"\"\n        Synchronizes the [local path](utilities.md#hatch.env.plugin.interface.FileSystemContext.local_path) as the\n        source with the [environment path](utilities.md#hatch.env.plugin.interface.FileSystemContext.env_path) as\n        the source.\n        \"\"\"\n\n\nclass Workspace:\n    def __init__(self, env: EnvironmentInterface, config: dict[str, Any]):\n        self.env = env\n        self.config = config\n\n    @cached_property\n    def parallel(self) -> bool:\n        parallel = self.config.get(\"parallel\", True)\n        if not isinstance(parallel, bool):\n            message = f\"Field `tool.hatch.envs.{self.env.name}.workspace.parallel` must be a boolean\"\n            raise TypeError(message)\n\n        return parallel\n\n    def get_dependencies(self) -> list[str]:\n        static_members: list[WorkspaceMember] = []\n        dynamic_members: list[WorkspaceMember] = []\n        for member in self.members:\n            if member.has_static_dependencies:\n                static_members.append(member)\n            else:\n                dynamic_members.append(member)\n\n        all_dependencies = []\n        for member in static_members:\n            dependencies, features = member.get_dependencies()\n            all_dependencies.extend(dependencies)\n            for feature in member.features:\n                all_dependencies.extend(features.get(feature, []))\n\n        if self.parallel:\n            from concurrent.futures import ThreadPoolExecutor\n\n            def get_member_deps(member):\n                with self.env.app.status(f\"Checking workspace member: {member.name}\"):\n                    dependencies, features = member.get_dependencies()\n                    deps = list(dependencies)\n                    for feature in member.features:\n                        deps.extend(features.get(feature, []))\n                    return deps\n\n            with ThreadPoolExecutor() as executor:\n                results = executor.map(get_member_deps, dynamic_members)\n                for deps in results:\n                    all_dependencies.extend(deps)\n        else:\n            for member in dynamic_members:\n                with self.env.app.status(f\"Checking workspace member: {member.name}\"):\n                    dependencies, features = member.get_dependencies()\n                    all_dependencies.extend(dependencies)\n                    for feature in member.features:\n                        all_dependencies.extend(features.get(feature, []))\n\n        return all_dependencies\n\n    @cached_property\n    def members(self) -> list[WorkspaceMember]:\n        import fnmatch\n\n        from hatch.project.core import Project\n        from hatch.utils.fs import Path\n        from hatch.utils.metadata import normalize_project_name\n\n        raw_members = self.config.get(\"members\", [])\n        if not isinstance(raw_members, list):\n            message = f\"Field `tool.hatch.envs.{self.env.name}.workspace.members` must be an array\"\n            raise TypeError(message)\n\n        # Get exclude patterns\n        exclude_patterns = self.config.get(\"exclude\", [])\n        if not isinstance(exclude_patterns, list):\n            message = f\"Field `tool.hatch.envs.{self.env.name}.workspace.exclude` must be an array\"\n            raise TypeError(message)\n\n        # First normalize configuration with context expansion\n        member_data: list[dict[str, Any]] = []\n        with self.env.apply_context():\n            for i, data in enumerate(raw_members, 1):\n                if isinstance(data, str):\n                    expanded_path = self.env.metadata.context.format(data)\n                    member_data.append({\"path\": expanded_path, \"features\": ()})\n                elif isinstance(data, dict):\n                    if \"path\" not in data:\n                        message = (\n                            f\"Member #{i} of field `tool.hatch.envs.{self.env.name}.workspace.members` must define \"\n                            f\"a `path` key\"\n                        )\n                        raise TypeError(message)\n\n                    path = data[\"path\"]\n                    if not isinstance(path, str):\n                        message = (\n                            f\"Option `path` of member #{i} of field `tool.hatch.envs.{self.env.name}.workspace.members` \"\n                            f\"must be a string\"\n                        )\n                        raise TypeError(message)\n\n                    if not path:\n                        message = (\n                            f\"Option `path` of member #{i} of field `tool.hatch.envs.{self.env.name}.workspace.members` \"\n                            f\"cannot be an empty string\"\n                        )\n                        raise ValueError(message)\n\n                    expanded_path = self.env.metadata.context.format(path)\n\n                    features = data.get(\"features\", [])\n                    if not isinstance(features, list):\n                        message = (\n                            f\"Option `features` of member #{i} of field `tool.hatch.envs.{self.env.name}.workspace.\"\n                            f\"members` must be an array of strings\"\n                        )\n                        raise TypeError(message)\n\n                    all_features: set[str] = set()\n                    for j, feature in enumerate(features, 1):\n                        if not isinstance(feature, str):\n                            message = (\n                                f\"Feature #{j} of option `features` of member #{i} of field \"\n                                f\"`tool.hatch.envs.{self.env.name}.workspace.members` must be a string\"\n                            )\n                            raise TypeError(message)\n\n                        if not feature:\n                            message = (\n                                f\"Feature #{j} of option `features` of member #{i} of field \"\n                                f\"`tool.hatch.envs.{self.env.name}.workspace.members` cannot be an empty string\"\n                            )\n                            raise ValueError(message)\n\n                        normalized_feature = normalize_project_name(feature)\n                        if normalized_feature in all_features:\n                            message = (\n                                f\"Feature #{j} of option `features` of member #{i} of field \"\n                                f\"`tool.hatch.envs.{self.env.name}.workspace.members` is a duplicate\"\n                            )\n                            raise ValueError(message)\n\n                        all_features.add(normalized_feature)\n\n                    member_data.append({\"path\": expanded_path, \"features\": tuple(sorted(all_features))})\n                else:\n                    message = (\n                        f\"Member #{i} of field `tool.hatch.envs.{self.env.name}.workspace.members` must be \"\n                        f\"a string or an inline table\"\n                    )\n                    raise TypeError(message)\n\n        root = str(self.env.root)\n        member_paths: dict[str, WorkspaceMember] = {}\n        for data in member_data:\n            # Given root R and member spec M, we need to find:\n            #\n            # 1. The absolute path AP of R/M\n            # 2. The shared prefix SP of R and AP\n            # 3. The relative path RP of M from AP\n            #\n            # For example, if:\n            #\n            # R = /foo/bar/baz\n            # M = ../dir/pkg-*\n            #\n            # Then:\n            #\n            # AP = /foo/bar/dir/pkg-*\n            # SP = /foo/bar\n            # RP = dir/pkg-*\n            path_spec = data[\"path\"]\n            normalized_path = os.path.normpath(os.path.join(root, path_spec))\n            absolute_path = os.path.abspath(normalized_path)\n            shared_prefix = os.path.commonpath([root, absolute_path])\n            relative_path = os.path.relpath(absolute_path, shared_prefix)\n\n            # Now we have the necessary information to perform an optimized glob search for members\n            members_found = False\n            for member_path in find_members(shared_prefix, relative_path.split(os.sep)):\n                # Check if member should be excluded\n                relative_member_path = os.path.relpath(member_path, shared_prefix)\n                should_exclude = False\n                for exclude_pattern in exclude_patterns:\n                    if fnmatch.fnmatch(relative_member_path, exclude_pattern) or fnmatch.fnmatch(\n                        member_path, exclude_pattern\n                    ):\n                        should_exclude = True\n                        break\n\n                if should_exclude:\n                    continue\n\n                project_file = os.path.join(member_path, \"pyproject.toml\")\n                if not os.path.isfile(project_file):\n                    message = (\n                        f\"Member derived from `{path_spec}` of field \"\n                        f\"`tool.hatch.envs.{self.env.name}.workspace.members` is not a project (no `pyproject.toml` \"\n                        f\"file): {member_path}\"\n                    )\n                    raise OSError(message)\n\n                members_found = True\n                if member_path in member_paths:\n                    message = (\n                        f\"Member derived from `{path_spec}` of field \"\n                        f\"`tool.hatch.envs.{self.env.name}.workspace.members` is a duplicate: {member_path}\"\n                    )\n                    raise ValueError(message)\n\n                project = Project(Path(member_path), locate=False)\n                project.set_app(self.env.app)\n                member_paths[member_path] = WorkspaceMember(project, features=data[\"features\"])\n\n            if not members_found:\n                message = (\n                    f\"No members could be derived from `{path_spec}` of field \"\n                    f\"`tool.hatch.envs.{self.env.name}.workspace.members`: {absolute_path}\"\n                )\n                raise OSError(message)\n\n        return list(member_paths.values())\n\n\nclass WorkspaceMember:\n    def __init__(self, project: Project, *, features: tuple[str]):\n        self.project = project\n        self.features = features\n        self._last_modified: float\n\n    @cached_property\n    def name(self) -> str:\n        return self.project.metadata.name\n\n    @cached_property\n    def has_static_dependencies(self) -> bool:\n        return self.project.has_static_dependencies\n\n    def get_dependencies(self) -> tuple[list[str], dict[str, list[str]]]:\n        return self.project.get_dependencies()\n\n    @property\n    def last_modified(self) -> float:\n        \"\"\"Get the last modification time of the member's pyproject.toml.\"\"\"\n        import os\n\n        pyproject_path = self.project.location / \"pyproject.toml\"\n        if pyproject_path.exists():\n            return os.path.getmtime(pyproject_path)\n        return 0.0\n\n    def get_editable_requirement(self, *, editable: bool = True) -> str:\n        \"\"\"Get the requirement string for this workspace member.\"\"\"\n        uri = self.project.location.as_uri()\n        if editable:\n            return f\"-e {self.name} @ {uri}\"\n        return f\"{self.name} @ {uri}\"\n\n\ndef expand_script_commands(env_name, script_name, commands, config, seen, active):\n    if script_name in seen:\n        return seen[script_name]\n\n    if script_name in active:\n        active.append(script_name)\n\n        message = f\"Circular expansion detected for field `tool.hatch.envs.{env_name}.scripts`: {' -> '.join(active)}\"\n        raise ValueError(message)\n\n    active.append(script_name)\n\n    expanded_commands = []\n\n    for command in commands:\n        possible_script, args, ignore_exit_code = parse_script_command(command)\n\n        if possible_script in config:\n            expanded_commands.extend(\n                format_script_commands(\n                    commands=expand_script_commands(\n                        env_name, possible_script, config[possible_script], config, seen, active\n                    ),\n                    args=args,\n                    ignore_exit_code=ignore_exit_code,\n                )\n            )\n        else:\n            expanded_commands.append(command)\n\n    seen[script_name] = expanded_commands\n    active.pop()\n\n    return expanded_commands\n\n\ndef find_members(root, relative_components):\n    import fnmatch\n    import re\n\n    component_matchers = []\n    for component in relative_components:\n        if any(special in component for special in \"*?[\"):\n            pattern = re.compile(fnmatch.translate(component))\n            component_matchers.append(lambda entry, pattern=pattern: pattern.search(entry.name))\n        else:\n            component_matchers.append(lambda entry, component=component: component == entry.name)\n\n    results = list(_recurse_members(root, 0, component_matchers))\n    yield from sorted(results, key=os.path.basename)\n\n\ndef _recurse_members(root, matcher_index, matchers):\n    if matcher_index == len(matchers):\n        yield root\n        return\n\n    matcher = matchers[matcher_index]\n    with os.scandir(root) as it:\n        for entry in it:\n            if entry.is_dir() and matcher(entry):\n                yield from _recurse_members(entry.path, matcher_index + 1, matchers)\n"
  },
  {
    "path": "src/hatch/env/system.py",
    "content": "import os\n\nfrom hatch.env.plugin.interface import EnvironmentInterface\nfrom hatch.utils.env import PythonInfo\n\n\nclass SystemEnvironment(EnvironmentInterface):\n    PLUGIN_NAME = \"system\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self.python_info = PythonInfo(self.platform)\n        self.install_indicator = self.data_directory / str(self.root).encode(\"utf-8\").hex()\n\n    def find(self):\n        return os.path.dirname(os.path.dirname(self.system_python))\n\n    def create(self):\n        self.install_indicator.touch()\n\n    def remove(self):\n        self.install_indicator.remove()\n\n    def exists(self):\n        return self.install_indicator.is_file()\n\n    def install_project(self):\n        self.platform.check_command(self.construct_pip_install_command([self.apply_features(str(self.root))]))\n\n    def install_project_dev_mode(self):\n        self.platform.check_command(\n            self.construct_pip_install_command([\"--editable\", self.apply_features(str(self.root))])\n        )\n\n    def dependencies_in_sync(self):\n        if not self.dependencies:\n            return True\n\n        from hatch.dep.sync import InstalledDistributions\n\n        distributions = InstalledDistributions(\n            sys_path=self.python_info.sys_path, environment=self.python_info.environment\n        )\n        return distributions.dependencies_in_sync(self.dependencies_complex)\n\n    def sync_dependencies(self):\n        self.platform.check_command(self.construct_pip_install_command(self.dependencies))\n"
  },
  {
    "path": "src/hatch/env/utils.py",
    "content": "from __future__ import annotations\n\nimport os\n\nfrom hatch.config.constants import AppEnvVars\n\n\ndef get_env_var(*, plugin_name: str, option: str) -> str:\n    return f\"{AppEnvVars.ENV_OPTION_PREFIX}{plugin_name}_{option.replace('-', '_')}\".upper()\n\n\ndef get_env_var_option(*, plugin_name: str, option: str, default: str = \"\") -> str:\n    return os.environ.get(get_env_var(plugin_name=plugin_name, option=option), default)\n\n\ndef ensure_valid_environment(env_config: dict):\n    env_config.setdefault(\"type\", \"virtual\")\n\n    workspace = env_config.get(\"workspace\")\n    if workspace is not None:\n        if not isinstance(workspace, dict):\n            msg = \"Field workspace must be a table\"\n            raise TypeError(msg)\n\n        members = workspace.get(\"members\", [])\n        if not isinstance(members, list):\n            msg = \"Field workspace.members must be an array\"\n            raise TypeError(msg)\n\n        # Validate each member\n        for i, member in enumerate(members, 1):\n            if isinstance(member, str):\n                continue\n            if isinstance(member, dict):\n                path = member.get(\"path\")\n                if path is None:\n                    msg = f\"Member #{i} must define a `path` key\"\n                    raise TypeError(msg)\n                if not isinstance(path, str):\n                    msg = f\"Member #{i} path must be a string\"\n                    raise TypeError(msg)\n            else:\n                msg = f\"Member #{i} must be a string or table\"\n                raise TypeError(msg)\n\n        exclude = workspace.get(\"exclude\", [])\n        if not isinstance(exclude, list):\n            msg = \"Field workspace.exclude must be an array\"\n            raise TypeError(msg)\n\n\ndef get_verbosity_flag(verbosity: int, *, adjustment=0) -> str:\n    verbosity += adjustment\n    if not verbosity:\n        return \"\"\n\n    if verbosity > 0:\n        return f\"-{'v' * abs(min(verbosity, 3))}\"\n\n    return f\"-{'q' * abs(max(verbosity, -3))}\"\n\n\ndef add_verbosity_flag(command: list[str], verbosity: int, *, adjustment=0):\n    flag = get_verbosity_flag(verbosity, adjustment=adjustment)\n    if flag:\n        command.append(flag)\n"
  },
  {
    "path": "src/hatch/env/virtual.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\nimport sysconfig\nfrom contextlib import contextmanager, nullcontext, suppress\nfrom functools import cached_property\nfrom os.path import isabs\nfrom typing import TYPE_CHECKING\n\nfrom hatch.config.constants import AppEnvVars\nfrom hatch.env.plugin.interface import EnvironmentInterface\nfrom hatch.env.utils import add_verbosity_flag\nfrom hatch.utils.fs import Path\nfrom hatch.utils.shells import ShellManager\nfrom hatch.utils.structures import EnvVars\nfrom hatch.venv.core import UVVirtualEnv, VirtualEnv\n\nFREETHREADED_BUILD = bool(sysconfig.get_config_var(\"Py_GIL_DISABLED\"))\n\nif TYPE_CHECKING:\n    from collections.abc import Callable, Iterable\n\n    from packaging.specifiers import SpecifierSet\n    from python_discovery import PythonInfo\n\n    from hatch.dep.core import Dependency\n    from hatch.dep.sync import InstalledDistributions\n    from hatch.python.core import PythonManager\n\n\nclass VirtualEnvironment(EnvironmentInterface):\n    PLUGIN_NAME = \"virtual\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        project_id = self.root.id\n        project_is_script = self.root.is_file()\n        project_name = (\n            project_id\n            if project_is_script\n            else self.metadata.name\n            if \"project\" in self.metadata.config\n            else f\"{project_id}-unmanaged\"\n        )\n        venv_name = project_name if self.name == \"default\" else self.name\n\n        # Conditions requiring a flat structure for build env\n        if (\n            self.isolated_data_directory == self.platform.home / \".virtualenvs\"\n            or self.root in self.isolated_data_directory.resolve().parents\n        ):\n            app_virtual_env_path = self.isolated_data_directory / venv_name\n        else:\n            app_virtual_env_path = self.isolated_data_directory / project_name / project_id / venv_name\n\n        # Explicit path\n        chosen_directory = self.get_env_var_option(\"path\") or self.config.get(\"path\", \"\")\n        if chosen_directory:\n            self.storage_path = self.data_directory / project_name / project_id\n            self.virtual_env_path = (\n                Path(chosen_directory) if isabs(chosen_directory) else (self.root / chosen_directory).resolve()\n            )\n        elif project_is_script:\n            self.storage_path = self.virtual_env_path = self.isolated_data_directory / venv_name\n        # Conditions requiring a flat structure\n        elif (\n            self.data_directory == self.platform.home / \".virtualenvs\"\n            or self.root in self.data_directory.resolve().parents\n        ):\n            self.storage_path = self.data_directory\n            self.virtual_env_path = self.storage_path / venv_name\n        # Otherwise the defined app path\n        else:\n            self.storage_path = self.data_directory / project_name / project_id\n            self.virtual_env_path = self.storage_path / venv_name\n\n        self.virtual_env = self.virtual_env_cls(self.virtual_env_path, self.platform, self.verbosity)\n        self.build_virtual_env = self.virtual_env_cls(\n            app_virtual_env_path.parent / f\"{app_virtual_env_path.name}-build\", self.platform, self.verbosity\n        )\n        self.shells = ShellManager(self)\n\n        self._parent_python = None\n\n    @cached_property\n    def use_uv(self) -> bool:\n        return self.installer == \"uv\" or bool(self.explicit_uv_path)\n\n    @cached_property\n    def installer(self) -> str:\n        return self.config.get(\"installer\", \"pip\")\n\n    @cached_property\n    def explicit_uv_path(self) -> str:\n        return self.get_env_var_option(\"uv_path\") or self.config.get(\"uv-path\", \"\")\n\n    @cached_property\n    def virtual_env_cls(self) -> type[VirtualEnv]:\n        return UVVirtualEnv if self.use_uv else VirtualEnv\n\n    def expose_uv(self):\n        if not (self.use_uv or self.uv_path):\n            return nullcontext()\n\n        return EnvVars({\"HATCH_UV\": self.uv_path})\n\n    @cached_property\n    def uv_path(self) -> str:\n        if self.explicit_uv_path:\n            return self.explicit_uv_path\n\n        from hatch.env.internal import is_default_environment\n\n        env_name = \"hatch-uv\"\n        if not (\n            # Prevent recursive loop\n            self.name == env_name\n            # Only if dependencies have been set by the user\n            or is_default_environment(env_name, self.app.project.config.internal_envs[env_name])\n        ):\n            uv_env = self.app.project.get_environment(env_name)\n            self.app.project.prepare_environment(uv_env, keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)))\n            with uv_env:\n                return self.platform.modules.shutil.which(\"uv\")\n\n        import sysconfig\n\n        scripts_dir = sysconfig.get_path(\"scripts\")\n        old_path = os.environ.get(\"PATH\", os.defpath)\n        new_path = f\"{scripts_dir}{os.pathsep}{old_path}\"\n        return self.platform.modules.shutil.which(\"uv\", path=new_path)\n\n    @cached_property\n    def distributions(self) -> InstalledDistributions:\n        from hatch.dep.sync import InstalledDistributions\n\n        return InstalledDistributions(sys_path=self.virtual_env.sys_path, environment=self.virtual_env.environment)\n\n    @cached_property\n    def missing_dependencies(self) -> list[Dependency]:\n        return self.distributions.missing_dependencies(self.all_dependencies_complex)\n\n    @staticmethod\n    def get_option_types() -> dict:\n        return {\"system-packages\": bool, \"path\": str, \"python-sources\": list, \"installer\": str, \"uv-path\": str}\n\n    def activate(self):\n        self.virtual_env.activate()\n\n    def deactivate(self):\n        self.virtual_env.deactivate()\n\n    def find(self):\n        return self.virtual_env_path\n\n    def create(self):\n        if self.root in self.storage_path.parents:\n            # Although it would be nice to support Mercurial, only Git supports multiple ignore files. See:\n            # https://github.com/pytest-dev/pytest/issues/3286#issuecomment-421439197\n            vcs_ignore_file = self.storage_path / \".gitignore\"\n            if not vcs_ignore_file.is_file():\n                vcs_ignore_file.ensure_parent_dir_exists()\n                vcs_ignore_file.write_text(\n                    \"\"\"\\\n# This file was automatically created by Hatch\n*\n\"\"\"\n                )\n\n        with self.expose_uv():\n            self.virtual_env.create(self.parent_python, allow_system_packages=self.config.get(\"system-packages\", False))\n\n    def remove(self):\n        self.virtual_env.remove()\n        self.build_virtual_env.remove()\n\n        # Clean up root directory of all virtual environments belonging to the project\n        if self.storage_path != self.platform.home / \".virtualenvs\" and self.storage_path.is_dir():\n            entries = [entry.name for entry in self.storage_path.iterdir()]\n            if not entries or (entries == [\".gitignore\"] and self.root in self.storage_path.parents):\n                self.storage_path.remove()\n\n    def exists(self):\n        return self.virtual_env.exists()\n\n    def install_project(self):\n        with self.safe_activation():\n            self.platform.check_command(self.construct_pip_install_command([self.apply_features(str(self.root))]))\n\n    def install_project_dev_mode(self):\n        with self.safe_activation():\n            self.platform.check_command(\n                self.construct_pip_install_command([\"--editable\", self.apply_features(str(self.root))])\n            )\n\n    def dependencies_in_sync(self):\n        with self.safe_activation():\n            return not self.missing_dependencies\n\n    def sync_dependencies(self):\n        with self.safe_activation():\n            # If we do not have missing dependencies we should not sync\n            if not self.missing_dependencies:\n                return\n\n            all_install_args = []\n\n            workspace_deps = [dep for dep in self.local_dependencies_complex if dep.path]\n            workspace_names = {dep.name.lower() for dep in self.local_dependencies_complex if dep.path}\n\n            for dep in workspace_deps:\n                if dep.editable:\n                    all_install_args.extend([\"--editable\", dep.path])\n                else:\n                    all_install_args.append(dep.path)\n\n            standard_dependencies = []\n\n            user_editable_dependencies = []\n\n            for dependency in self.missing_dependencies:\n                # Skip if already handled as workspace dependency\n                if dependency.name.lower() in workspace_names:\n                    continue\n\n                if dependency.editable and dependency.path is not None:\n                    user_editable_dependencies.append(str(dependency.path))\n                else:\n                    standard_dependencies.append(str(dependency))\n\n            all_install_args.extend(standard_dependencies)\n\n            for dep_path in user_editable_dependencies:\n                all_install_args.extend([\"--editable\", dep_path])\n\n            if all_install_args:\n                self.platform.check_command(self.construct_pip_install_command(all_install_args))\n\n    @contextmanager\n    def command_context(self):\n        with self.safe_activation():\n            yield\n\n    def construct_pip_install_command(self, args: list[str]):\n        if not self.use_uv:\n            return super().construct_pip_install_command(args)\n\n        command = [self.uv_path, \"pip\", \"install\"]\n\n        # Default to -1 verbosity\n        add_verbosity_flag(command, self.verbosity, adjustment=-1)\n\n        command.extend(args)\n        return command\n\n    def enter_shell(self, name: str, path: str, args: Iterable[str]):\n        shell_executor = getattr(self.shells, f\"enter_{name}\", None)\n        if shell_executor is None:\n            # Manually activate in lieu of an activation script\n            with self.safe_activation():\n                self.platform.exit_with_command([path, *args])\n        else:\n            with self.expose_uv(), self.get_env_vars():\n                shell_executor(path, args, self.virtual_env.executables_directory)\n\n    def check_compatibility(self):\n        super().check_compatibility()\n\n        python_version = self.config.get(\"python\", \"\")\n        if (\n            os.environ.get(AppEnvVars.PYTHON)\n            or self._find_existing_interpreter(python_version) is not None\n            or self._get_available_distribution(python_version) is not None\n        ):\n            return\n\n        message = (\n            f\"cannot locate Python: {python_version}\"\n            if python_version\n            else \"no compatible Python distribution available\"\n        )\n        raise OSError(message)\n\n    @cached_property\n    def _preferred_python_version(self):\n        version = f\"{sys.version_info.major}.{sys.version_info.minor}\"\n\n        if FREETHREADED_BUILD:\n            version += \"t\"\n\n        return version\n\n    @cached_property\n    def parent_python(self):\n        if python_choice := self.config.get(\"python\", \"\"):\n            return self._get_concrete_interpreter_path(python_choice)\n\n        if explicit_default := os.environ.get(AppEnvVars.PYTHON):\n            return sys.executable if explicit_default == \"self\" else explicit_default\n\n        return self._get_concrete_interpreter_path()\n\n    @cached_property\n    def python_manager(self) -> PythonManager:\n        from hatch.python.core import PythonManager\n\n        return PythonManager(self.isolated_data_directory / \".pythons\")\n\n    def get_interpreter_resolver_env(self) -> dict[str, str]:\n        env = dict(os.environ)\n        python_dirs = [str(dist.python_path.parent) for dist in self.python_manager.get_installed().values()]\n        if not python_dirs:\n            return env\n\n        internal_path = os.pathsep.join(python_dirs)\n        old_path = env.pop(\"PATH\", None)\n        env[\"PATH\"] = internal_path if old_path is None else f\"{old_path}{os.pathsep}{internal_path}\"\n\n        return env\n\n    def upgrade_possible_internal_python(self, python_path: str) -> None:\n        if \"internal\" not in self._python_sources:\n            return\n\n        for dist in self.python_manager.get_installed().values():\n            if dist.python_path == Path(python_path):\n                if dist.needs_update():\n                    with self.app.status(f\"Updating Python distribution: {dist.name}\"):\n                        self.python_manager.install(dist.name)\n\n                break\n\n    def _interpreter_is_compatible(self, interpreter: PythonInfo) -> bool:\n        return (\n            interpreter.executable is not None\n            and self._is_stable_path(interpreter.executable)\n            and (self.skip_install or self._python_constraint.contains(interpreter.version_str))\n        )\n\n    def _get_concrete_interpreter_path(self, python_version: str = \"\") -> str | None:\n        known_resolvers = self._python_resolvers()\n        resolvers = [known_resolvers[source] for source in self._python_sources]\n        if python_version:\n            for resolver in resolvers:\n                if (concrete_path := resolver(python_version)) is not None:\n                    return concrete_path\n        else:\n            # Prefer the Python version Hatch is currently using\n            for resolver in resolvers:\n                if (concrete_path := resolver(self._preferred_python_version)) is not None:\n                    return concrete_path\n\n            # Fallback to whatever is compatible\n            for resolver in resolvers:\n                if (concrete_path := resolver(\"\")) is not None:\n                    return concrete_path\n\n        return None\n\n    def _resolve_external_interpreter_path(self, python_version: str) -> str | None:\n        if (existing_path := self._find_existing_interpreter(python_version)) is not None:\n            self.upgrade_possible_internal_python(existing_path)\n            return existing_path\n\n        return None\n\n    def _resolve_internal_interpreter_path(self, python_version: str) -> str | None:\n        if (available_distribution := self._get_available_distribution(python_version)) is not None:\n            with self.app.status(f\"Installing Python distribution: {available_distribution}\"):\n                dist = self.python_manager.install(available_distribution)\n\n            return str(dist.python_path)\n\n        return None\n\n    def _find_existing_interpreter(self, python_version: str = \"\") -> str | None:\n        import python_discovery\n\n        python_info = python_discovery.get_interpreter(\n            python_version, (), env=self.get_interpreter_resolver_env(), predicate=self._interpreter_is_compatible\n        )\n        return None if python_info is None else python_info.executable\n\n    def _get_available_distribution(self, python_version: str = \"\") -> str | None:\n        from hatch.python.resolve import get_compatible_distributions\n\n        compatible_distributions = get_compatible_distributions()\n        for installed_distribution in self.python_manager.get_installed():\n            compatible_distributions.pop(installed_distribution, None)\n\n        if not python_version:\n            # Only try providing CPython distributions\n            available_distributions = [d for d in compatible_distributions if not d.startswith(\"pypy\")]\n\n            # Prioritize the version that Hatch is currently using, if available\n            with suppress(ValueError):\n                available_distributions.remove(self._preferred_python_version)\n                available_distributions.append(self._preferred_python_version)\n\n            # Latest first\n            available_distributions.reverse()\n        elif python_version in compatible_distributions:\n            available_distributions = [python_version]\n        else:\n            return None\n\n        for available_distribution in available_distributions:\n            minor_version = (\n                available_distribution.replace(\"pypy\", \"\", 1)\n                if available_distribution.startswith(\"pypy\")\n                else available_distribution\n            )\n            if not self._python_constraint.contains(minor_version):\n                continue\n\n            return available_distribution\n\n        return None\n\n    def _is_stable_path(self, executable: str) -> bool:\n        path = Path(executable).resolve()\n        parents = path.parents\n\n        # https://pypa.github.io/pipx/how-pipx-works/\n        if (Path.home() / \".local\" / \"pipx\" / \"venvs\") in parents:\n            return False\n\n        from platformdirs import user_data_dir\n\n        # https://github.com/ofek/pyapp/blob/v0.13.0/src/app.rs#L27\n        if Path(user_data_dir(\"pyapp\", appauthor=False)) in parents:\n            return False\n\n        # via Windows store\n        if self.platform.windows and str(path).endswith(\"WindowsApps\\\\python.exe\"):\n            return False\n\n        # via Homebrew\n        return not (self.platform.macos and Path(\"/usr/local/Cellar\") in parents)\n\n    @cached_property\n    def _python_sources(self) -> list[str]:\n        return self.config.get(\"python-sources\") or [\"external\", \"internal\"]\n\n    def _python_resolvers(self) -> dict[str, Callable[[str], str | None]]:\n        return {\n            \"external\": self._resolve_external_interpreter_path,\n            \"internal\": self._resolve_internal_interpreter_path,\n        }\n\n    @cached_property\n    def _python_constraint(self) -> SpecifierSet:\n        from packaging.specifiers import SpecifierSet\n\n        # Note that we do not support this field being dynamic because if we were to set up the\n        # build environment to retrieve the field then we would be stuck because we need to use\n        # a satisfactory version to set up the environment\n        return SpecifierSet(self.metadata.config.get(\"project\", {}).get(\"requires-python\", \"\"))\n\n    @contextmanager\n    def safe_activation(self):\n        # In order of precedence:\n        # - This environment\n        # - UV\n        # - User-defined environment variables\n        with self.get_env_vars(), self.expose_uv(), self:\n            yield\n"
  },
  {
    "path": "src/hatch/errors/__init__.py",
    "content": "class HatchError(Exception):\n    pass\n\n\nclass PythonDistributionUnknownError(HatchError):\n    pass\n\n\nclass PythonDistributionResolutionError(HatchError):\n    pass\n"
  },
  {
    "path": "src/hatch/index/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/index/core.py",
    "content": "from __future__ import annotations\n\nimport sys\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING\n\nimport hyperlink\n\nfrom hatch._version import __version__\n\nif TYPE_CHECKING:\n    import httpx\n\n    from hatch.utils.fs import Path\n\n\nclass IndexURLs:\n    def __init__(self, repo: str):\n        self.repo = hyperlink.parse(repo).normalize()\n\n        # PyPI\n        if self.repo.host.endswith(\"pypi.org\"):  # no cov\n            repo_url = self.repo.replace(host=\"pypi.org\") if self.repo.host == \"upload.pypi.org\" else self.repo\n\n            self.simple = repo_url.click(\"/simple/\")\n            self.project = repo_url.click(\"/project/\")\n\n        # Assume devpi\n        else:\n            self.simple = self.repo.child(\"+simple\", \"\")\n            self.project = self.repo\n\n\nclass PackageIndex:\n    def __init__(self, repo: str, *, user=\"\", auth=\"\", ca_cert=None, client_cert=None, client_key=None):\n        self.urls = IndexURLs(repo)\n        self.repo = str(self.urls.repo)\n        self.user = user\n        self.auth = auth\n\n        self.__cert = None\n        if client_cert:\n            self.__cert = client_cert\n            if client_key:\n                self.__cert = (client_cert, client_key)\n\n        self.__verify = True\n        if ca_cert:\n            self.__verify = ca_cert\n\n    @cached_property\n    def client(self) -> httpx.Client:\n        import httpx\n\n        from hatch.utils.network import DEFAULT_TIMEOUT\n\n        user_agent = (\n            f\"Hatch/{__version__} \"\n            f\"{sys.implementation.name}/{'.'.join(map(str, sys.version_info[:3]))} \"\n            f\"HTTPX/{httpx.__version__}\"\n        )\n        return httpx.Client(\n            headers={\"User-Agent\": user_agent},\n            transport=httpx.HTTPTransport(retries=3, verify=self.__verify, cert=self.__cert),\n            timeout=DEFAULT_TIMEOUT,\n        )\n\n    def upload_artifact(self, artifact: Path, data: dict):\n        import hashlib\n        import io\n\n        data[\":action\"] = \"file_upload\"\n        data[\"protocol_version\"] = \"1\"\n\n        with artifact.open(\"rb\") as f:\n            # https://github.com/pypa/warehouse/blob/7fc3ce5bd7ecc93ef54c1652787fb5e7757fe6f2/tests/unit/packaging/test_tasks.py#L189-L191\n            md5_hash = hashlib.md5()  # noqa: S324\n            sha256_hash = hashlib.sha256()\n            blake2_256_hash = hashlib.blake2b(digest_size=32)\n\n            while True:\n                chunk = f.read(io.DEFAULT_BUFFER_SIZE)\n                if not chunk:\n                    break\n\n                md5_hash.update(chunk)\n                sha256_hash.update(chunk)\n                blake2_256_hash.update(chunk)\n\n            data[\"md5_digest\"] = md5_hash.hexdigest()\n            data[\"sha256_digest\"] = sha256_hash.hexdigest()\n            data[\"blake2_256_digest\"] = blake2_256_hash.hexdigest()\n\n            f.seek(0)\n\n            response = self.client.post(\n                self.repo,\n                data=data,\n                files={\"content\": (artifact.name, f, \"application/octet-stream\")},\n                auth=(self.user, self.auth),\n            )\n            response.raise_for_status()\n\n    def get_simple_api(self, project: str) -> httpx.Response:\n        return self.client.get(\n            str(self.urls.simple.child(project, \"\")),\n            headers={\"Cache-Control\": \"no-cache\"},\n            auth=(self.user, self.auth),\n        )\n"
  },
  {
    "path": "src/hatch/index/errors.py",
    "content": "class ArtifactMetadataError(Exception):\n    pass\n"
  },
  {
    "path": "src/hatch/index/publish.py",
    "content": "from hatch.index.errors import ArtifactMetadataError\n\nMULTIPLE_USE_METADATA_FIELDS = {\n    \"classifier\",\n    \"dynamic\",\n    \"license_file\",\n    \"obsoletes_dist\",\n    \"platform\",\n    \"project_url\",\n    \"provides_dist\",\n    \"provides_extra\",\n    \"requires_dist\",\n    \"requires_external\",\n    \"supported_platform\",\n}\nRENAMED_METADATA_FIELDS = {\"classifier\": \"classifiers\", \"project_url\": \"project_urls\"}\n\n\ndef get_wheel_form_data(artifact):\n    import zipfile\n\n    from packaging.tags import parse_tag\n\n    with zipfile.ZipFile(str(artifact), \"r\") as zip_archive:\n        for path in zip_archive.namelist():\n            root = path.split(\"/\", 1)[0]\n            if root.endswith(\".dist-info\"):\n                dist_info_dir = root\n                break\n        else:  # no cov\n            message = f\"Could not find the `.dist-info` directory in wheel: {artifact}\"\n            raise ArtifactMetadataError(message)\n\n        try:\n            with zip_archive.open(f\"{dist_info_dir}/METADATA\") as zip_file:\n                metadata_file_contents = zip_file.read().decode(\"utf-8\")\n        except KeyError:  # no cov\n            message = f\"Could not find a `METADATA` file in the `{dist_info_dir}` directory\"\n            raise ArtifactMetadataError(message) from None\n        else:\n            data = parse_headers(metadata_file_contents)\n\n    data[\"filetype\"] = \"bdist_wheel\"\n\n    # Examples:\n    # cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl -> pp37\n    # hatchling-1rc1-py2.py3-none-any.whl -> py2.py3\n    tag_component = \"-\".join(artifact.stem.split(\"-\")[-3:])\n    data[\"pyversion\"] = \".\".join(sorted({tag.interpreter for tag in parse_tag(tag_component)}))\n\n    return data\n\n\ndef get_sdist_form_data(artifact):\n    import tarfile\n\n    with tarfile.open(str(artifact), \"r:gz\") as tar_archive:\n        pkg_info_dir_parts = []\n        for tar_info in tar_archive:\n            if tar_info.isfile():\n                pkg_info_dir_parts.append(tar_info.name.split(\"/\", 1)[0])\n                break\n        else:  # no cov\n            message = f\"Could not find any files in sdist: {artifact}\"\n            raise ArtifactMetadataError(message)\n\n        pkg_info_dir_parts.append(\"PKG-INFO\")\n        pkg_info_path = \"/\".join(pkg_info_dir_parts)\n        try:\n            with tar_archive.extractfile(pkg_info_path) as tar_file:\n                metadata_file_contents = tar_file.read().decode(\"utf-8\")\n        except KeyError:  # no cov\n            message = f\"Could not find file: {pkg_info_path}\"\n            raise ArtifactMetadataError(message) from None\n        else:\n            data = parse_headers(metadata_file_contents)\n\n    data[\"filetype\"] = \"sdist\"\n    data[\"pyversion\"] = \"source\"\n\n    return data\n\n\ndef parse_headers(metadata_file_contents):\n    import email\n\n    message = email.message_from_string(metadata_file_contents)\n\n    headers = {\"description\": message.get_payload()}\n\n    for header, value in message.items():\n        normalized_header = header.lower().replace(\"-\", \"_\")\n        header_name = RENAMED_METADATA_FIELDS.get(normalized_header, normalized_header)\n\n        if normalized_header in MULTIPLE_USE_METADATA_FIELDS:\n            if header_name in headers:\n                headers[header_name].append(value)\n            else:\n                headers[header_name] = [value]\n        else:\n            headers[header_name] = value\n\n    return headers\n"
  },
  {
    "path": "src/hatch/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/plugin/constants.py",
    "content": "DEFAULT_CUSTOM_SCRIPT = \"hatch_plugins.py\"\n"
  },
  {
    "path": "src/hatch/plugin/manager.py",
    "content": "from hatchling.plugin.manager import PluginManager as _PluginManager\n\n\nclass PluginManager(_PluginManager):\n    def initialize(self):\n        super().initialize()\n\n        from hatch.plugin import specs\n\n        self.manager.add_hookspecs(specs)\n\n    def hatch_register_environment(self):\n        from hatch.env.plugin import hooks\n\n        self.manager.register(hooks)\n\n    def hatch_register_environment_collector(self):\n        from hatch.env.collectors.plugin import hooks\n\n        self.manager.register(hooks)\n\n    def hatch_register_publisher(self):\n        from hatch.publish.plugin import hooks\n\n        self.manager.register(hooks)\n\n    def hatch_register_template(self):\n        from hatch.template.plugin import hooks\n\n        self.manager.register(hooks)\n"
  },
  {
    "path": "src/hatch/plugin/specs.py",
    "content": "from hatchling.plugin.specs import hookspec\n\n\n@hookspec\ndef hatch_register_environment():\n    \"\"\"Register new classes that adhere to the environment interface.\"\"\"\n\n\n@hookspec\ndef hatch_register_environment_collector():\n    \"\"\"Register new classes that adhere to the environment collector interface.\"\"\"\n\n\n@hookspec\ndef hatch_register_version_scheme():\n    \"\"\"Register new classes that adhere to the version scheme interface.\"\"\"\n\n\n@hookspec\ndef hatch_register_publisher():\n    \"\"\"Register new classes that adhere to the publisher interface.\"\"\"\n\n\n@hookspec\ndef hatch_register_template():\n    \"\"\"Register new classes that adhere to the template interface.\"\"\"\n"
  },
  {
    "path": "src/hatch/plugin/utils.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\n\n\ndef load_plugin_from_script(\n    path: str, script_name: str, plugin_class: type[EnvironmentCollectorInterface], plugin_id: str\n) -> type[EnvironmentCollectorInterface]:\n    from importlib.util import module_from_spec, spec_from_file_location\n\n    spec = spec_from_file_location(script_name, path)\n    module = module_from_spec(spec)  # type: ignore[arg-type]\n    spec.loader.exec_module(module)  # type: ignore[union-attr]\n\n    plugin_finder = f\"get_{plugin_id}\"\n    names = dir(module)\n    if plugin_finder in names:\n        return getattr(module, plugin_finder)()\n\n    subclasses = []\n    for name in names:\n        obj = getattr(module, name)\n        if obj is plugin_class:\n            continue\n\n        try:\n            if issubclass(obj, plugin_class):\n                subclasses.append(obj)\n        except TypeError:\n            continue\n\n    if not subclasses:\n        message = f\"Unable to find a subclass of `{plugin_class.__name__}` in `{script_name}`: {path}\"\n        raise ValueError(message)\n\n    if len(subclasses) > 1:\n        message = (\n            f\"Multiple subclasses of `{plugin_class.__name__}` found in `{script_name}`, \"\n            f\"select one by defining a function named `{plugin_finder}`: {path}\"\n        )\n        raise ValueError(message)\n\n    return subclasses[0]\n"
  },
  {
    "path": "src/hatch/project/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/project/config.py",
    "content": "from __future__ import annotations\n\nimport re\nfrom copy import deepcopy\nfrom functools import cached_property\nfrom itertools import product\nfrom os import environ\nfrom typing import TYPE_CHECKING, Any\n\nfrom hatch.env.utils import ensure_valid_environment\nfrom hatch.project.constants import DEFAULT_BUILD_DIRECTORY, BuildEnvVars\nfrom hatch.project.env import apply_overrides\nfrom hatch.project.utils import format_script_commands, parse_script_command\n\nif TYPE_CHECKING:\n    from hatch.dep.core import Dependency\n\n\nclass ProjectConfig:\n    def __init__(self, root, config, plugin_manager=None):\n        self.root = root\n        self.config = config\n        self.plugin_manager = plugin_manager\n\n        self._matrices = None\n        self._env = None\n        self._env_requires_complex = None\n        self._env_requires = None\n        self._env_collectors = None\n        self._envs = None\n        self._internal_envs = None\n        self._internal_matrices = None\n        self._matrix_variables = None\n        self._publish = None\n        self._scripts = None\n        self._cached_env_overrides = {}\n\n    @cached_property\n    def build(self):\n        config = self.config.get(\"build\", {})\n        if not isinstance(config, dict):\n            message = \"Field `tool.hatch.build` must be a table\"\n            raise TypeError(message)\n\n        return BuildConfig(config)\n\n    @property\n    def env(self):\n        if self._env is None:\n            config = self.config.get(\"env\", {})\n            if not isinstance(config, dict):\n                message = \"Field `tool.hatch.env` must be a table\"\n                raise TypeError(message)\n\n            self._env = config\n\n        return self._env\n\n    @property\n    def env_requires_complex(self) -> list[Dependency]:\n        if self._env_requires_complex is None:\n            from hatch.dep.core import Dependency, InvalidDependencyError\n\n            requires = self.env.get(\"requires\", [])\n            if not isinstance(requires, list):\n                message = \"Field `tool.hatch.env.requires` must be an array\"\n                raise TypeError(message)\n\n            requires_complex = []\n\n            for i, entry in enumerate(requires, 1):\n                if not isinstance(entry, str):\n                    message = f\"Requirement #{i} in `tool.hatch.env.requires` must be a string\"\n                    raise TypeError(message)\n\n                try:\n                    requires_complex.append(Dependency(entry))\n                except InvalidDependencyError as e:\n                    message = f\"Requirement #{i} in `tool.hatch.env.requires` is invalid: {e}\"\n                    raise ValueError(message) from None\n\n            self._env_requires_complex = requires_complex\n\n        return self._env_requires_complex\n\n    @property\n    def env_requires(self):\n        if self._env_requires is None:\n            self._env_requires = [str(r) for r in self.env_requires_complex]\n\n        return self._env_requires\n\n    @property\n    def env_collectors(self):\n        if self._env_collectors is None:\n            collectors = self.env.get(\"collectors\", {})\n            if not isinstance(collectors, dict):\n                message = \"Field `tool.hatch.env.collectors` must be a table\"\n                raise TypeError(message)\n\n            final_config = {\"default\": {}}\n            for collector, config in collectors.items():\n                if not isinstance(config, dict):\n                    message = f\"Field `tool.hatch.env.collectors.{collector}` must be a table\"\n                    raise TypeError(message)\n\n                final_config[collector] = config\n\n            self._env_collectors = final_config\n\n        return self._env_collectors\n\n    @property\n    def matrices(self):\n        if self._matrices is None:\n            _ = self.envs\n\n        return self._matrices\n\n    @property\n    def matrix_variables(self):\n        if self._matrix_variables is None:\n            _ = self.envs\n\n        return self._matrix_variables\n\n    @property\n    def internal_envs(self):\n        if self._internal_envs is None:\n            _ = self.envs\n\n        return self._internal_envs\n\n    @property\n    def internal_matrices(self):\n        if self._internal_matrices is None:\n            _ = self.envs\n\n        return self._internal_matrices\n\n    @property\n    def envs(self):\n        from hatch.env.internal import get_internal_env_config\n        from hatch.utils.platform import get_platform_name\n\n        if self._envs is None:\n            env_config = self.config.get(\"envs\", {})\n            if not isinstance(env_config, dict):\n                message = \"Field `tool.hatch.envs` must be a table\"\n                raise TypeError(message)\n\n            config = {}\n            environment_collectors = []\n\n            for collector, collector_config in self.env_collectors.items():\n                collector_class = self.plugin_manager.environment_collector.get(collector)\n                if collector_class is None:\n                    message = f\"Unknown environment collector: {collector}\"\n                    raise ValueError(message)\n\n                environment_collector = collector_class(self.root, collector_config)\n                environment_collectors.append(environment_collector)\n\n                for env_name, data in environment_collector.get_initial_config().items():\n                    config.setdefault(env_name, data)\n\n            for env_name, data in env_config.items():\n                if not isinstance(data, dict):\n                    message = f\"Field `tool.hatch.envs.{env_name}` must be a table\"\n                    raise TypeError(message)\n\n                config.setdefault(env_name, {}).update(data)\n\n            for environment_collector in environment_collectors:\n                environment_collector.finalize_config(config)\n\n            # Prevent plugins from removing the default environment\n            ensure_valid_environment(config.setdefault(\"default\", {}))\n\n            seen = set()\n            active = []\n            for env_name, data in config.items():\n                _populate_default_env_values(env_name, data, config, seen, active)\n\n            current_platform = get_platform_name()\n            all_matrices = {}\n            generated_envs = {}\n            final_config = {}\n            cached_overrides = {}\n            for env_name, raw_initial_config in config.items():\n                current_cached_overrides = cached_overrides[env_name] = {\n                    \"platform\": [],\n                    \"env\": [],\n                    \"matrix\": [],\n                    \"name\": [],\n                }\n\n                # Only shallow copying is necessary since we just want to modify keys\n                initial_config = raw_initial_config.copy()\n\n                matrix_name_format = initial_config.pop(\"matrix-name-format\", \"{value}\")\n                if not isinstance(matrix_name_format, str):\n                    message = f\"Field `tool.hatch.envs.{env_name}.matrix-name-format` must be a string\"\n                    raise TypeError(message)\n\n                if \"{value}\" not in matrix_name_format:\n                    message = (\n                        f\"Field `tool.hatch.envs.{env_name}.matrix-name-format` must \"\n                        f\"contain at least the `{{value}}` placeholder\"\n                    )\n                    raise ValueError(message)\n\n                overrides = initial_config.pop(\"overrides\", {})\n                if not isinstance(overrides, dict):\n                    message = f\"Field `tool.hatch.envs.{env_name}.overrides` must be a table\"\n                    raise TypeError(message)\n\n                # Apply any configuration based on the current platform\n                platform_overrides = overrides.get(\"platform\", {})\n                if not isinstance(platform_overrides, dict):\n                    message = f\"Field `tool.hatch.envs.{env_name}.overrides.platform` must be a table\"\n                    raise TypeError(message)\n\n                for platform, options in platform_overrides.items():\n                    if not isinstance(options, dict):\n                        message = f\"Field `tool.hatch.envs.{env_name}.overrides.platform.{platform}` must be a table\"\n                        raise TypeError(message)\n\n                    if platform != current_platform:\n                        continue\n\n                    apply_overrides(env_name, \"platform\", platform, current_platform, options, initial_config)\n                    current_cached_overrides[\"platform\"].append((platform, current_platform, options))\n\n                # Apply any configuration based on environment variables\n                env_var_overrides = overrides.get(\"env\", {})\n                if not isinstance(env_var_overrides, dict):\n                    message = f\"Field `tool.hatch.envs.{env_name}.overrides.env` must be a table\"\n                    raise TypeError(message)\n\n                for env_var, options in env_var_overrides.items():\n                    if not isinstance(options, dict):\n                        message = f\"Field `tool.hatch.envs.{env_name}.overrides.env.{env_var}` must be a table\"\n                        raise TypeError(message)\n\n                    if env_var not in environ:\n                        continue\n\n                    apply_overrides(env_name, \"env\", env_var, environ[env_var], options, initial_config)\n                    current_cached_overrides[\"env\"].append((env_var, environ[env_var], options))\n\n                if \"matrix\" not in initial_config:\n                    final_config[env_name] = initial_config\n                    continue\n\n                matrices = initial_config.pop(\"matrix\")\n                if not isinstance(matrices, list):\n                    message = f\"Field `tool.hatch.envs.{env_name}.matrix` must be an array\"\n                    raise TypeError(message)\n\n                matrix_overrides = overrides.get(\"matrix\", {})\n                if not isinstance(matrix_overrides, dict):\n                    message = f\"Field `tool.hatch.envs.{env_name}.overrides.matrix` must be a table\"\n                    raise TypeError(message)\n\n                name_overrides = overrides.get(\"name\", {})\n                if not isinstance(name_overrides, dict):\n                    message = f\"Field `tool.hatch.envs.{env_name}.overrides.name` must be a table\"\n                    raise TypeError(message)\n\n                matrix_data = all_matrices[env_name] = {\"config\": deepcopy(initial_config)}\n                all_envs = matrix_data[\"envs\"] = {}\n                for i, raw_matrix in enumerate(matrices, 1):\n                    matrix = raw_matrix\n                    if not isinstance(matrix, dict):\n                        message = f\"Entry #{i} in field `tool.hatch.envs.{env_name}.matrix` must be a table\"\n                        raise TypeError(message)\n\n                    if not matrix:\n                        message = f\"Matrix #{i} in field `tool.hatch.envs.{env_name}.matrix` cannot be empty\"\n                        raise ValueError(message)\n\n                    for j, (variable, values) in enumerate(matrix.items(), 1):\n                        if not variable:\n                            message = (\n                                f\"Variable #{j} in matrix #{i} in field `tool.hatch.envs.{env_name}.matrix` \"\n                                f\"cannot be an empty string\"\n                            )\n                            raise ValueError(message)\n\n                        if not isinstance(values, list):\n                            message = (\n                                f\"Variable `{variable}` in matrix #{i} in field `tool.hatch.envs.{env_name}.matrix` \"\n                                f\"must be an array\"\n                            )\n                            raise TypeError(message)\n\n                        if not values:\n                            message = (\n                                f\"Variable `{variable}` in matrix #{i} in field `tool.hatch.envs.{env_name}.matrix` \"\n                                f\"cannot be empty\"\n                            )\n                            raise ValueError(message)\n\n                        existing_values = set()\n                        for k, value in enumerate(values, 1):\n                            if not isinstance(value, str):\n                                message = (\n                                    f\"Value #{k} of variable `{variable}` in matrix #{i} in field \"\n                                    f\"`tool.hatch.envs.{env_name}.matrix` must be a string\"\n                                )\n                                raise TypeError(message)\n\n                            if not value:\n                                message = (\n                                    f\"Value #{k} of variable `{variable}` in matrix #{i} in field \"\n                                    f\"`tool.hatch.envs.{env_name}.matrix` cannot be an empty string\"\n                                )\n                                raise ValueError(message)\n\n                            if value in existing_values:\n                                message = (\n                                    f\"Value #{k} of variable `{variable}` in matrix #{i} in field \"\n                                    f\"`tool.hatch.envs.{env_name}.matrix` is a duplicate\"\n                                )\n                                raise ValueError(message)\n\n                            existing_values.add(value)\n\n                    variables = {}\n\n                    # Ensure that any Python variable comes first\n                    python_selected = False\n                    for variable in (\"py\", \"python\"):\n                        if variable in matrix:\n                            if python_selected:\n                                message = (\n                                    f\"Matrix #{i} in field `tool.hatch.envs.{env_name}.matrix` \"\n                                    f\"cannot contain both `py` and `python` variables\"\n                                )\n                                raise ValueError(message)\n                            python_selected = True\n\n                            # Only shallow copying is necessary since we just want to remove a key\n                            matrix = matrix.copy()\n                            variables[variable] = matrix.pop(variable)\n\n                    variables.update(matrix)\n\n                    for result in product(*variables.values()):\n                        # Make a value mapping for easy referencing\n                        variable_values = dict(zip(variables, result, strict=False))\n\n                        # Create the environment's initial configuration\n                        new_config = deepcopy(initial_config)\n\n                        cached_matrix_overrides = []\n\n                        # Apply any configuration based on matrix variables\n                        for variable, options in matrix_overrides.items():\n                            if not isinstance(options, dict):\n                                message = (\n                                    f\"Field `tool.hatch.envs.{env_name}.overrides.matrix.{variable}` must be a table\"\n                                )\n                                raise TypeError(message)\n\n                            if variable not in variables:\n                                continue\n\n                            apply_overrides(\n                                env_name, \"matrix\", variable, variable_values[variable], options, new_config\n                            )\n                            cached_matrix_overrides.append((variable, variable_values[variable], options))\n\n                        # Construct the environment name\n                        final_matrix_name_format = new_config.pop(\"matrix-name-format\", matrix_name_format)\n                        env_name_parts = []\n                        for j, (variable, value) in enumerate(variable_values.items()):\n                            if j == 0 and python_selected:\n                                new_config[\"python\"] = value\n                                env_name_parts.append(value if value.startswith(\"py\") else f\"py{value}\")\n                            else:\n                                env_name_parts.append(final_matrix_name_format.format(variable=variable, value=value))\n\n                        new_env_name = \"-\".join(env_name_parts)\n\n                        cached_name_overrides = []\n\n                        # Apply any configuration based on the final name, minus the prefix for non-default environments\n                        for pattern, options in name_overrides.items():\n                            if not isinstance(options, dict):\n                                message = f\"Field `tool.hatch.envs.{env_name}.overrides.name.{pattern}` must be a table\"\n                                raise TypeError(message)\n\n                            if not re.search(pattern, new_env_name):\n                                continue\n\n                            apply_overrides(env_name, \"name\", pattern, new_env_name, options, new_config)\n                            cached_name_overrides.append((pattern, new_env_name, options))\n\n                        if env_name != \"default\":\n                            new_env_name = f\"{env_name}.{new_env_name}\"\n\n                        # Save the generated environment\n                        final_config[new_env_name] = new_config\n                        cached_overrides[new_env_name] = {\n                            \"platform\": current_cached_overrides[\"platform\"],\n                            \"env\": current_cached_overrides[\"env\"],\n                            \"matrix\": cached_matrix_overrides,\n                            \"name\": cached_name_overrides,\n                        }\n                        all_envs[new_env_name] = variable_values\n                        if \"py\" in variable_values:\n                            all_envs[new_env_name] = {\"python\": variable_values.pop(\"py\"), **variable_values}\n\n                # Remove the root matrix generator\n                del cached_overrides[env_name]\n\n                # Save the variables used to generate the environments\n                generated_envs.update(all_envs)\n\n            for environment_collector in environment_collectors:\n                environment_collector.finalize_environments(final_config)\n\n            self._matrices = all_matrices\n            self._internal_matrices = {}\n            self._envs = final_config\n            self._matrix_variables = generated_envs\n            self._cached_env_overrides.update(cached_overrides)\n\n            # Extract the internal environments\n            self._internal_envs = {}\n            for internal_name in get_internal_env_config():\n                try:\n                    self._internal_envs[internal_name] = self._envs.pop(internal_name)\n                # Matrix\n                except KeyError:\n                    self._internal_matrices[internal_name] = self._matrices.pop(internal_name)\n                    for env_name in [env_name for env_name in self._envs if env_name.startswith(f\"{internal_name}.\")]:\n                        self._internal_envs[env_name] = self._envs.pop(env_name)\n\n        return self._envs\n\n    @property\n    def publish(self):\n        if self._publish is None:\n            config = self.config.get(\"publish\", {})\n            if not isinstance(config, dict):\n                message = \"Field `tool.hatch.publish` must be a table\"\n                raise TypeError(message)\n\n            for publisher, data in config.items():\n                if not isinstance(data, dict):\n                    message = f\"Field `tool.hatch.publish.{publisher}` must be a table\"\n                    raise TypeError(message)\n\n            self._publish = config\n\n        return self._publish\n\n    @property\n    def scripts(self):\n        if self._scripts is None:\n            script_config = self.config.get(\"scripts\", {})\n            if not isinstance(script_config, dict):\n                message = \"Field `tool.hatch.scripts` must be a table\"\n                raise TypeError(message)\n\n            config = {}\n\n            for name, data in script_config.items():\n                if \" \" in name:\n                    message = f\"Script name `{name}` in field `tool.hatch.scripts` must not contain spaces\"\n                    raise ValueError(message)\n\n                commands = []\n\n                if isinstance(data, str):\n                    commands.append(data)\n                elif isinstance(data, list):\n                    for i, command in enumerate(data, 1):\n                        if not isinstance(command, str):\n                            message = f\"Command #{i} in field `tool.hatch.scripts.{name}` must be a string\"\n                            raise TypeError(message)\n\n                        commands.append(command)\n                else:\n                    message = f\"Field `tool.hatch.scripts.{name}` must be a string or an array of strings\"\n                    raise TypeError(message)\n\n                config[name] = commands\n\n            seen = {}\n            active = []\n            for script_name, commands in config.items():\n                commands[:] = expand_script_commands(script_name, commands, config, seen, active)\n\n            self._scripts = config\n\n        return self._scripts\n\n    def finalize_env_overrides(self, option_types):\n        # We lazily apply overrides because we need type information potentially defined by\n        # environment plugins for their options\n        if not self._cached_env_overrides:\n            return\n\n        for environments in (self.envs, self.internal_envs):\n            for env_name, config in environments.items():\n                for override_name, data in self._cached_env_overrides.get(env_name, {}).items():\n                    for condition, condition_value, options in data:\n                        apply_overrides(\n                            env_name, override_name, condition, condition_value, options, config, option_types\n                        )\n\n        self._cached_env_overrides.clear()\n\n\nclass BuildConfig:\n    def __init__(self, config: dict[str, Any]) -> None:\n        self.__config = config\n        self.__targets: dict[str, BuildTargetConfig] = config\n\n    @cached_property\n    def directory(self) -> str:\n        directory = self.__config.get(\"directory\", DEFAULT_BUILD_DIRECTORY)\n        if not isinstance(directory, str):\n            message = \"Field `tool.hatch.build.directory` must be a string\"\n            raise TypeError(message)\n\n        return directory\n\n    @cached_property\n    def dependencies(self) -> list[str]:\n        dependencies: list[str] = self.__config.get(\"dependencies\", [])\n        if not isinstance(dependencies, list):\n            message = \"Field `tool.hatch.build.dependencies` must be an array\"\n            raise TypeError(message)\n\n        for i, dependency in enumerate(dependencies, 1):\n            if not isinstance(dependency, str):\n                message = f\"Dependency #{i} in field `tool.hatch.build.dependencies` must be a string\"\n                raise TypeError(message)\n\n        return list(dependencies)\n\n    @cached_property\n    def hook_config(self) -> dict[str, dict[str, Any]]:\n        hook_config: dict[str, dict[str, Any]] = self.__config.get(\"hooks\", {})\n        if not isinstance(hook_config, dict):\n            message = \"Field `tool.hatch.build.hooks` must be a table\"\n            raise TypeError(message)\n\n        for hook_name, config in hook_config.items():\n            if not isinstance(config, dict):\n                message = f\"Field `tool.hatch.build.hooks.{hook_name}` must be a table\"\n                raise TypeError(message)\n\n        return finalize_hook_config(hook_config)\n\n    @cached_property\n    def __target_config(self) -> dict[str, Any]:\n        config = self.__config.get(\"targets\", {})\n        if not isinstance(config, dict):\n            message = \"Field `tool.hatch.build.targets` must be a table\"\n            raise TypeError(message)\n\n        return config\n\n    def target(self, target: str) -> BuildTargetConfig:\n        if target in self.__targets:\n            return self.__targets[target]\n\n        config = self.__target_config.get(target, {})\n        if not isinstance(config, dict):\n            message = f\"Field `tool.hatch.build.targets.{target}` must be a table\"\n            raise TypeError(message)\n\n        target_config = BuildTargetConfig(target, config, self)\n        self.__targets[target] = target_config\n        return target_config\n\n\nclass BuildTargetConfig:\n    def __init__(self, name: str, config: dict[str, Any], global_config: BuildConfig) -> None:\n        self.__name = name\n        self.__config = config\n        self.__global_config = global_config\n\n    @cached_property\n    def directory(self) -> str:\n        directory = self.__config.get(\"directory\", self.__global_config.directory)\n        if not isinstance(directory, str):\n            message = f\"Field `tool.hatch.build.targets.{self.__name}.directory` must be a string\"\n            raise TypeError(message)\n\n        return directory\n\n    @cached_property\n    def dependencies(self) -> list[str]:\n        dependencies: list[str] = self.__config.get(\"dependencies\", [])\n        if not isinstance(dependencies, list):\n            message = f\"Field `tool.hatch.build.targets.{self.__name}.dependencies` must be an array\"\n            raise TypeError(message)\n\n        for i, dependency in enumerate(dependencies, 1):\n            if not isinstance(dependency, str):\n                message = (\n                    f\"Dependency #{i} in field `tool.hatch.build.targets.{self.__name}.dependencies` must be a string\"\n                )\n                raise TypeError(message)\n\n        all_dependencies = list(self.__global_config.dependencies)\n        all_dependencies.extend(dependencies)\n        return all_dependencies\n\n    @cached_property\n    def hook_config(self) -> dict[str, dict[str, Any]]:\n        hook_config: dict[str, dict[str, Any]] = self.__config.get(\"hooks\", {})\n        if not isinstance(hook_config, dict):\n            message = f\"Field `tool.hatch.build.targets.{self.__name}.hooks` must be a table\"\n            raise TypeError(message)\n\n        for hook_name, config in hook_config.items():\n            if not isinstance(config, dict):\n                message = f\"Field `tool.hatch.build.targets.{self.__name}.hooks.{hook_name}` must be a table\"\n                raise TypeError(message)\n\n        config = self.__global_config.hook_config.copy()\n        config.update(hook_config)\n        return finalize_hook_config(config)\n\n\ndef expand_script_commands(script_name, commands, config, seen, active):\n    if script_name in seen:\n        return seen[script_name]\n\n    if script_name in active:\n        active.append(script_name)\n\n        message = f\"Circular expansion detected for field `tool.hatch.scripts`: {' -> '.join(active)}\"\n        raise ValueError(message)\n\n    active.append(script_name)\n\n    expanded_commands = []\n\n    for command in commands:\n        possible_script, args, ignore_exit_code = parse_script_command(command)\n\n        if possible_script in config:\n            expanded_commands.extend(\n                format_script_commands(\n                    commands=expand_script_commands(possible_script, config[possible_script], config, seen, active),\n                    args=args,\n                    ignore_exit_code=ignore_exit_code,\n                )\n            )\n        else:\n            expanded_commands.append(command)\n\n    seen[script_name] = expanded_commands\n    active.pop()\n\n    return expanded_commands\n\n\ndef _populate_default_env_values(env_name, data, config, seen, active):\n    if env_name in seen:\n        return\n\n    if data.pop(\"detached\", False):\n        data[\"template\"] = env_name\n        data[\"skip-install\"] = True\n\n    template_name = data.pop(\"template\", \"default\")\n    if template_name not in config:\n        message = f\"Field `tool.hatch.envs.{env_name}.template` refers to an unknown environment `{template_name}`\"\n        raise ValueError(message)\n\n    if env_name in active:\n        active.append(env_name)\n\n        message = f\"Circular inheritance detected for field `tool.hatch.envs.*.template`: {' -> '.join(active)}\"\n        raise ValueError(message)\n\n    if template_name == env_name:\n        ensure_valid_environment(data)\n        seen.add(env_name)\n        return\n\n    active.append(env_name)\n\n    template_config = config[template_name]\n    _populate_default_env_values(template_name, template_config, config, seen, active)\n\n    for key, value in template_config.items():\n        if key == \"matrix\":\n            continue\n\n        if key == \"scripts\":\n            scripts = data[\"scripts\"] if \"scripts\" in data else data.setdefault(\"scripts\", {})\n            for script, commands in value.items():\n                scripts.setdefault(script, commands)\n        else:\n            data.setdefault(key, value)\n\n    seen.add(env_name)\n    active.pop()\n\n\ndef finalize_hook_config(hook_config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:\n    if env_var_enabled(BuildEnvVars.NO_HOOKS):\n        return {}\n\n    all_hooks_enabled = env_var_enabled(BuildEnvVars.HOOKS_ENABLE)\n    final_hook_config: dict[str, dict[str, Any]] = {\n        hook_name: config\n        for hook_name, config in hook_config.items()\n        if (\n            all_hooks_enabled\n            or config.get(\"enable-by-default\", True)\n            or env_var_enabled(f\"{BuildEnvVars.HOOK_ENABLE_PREFIX}{hook_name.upper()}\")\n        )\n    }\n    return final_hook_config\n\n\ndef env_var_enabled(env_var: str, *, default: bool = False) -> bool:\n    if env_var in environ:\n        return environ[env_var] in {\"1\", \"true\"}\n\n    return default\n"
  },
  {
    "path": "src/hatch/project/constants.py",
    "content": "BUILD_BACKEND = \"hatchling.build\"\nDEFAULT_BUILD_DIRECTORY = \"dist\"\nDEFAULT_BUILD_SCRIPT = \"hatch_build.py\"\nDEFAULT_CONFIG_FILE = \"hatch.toml\"\n\n\nclass BuildEnvVars:\n    REQUESTED_TARGETS = \"HATCH_BUILD_REQUESTED_TARGETS\"\n    LOCATION = \"HATCH_BUILD_LOCATION\"\n    HOOKS_ONLY = \"HATCH_BUILD_HOOKS_ONLY\"\n    NO_HOOKS = \"HATCH_BUILD_NO_HOOKS\"\n    HOOKS_ENABLE = \"HATCH_BUILD_HOOKS_ENABLE\"\n    HOOK_ENABLE_PREFIX = \"HATCH_BUILD_HOOK_ENABLE_\"\n    CLEAN = \"HATCH_BUILD_CLEAN\"\n    CLEAN_HOOKS_AFTER = \"HATCH_BUILD_CLEAN_HOOKS_AFTER\"\n"
  },
  {
    "path": "src/hatch/project/core.py",
    "content": "from __future__ import annotations\n\nimport re\nfrom collections import defaultdict\nfrom collections.abc import Generator\nfrom contextlib import contextmanager\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, Any, cast\n\nfrom hatch.project.env import EnvironmentMetadata\nfrom hatch.utils.fs import Path\nfrom hatch.utils.runner import ExecutionContext\n\nif TYPE_CHECKING:\n    from collections.abc import Generator\n\n    from hatch.cli.application import Application\n    from hatch.config.model import RootConfig\n    from hatch.env.plugin.interface import EnvironmentInterface\n    from hatch.project.frontend.core import BuildFrontend\n\n\nclass Project:\n    def __init__(self, path: Path, *, name: str | None = None, config=None, locate: bool = True):\n        self._path = path\n\n        # From app config\n        self.chosen_name = name\n\n        # Lazily attach the current app\n        self.__app: Application | None = None\n\n        # Location of pyproject.toml\n        self._project_file_path: Path | None = None\n\n        self._root_searched = False\n        self._root: Path | None = None\n        self._raw_config = config\n        self._plugin_manager = None\n        self._metadata = None\n        self._config = None\n\n        self._explicit_path: Path | None = None if locate else path\n        self.current_member_path: Path | None = None\n\n    @property\n    def plugin_manager(self):\n        if self._plugin_manager is None:\n            from hatch.plugin.manager import PluginManager\n\n            self._plugin_manager = PluginManager()\n\n        return self._plugin_manager\n\n    @property\n    def config(self):\n        if self._config is None:\n            from hatch.project.config import ProjectConfig\n\n            self._config = ProjectConfig(self.location, self.metadata.hatch.config, self.plugin_manager)\n\n        return self._config\n\n    @property\n    def root(self) -> Path | None:\n        if not self._root_searched:\n            self._root = self.find_project_root()\n            self._root_searched = True\n\n        return self._root\n\n    @property\n    def location(self) -> Path:\n        return self._explicit_path or self.root or self._path\n\n    def set_path(self, path: Path) -> None:\n        self._explicit_path = path\n\n    def set_app(self, app: Application) -> None:\n        self.__app = app\n\n    @cached_property\n    def app(self) -> Application:\n        if self.__app is None:  # no cov\n            message = \"The application has not been set\"\n            raise RuntimeError(message)\n\n        from hatch.cli.application import Application\n\n        return cast(Application, self.__app)\n\n    @cached_property\n    def build_env(self) -> EnvironmentInterface:\n        # Prevent the default environment from being used as a builder environment\n        environment = self.get_environment(\"hatch-build\" if self.app.env == \"default\" else self.app.env)\n        if not environment.builder:\n            self.app.abort(f\"Environment `{environment.name}` is not a builder environment\")\n\n        return environment\n\n    @cached_property\n    def build_frontend(self) -> BuildFrontend:\n        from hatch.project.frontend.core import BuildFrontend\n\n        return BuildFrontend(self, self.build_env)\n\n    @cached_property\n    def env_metadata(self) -> EnvironmentMetadata:\n        return EnvironmentMetadata(self.app.data_dir / \"env\" / \".metadata\", self.location)\n\n    @cached_property\n    def dependency_groups(self) -> dict[str, Any]:\n        \"\"\"\n        https://peps.python.org/pep-0735/\n        \"\"\"\n        from hatch.utils.metadata import normalize_project_name\n\n        dependency_groups = self.raw_config.get(\"dependency-groups\", {})\n\n        if not isinstance(dependency_groups, dict):\n            message = \"Field `dependency-groups` must be a table\"\n            raise TypeError(message)\n\n        original_names = defaultdict(list)\n        normalized_groups = {}\n\n        for group_name, value in dependency_groups.items():\n            normed_group_name = normalize_project_name(group_name)\n            original_names[normed_group_name].append(group_name)\n            normalized_groups[normed_group_name] = value\n\n        errors = []\n        for normed_name, names in original_names.items():\n            if len(names) > 1:\n                errors.append(f\"{normed_name} ({', '.join(names)})\")\n        if errors:\n            msg = f\"Field `dependency-groups` contains duplicate names: {', '.join(errors)}\"\n            raise ValueError(msg)\n\n        return normalized_groups\n\n    def get_environment(self, env_name: str | None = None) -> EnvironmentInterface:\n        if env_name is None:\n            env_name = self.app.env\n\n        if env_name in self.config.internal_envs:\n            config = self.config.internal_envs[env_name]\n        elif env_name in self.config.envs:\n            config = self.config.envs[env_name]\n        else:\n            self.app.abort(f\"Unknown environment: {env_name}\")\n\n        environment_type = config[\"type\"]\n        environment_class = self.plugin_manager.environment.get(environment_type)\n        if environment_class is None:\n            self.app.abort(f\"Environment `{env_name}` has unknown type: {environment_type}\")\n\n        from hatch.env.internal import is_isolated_environment\n\n        if self.location.is_file():\n            data_directory = isolated_data_directory = self.app.data_dir / \"env\" / environment_type / \".scripts\"\n        elif is_isolated_environment(env_name, config):\n            data_directory = isolated_data_directory = self.app.data_dir / \"env\" / \".internal\" / env_name\n        else:\n            data_directory = self.app.get_env_directory(environment_type)\n            isolated_data_directory = self.app.data_dir / \"env\" / environment_type\n\n        self.config.finalize_env_overrides(environment_class.get_option_types())\n\n        return environment_class(\n            self.location,\n            self.metadata,\n            env_name,\n            config,\n            self.config.matrix_variables.get(env_name, {}),\n            data_directory,\n            isolated_data_directory,\n            self.app.platform,\n            self.app.verbosity,\n            self.app,\n        )\n\n    @staticmethod\n    @contextmanager\n    def managed_environment(\n        environment: EnvironmentInterface, *, keep_env: bool = False\n    ) -> Generator[EnvironmentInterface, None, None]:\n        \"\"\"Context manager that removes environment on error unless keep_env is True.\"\"\"\n        try:\n            yield environment\n        except Exception:\n            if not keep_env and environment.exists():\n                environment.remove()\n            raise\n\n    # Ensure that this method is clearly written since it is\n    # used for documenting the life cycle of environments.\n    def prepare_environment(self, environment: EnvironmentInterface, *, keep_env: bool):\n        if not environment.exists():\n            with self.managed_environment(environment, keep_env=keep_env):\n                self.env_metadata.reset(environment)\n\n                with environment.app_status_creation():\n                    environment.create()\n\n                if not environment.skip_install:\n                    if environment.pre_install_commands:\n                        with environment.app_status_pre_installation():\n                            self.app.run_shell_commands(\n                                ExecutionContext(\n                                    environment,\n                                    shell_commands=environment.pre_install_commands,\n                                    source=\"pre-install\",\n                                    show_code_on_error=True,\n                                )\n                            )\n\n                    with environment.app_status_project_installation():\n                        if environment.dev_mode:\n                            environment.install_project_dev_mode()\n                        else:\n                            environment.install_project()\n\n                    if environment.post_install_commands:\n                        with environment.app_status_post_installation():\n                            self.app.run_shell_commands(\n                                ExecutionContext(\n                                    environment,\n                                    shell_commands=environment.post_install_commands,\n                                    source=\"post-install\",\n                                    show_code_on_error=True,\n                                )\n                            )\n\n        with environment.app_status_dependency_state_check():\n            new_dep_hash = environment.dependency_hash()\n\n        current_dep_hash = self.env_metadata.dependency_hash(environment)\n        if new_dep_hash != current_dep_hash:\n            with environment.app_status_dependency_installation_check():\n                dependencies_in_sync = environment.dependencies_in_sync()\n\n            if not dependencies_in_sync:\n                with environment.app_status_dependency_synchronization():\n                    environment.sync_dependencies()\n                    new_dep_hash = environment.dependency_hash()\n\n            self.env_metadata.update_dependency_hash(environment, new_dep_hash)\n\n    def prepare_build_environment(self, *, targets: list[str] | None = None, keep_env: bool = False) -> None:\n        from hatch.project.constants import BUILD_BACKEND, BuildEnvVars\n        from hatch.utils.structures import EnvVars\n\n        if targets is None:\n            targets = [\"wheel\"]\n\n        env_vars = {BuildEnvVars.REQUESTED_TARGETS: \" \".join(sorted(targets))}\n        build_backend = self.metadata.build.build_backend\n        with self.location.as_cwd(), self.build_env.get_env_vars(), EnvVars(env_vars):\n            if not self.build_env.exists():\n                try:\n                    self.build_env.check_compatibility()\n                except Exception as e:  # noqa: BLE001\n                    self.app.abort(f\"Environment `{self.build_env.name}` is incompatible: {e}\")\n\n            self.prepare_environment(self.build_env, keep_env=keep_env)\n\n            additional_dependencies: list[str] = []\n            with self.app.status(\"Inspecting build dependencies\"):\n                if build_backend != BUILD_BACKEND:\n                    for target in targets:\n                        if target == \"sdist\":\n                            additional_dependencies.extend(self.build_frontend.get_requires(\"sdist\"))\n                        elif target == \"wheel\":\n                            additional_dependencies.extend(self.build_frontend.get_requires(\"wheel\"))\n                        else:\n                            self.app.abort(f\"Target `{target}` is not supported by `{build_backend}`\")\n                else:\n                    required_build_deps = self.build_frontend.hatch.get_required_build_deps(targets)\n                    if required_build_deps:\n                        with self.metadata.context.apply_context(self.build_env.context):\n                            additional_dependencies.extend(\n                                self.metadata.context.format(dep) for dep in required_build_deps\n                            )\n\n            if additional_dependencies:\n                from hatch.dep.core import Dependency\n\n                self.build_env.additional_dependencies.extend(map(Dependency, additional_dependencies))\n                with self.build_env.app_status_dependency_synchronization():\n                    self.build_env.sync_dependencies()\n\n    def get_dependencies(self) -> tuple[list[str], dict[str, list[str]]]:\n        dynamic_fields = {\"dependencies\", \"optional-dependencies\"}\n        if not dynamic_fields.intersection(self.metadata.dynamic):\n            dependencies: list[str] = self.metadata.core_raw_metadata.get(\"dependencies\", [])\n            features: dict[str, list[str]] = self.metadata.core_raw_metadata.get(\"optional-dependencies\", {})\n            return dependencies, features\n\n        from hatch.project.constants import BUILD_BACKEND\n\n        self.prepare_build_environment()\n        build_backend = self.metadata.build.build_backend\n        with self.location.as_cwd(), self.build_env.get_env_vars():\n            if build_backend != BUILD_BACKEND:\n                project_metadata = self.build_frontend.get_core_metadata()\n            else:\n                project_metadata = self.build_frontend.hatch.get_core_metadata()\n\n        dynamic_dependencies: list[str] = project_metadata.get(\"dependencies\", [])\n        dynamic_features: dict[str, list[str]] = project_metadata.get(\"optional-dependencies\", {})\n\n        return dynamic_dependencies, dynamic_features\n\n    @cached_property\n    def has_static_dependencies(self) -> bool:\n        dynamic_fields = {\"dependencies\", \"optional-dependencies\"}\n        return not dynamic_fields.intersection(self.metadata.dynamic)\n\n    def expand_environments(self, env_name: str) -> list[str]:\n        if env_name in self.config.internal_matrices:\n            return list(self.config.internal_matrices[env_name][\"envs\"])\n\n        if env_name in self.config.matrices:\n            return list(self.config.matrices[env_name][\"envs\"])\n\n        if env_name in self.config.internal_envs:\n            return [env_name]\n\n        if env_name in self.config.envs:\n            return [env_name]\n\n        return []\n\n    @classmethod\n    def from_config(cls, config: RootConfig, project: str) -> Project | None:\n        # Disallow empty strings\n        if not project:\n            return None\n\n        if project in config.projects:\n            location = config.projects[project].location\n            if location:\n                return cls(Path(location).resolve(), name=project)\n        else:\n            for project_dir in config.dirs.project:\n                if not project_dir:\n                    continue\n\n                location = Path(project_dir, project)\n                if location.is_dir():\n                    return cls(Path(location).resolve(), name=project)\n\n        return None\n\n    def find_project_root(self) -> Path | None:\n        path = self._path\n\n        while True:\n            possible_file = path.joinpath(\"pyproject.toml\")\n            if possible_file.is_file():\n                self._project_file_path = possible_file\n                return path\n\n            if path.joinpath(\"setup.py\").is_file():\n                return path\n\n            new_path = path.parent\n            if new_path == path:\n                return None\n\n            path = new_path\n\n    @contextmanager\n    def ensure_cwd(self) -> Generator[Path, None, None]:\n        cwd = Path.cwd()\n        location = self.location\n        if location.is_file() or cwd == location or location in cwd.parents:\n            yield cwd\n        else:\n            with location.as_cwd():\n                yield location\n\n    @staticmethod\n    def canonicalize_name(name: str, *, strict=True) -> str:\n        if strict:\n            return re.sub(r\"[-_.]+\", \"-\", name).lower()\n\n        # Used for creating new projects\n        return re.sub(r\"[-_. ]+\", \"-\", name).lower()\n\n    @property\n    def metadata(self):\n        if self._metadata is None:\n            from hatchling.metadata.core import ProjectMetadata\n\n            self._metadata = ProjectMetadata(self.location, self.plugin_manager, self.raw_config)\n\n        return self._metadata\n\n    @property\n    def raw_config(self):\n        if self._raw_config is None:\n            if self.root is None or self._project_file_path is None:\n                # Assume no pyproject.toml e.g. environment management only\n                self._raw_config = {\"project\": {\"name\": self.location.name}}\n            else:\n                from hatch.utils.toml import load_toml_file\n\n                raw_config = load_toml_file(str(self._project_file_path))\n                # Assume environment management only\n                if \"project\" not in raw_config:\n                    raw_config[\"project\"] = {\"name\": self.location.name}\n\n                self._raw_config = raw_config\n\n        return self._raw_config\n\n    def save_config(self, config):\n        import tomlkit\n\n        with open(str(self._project_file_path), \"w\", encoding=\"utf-8\") as f:\n            f.write(tomlkit.dumps(config))\n\n    @staticmethod\n    def initialize(project_file_path, template_config):\n        import tomlkit\n\n        with open(str(project_file_path), encoding=\"utf-8\") as f:\n            raw_config = tomlkit.parse(f.read())\n\n        build_system_config = raw_config.setdefault(\"build-system\", {})\n\n        build_system_config.clear()\n        build_system_config[\"requires\"] = [\"hatchling\"]\n        build_system_config[\"build-backend\"] = \"hatchling.build\"\n\n        project_config = raw_config.get(\"project\")\n        if project_config is None:\n            raw_config[\"project\"] = project_config = {}\n\n        project_name = project_config.get(\"name\")\n        if not project_name:\n            project_config[\"name\"] = template_config[\"project_name_normalized\"]\n\n        project_description = project_config.get(\"description\")\n        if not project_description:\n            project_config[\"description\"] = template_config[\"description\"]\n\n        project_config[\"dynamic\"] = [\"version\"]\n\n        tool_config = raw_config.get(\"tool\")\n        if tool_config is None:\n            raw_config[\"tool\"] = tool_config = {}\n\n        hatch_config = tool_config.get(\"hatch\")\n        if hatch_config is None:\n            tool_config[\"hatch\"] = hatch_config = {}\n\n        version_config = hatch_config.get(\"version\")\n        if version_config is None:\n            hatch_config[\"version\"] = version_config = {}\n\n        version_config.clear()\n        version_config[\"path\"] = f\"{template_config['package_name']}/__init__.py\"\n\n        with open(str(project_file_path), \"w\", encoding=\"utf-8\") as f:\n            f.write(tomlkit.dumps(raw_config))\n"
  },
  {
    "path": "src/hatch/project/env.py",
    "content": "from __future__ import annotations\n\nfrom functools import cached_property\nfrom os import environ\nfrom typing import TYPE_CHECKING, Any\n\nfrom hatch.utils.platform import get_platform_name\n\nif TYPE_CHECKING:\n    from hatch.env.plugin.interface import EnvironmentInterface\n    from hatch.utils.fs import Path\n\nRESERVED_OPTIONS = {\n    \"builder\": bool,\n    \"dependencies\": list,\n    \"dependency-groups\": list,\n    \"extra-dependencies\": list,\n    \"dev-mode\": bool,\n    \"env-exclude\": list,\n    \"env-include\": list,\n    \"env-vars\": dict,\n    \"features\": list,\n    \"matrix-name-format\": str,\n    \"platforms\": list,\n    \"post-install-commands\": list,\n    \"pre-install-commands\": list,\n    \"python\": str,\n    \"scripts\": dict,\n    \"skip-install\": bool,\n    \"type\": str,\n    \"workspace\": dict,\n}\n\n\ndef apply_overrides(env_name, source, condition, condition_value, options, new_config, option_types=None):\n    if option_types is None:\n        option_types = RESERVED_OPTIONS\n\n    for raw_option, data in options.items():\n        _, separator, option = raw_option.rpartition(\"set-\")\n        overwrite = bool(separator)\n\n        # Prevent manipulation of reserved options\n        if option_types is not RESERVED_OPTIONS and option in RESERVED_OPTIONS:\n            continue\n\n        override_type = option_types.get(option)\n        if option == \"workspace\":\n            _apply_override_to_workspace(\n                env_name, option, data, source, condition, condition_value, new_config, overwrite\n            )\n        elif override_type in TYPE_OVERRIDES:\n            TYPE_OVERRIDES[override_type](\n                env_name, option, data, source, condition, condition_value, new_config, overwrite\n            )\n        elif isinstance(data, dict) and \"value\" in data:\n            if _resolve_condition(env_name, option, source, condition, condition_value, data):\n                new_config[option] = data[\"value\"]\n        elif option_types is not RESERVED_OPTIONS:\n            message = (\n                f\"Untyped option `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                f\"must be defined as a table with a `value` key\"\n            )\n            raise ValueError(message)\n\n\ndef _apply_override_to_mapping(env_name, option, data, source, condition, condition_value, new_config, overwrite):\n    new_mapping = {}\n    if isinstance(data, str):\n        key, separator, value = data.partition(\"=\")\n        if not separator:\n            value = condition_value\n        new_mapping[key] = value\n    elif isinstance(data, list):\n        for i, entry in enumerate(data, 1):\n            if isinstance(entry, str):\n                key, separator, value = entry.partition(\"=\")\n                if not separator:\n                    value = condition_value\n                new_mapping[key] = value\n            elif isinstance(entry, dict):\n                if \"key\" not in entry:\n                    message = (\n                        f\"Entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                        f\"must have an option named `key`\"\n                    )\n                    raise ValueError(message)\n                key = entry[\"key\"]\n                if not isinstance(key, str):\n                    message = (\n                        f\"Option `key` in entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                        f\"{condition}.{option}` must be a string\"\n                    )\n                    raise TypeError(message)\n\n                if not key:\n                    message = (\n                        f\"Option `key` in entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                        f\"{condition}.{option}` cannot be an empty string\"\n                    )\n                    raise ValueError(message)\n\n                value = entry.get(\"value\", condition_value)\n                if not isinstance(value, str):\n                    message = (\n                        f\"Option `value` in entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                        f\"{condition}.{option}` must be a string\"\n                    )\n                    raise TypeError(message)\n                if _resolve_condition(env_name, option, source, condition, condition_value, entry, i):\n                    new_mapping[key] = value\n            else:\n                message = (\n                    f\"Entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                    f\"must be a string or an inline table\"\n                )\n                raise TypeError(message)\n    else:\n        message = (\n            f\"Field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` must be a string or an array\"\n        )\n        raise TypeError(message)\n\n    if overwrite:\n        new_config[option] = new_mapping\n    elif option in new_config:\n        new_config[option].update(new_mapping)\n    elif new_mapping:\n        new_config[option] = new_mapping\n\n\ndef _apply_override_to_array(env_name, option, data, source, condition, condition_value, new_config, overwrite):\n    if not isinstance(data, list):\n        message = f\"Field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` must be an array\"\n        raise TypeError(message)\n\n    new_array = []\n    for i, entry in enumerate(data, 1):\n        if isinstance(entry, str):\n            new_array.append(entry)\n        elif isinstance(entry, dict):\n            if \"value\" not in entry:\n                message = (\n                    f\"Entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                    f\"must have an option named `value`\"\n                )\n                raise ValueError(message)\n            value = entry[\"value\"]\n            if not isinstance(value, str):\n                message = (\n                    f\"Option `value` in entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                    f\"{condition}.{option}` must be a string\"\n                )\n                raise TypeError(message)\n\n            if not value:\n                message = (\n                    f\"Option `value` in entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                    f\"{condition}.{option}` cannot be an empty string\"\n                )\n                raise ValueError(message)\n            if _resolve_condition(env_name, option, source, condition, condition_value, entry, i):\n                new_array.append(value)\n        else:\n            message = (\n                f\"Entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                f\"must be a string or an inline table\"\n            )\n            raise TypeError(message)\n\n    if overwrite:\n        new_config[option] = new_array\n    elif option in new_config:\n        new_config[option].extend(new_array)\n    elif new_array:\n        new_config[option] = new_array\n\n\ndef _apply_override_to_string(\n    env_name,\n    option,\n    data,\n    source,\n    condition,\n    condition_value,\n    new_config,\n    overwrite,  # noqa: ARG001\n):\n    if isinstance(data, str):\n        new_config[option] = data\n    elif isinstance(data, dict):\n        if \"value\" not in data:\n            message = (\n                f\"Field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                f\"must have an option named `value`\"\n            )\n            raise ValueError(message)\n        value = data[\"value\"]\n        if not isinstance(value, str):\n            message = (\n                f\"Option `value` in field `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                f\"{condition}.{option}` must be a string\"\n            )\n            raise TypeError(message)\n        if _resolve_condition(env_name, option, source, condition, condition_value, data):\n            new_config[option] = value\n    elif isinstance(data, list):\n        for i, entry in enumerate(data, 1):\n            if isinstance(entry, str):\n                new_config[option] = entry\n                break\n\n            if isinstance(entry, dict):\n                if \"value\" not in entry:\n                    message = (\n                        f\"Entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                        f\"must have an option named `value`\"\n                    )\n                    raise ValueError(message)\n                value = entry[\"value\"]\n                if not isinstance(value, str):\n                    message = (\n                        f\"Option `value` in entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                        f\"{condition}.{option}` must be a string\"\n                    )\n                    raise TypeError(message)\n                if _resolve_condition(env_name, option, source, condition, condition_value, entry, i):\n                    new_config[option] = value\n                    break\n            else:\n                message = (\n                    f\"Entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                    f\"must be a string or an inline table\"\n                )\n                raise TypeError(message)\n    else:\n        message = (\n            f\"Field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n            f\"must be a string, inline table, or an array\"\n        )\n        raise TypeError(message)\n\n\ndef _apply_override_to_boolean(\n    env_name,\n    option,\n    data,\n    source,\n    condition,\n    condition_value,\n    new_config,\n    overwrite,  # noqa: ARG001\n):\n    if isinstance(data, bool):\n        new_config[option] = data\n    elif isinstance(data, dict):\n        if \"value\" not in data:\n            message = (\n                f\"Field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                f\"must have an option named `value`\"\n            )\n            raise ValueError(message)\n        value = data[\"value\"]\n        if not isinstance(value, bool):\n            message = (\n                f\"Option `value` in field `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                f\"{condition}.{option}` must be a boolean\"\n            )\n            raise TypeError(message)\n        if _resolve_condition(env_name, option, source, condition, condition_value, data):\n            new_config[option] = value\n    elif isinstance(data, list):\n        for i, entry in enumerate(data, 1):\n            if isinstance(entry, bool):\n                new_config[option] = entry\n                break\n\n            if isinstance(entry, dict):\n                if \"value\" not in entry:\n                    message = (\n                        f\"Entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                        f\"must have an option named `value`\"\n                    )\n                    raise ValueError(message)\n                value = entry[\"value\"]\n                if not isinstance(value, bool):\n                    message = (\n                        f\"Option `value` in entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                        f\"{condition}.{option}` must be a boolean\"\n                    )\n                    raise TypeError(message)\n                if _resolve_condition(env_name, option, source, condition, condition_value, entry, i):\n                    new_config[option] = value\n                    break\n            else:\n                message = (\n                    f\"Entry #{i} in field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n                    f\"must be a boolean or an inline table\"\n                )\n                raise TypeError(message)\n    else:\n        message = (\n            f\"Field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` \"\n            f\"must be a boolean, inline table, or an array\"\n        )\n        raise TypeError(message)\n\n\ndef _apply_override_to_workspace(env_name, option, data, source, condition, condition_value, new_config, overwrite):\n    \"\"\"Handle workspace dict with nested members/exclude/parallel.\"\"\"\n    if not isinstance(data, dict):\n        message = f\"Field `tool.hatch.envs.{env_name}.overrides.{source}.{condition}.{option}` must be a table\"\n        raise TypeError(message)\n\n    # Get or create workspace dict\n    workspace = {} if overwrite else new_config.setdefault(option, {})\n\n    for key, value in data.items():\n        if key in {\"members\", \"exclude\"}:\n            # Delegate to array handler - pass workspace dict\n            _apply_override_to_array(env_name, key, value, source, condition, condition_value, workspace, overwrite)\n        elif key == \"parallel\":\n            # Delegate to boolean handler - pass workspace dict\n            _apply_override_to_boolean(env_name, key, value, source, condition, condition_value, workspace, overwrite)\n        else:\n            message = f\"Unknown workspace option: {key}\"\n            raise ValueError(message)\n\n    # Update new_config with the workspace dict\n    if overwrite or workspace:\n        new_config[option] = workspace\n\n\ndef _resolve_condition(env_name, option, source, condition, condition_value, condition_config, condition_index=None):\n    location = \"field\" if condition_index is None else f\"entry #{condition_index} in field\"\n\n    if \"if\" in condition_config:\n        allowed_values = condition_config[\"if\"]\n        if not isinstance(allowed_values, list):\n            message = (\n                f\"Option `if` in {location} `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                f\"{condition}.{option}` must be an array\"\n            )\n            raise TypeError(message)\n\n        if condition_value not in allowed_values:\n            return False\n\n    if \"platform\" in condition_config:\n        allowed_platforms = condition_config[\"platform\"]\n        if not isinstance(allowed_platforms, list):\n            message = (\n                f\"Option `platform` in {location} `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                f\"{condition}.{option}` must be an array\"\n            )\n            raise TypeError(message)\n\n        for i, entry in enumerate(allowed_platforms, 1):\n            if not isinstance(entry, str):\n                message = (\n                    f\"Item #{i} in option `platform` in {location} `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                    f\"{condition}.{option}` must be a string\"\n                )\n                raise TypeError(message)\n\n        if get_platform_name() not in allowed_platforms:\n            return False\n\n    if \"env\" in condition_config:\n        env_vars = condition_config[\"env\"]\n        if not isinstance(env_vars, list):\n            message = (\n                f\"Option `env` in {location} `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                f\"{condition}.{option}` must be an array\"\n            )\n            raise TypeError(message)\n\n        required_env_vars = {}\n        for i, entry in enumerate(env_vars, 1):\n            if not isinstance(entry, str):\n                message = (\n                    f\"Item #{i} in option `env` in {location} `tool.hatch.envs.{env_name}.overrides.{source}.\"\n                    f\"{condition}.{option}` must be a string\"\n                )\n                raise TypeError(message)\n\n            # Allow matching empty strings\n            if \"=\" in entry:\n                env_var, _, value = entry.partition(\"=\")\n                required_env_vars[env_var] = value\n            else:\n                required_env_vars[entry] = None\n\n        for env_var, value in required_env_vars.items():\n            if env_var not in environ or (value is not None and value != environ[env_var]):\n                return False\n\n    return True\n\n\nTYPE_OVERRIDES = {\n    dict: _apply_override_to_mapping,\n    list: _apply_override_to_array,\n    str: _apply_override_to_string,\n    bool: _apply_override_to_boolean,\n}\n\n\nclass EnvironmentMetadata:\n    def __init__(self, data_dir: Path, project_path: Path):\n        self.__data_dir = data_dir\n        self.__project_path = project_path\n\n    def dependency_hash(self, environment: EnvironmentInterface) -> str:\n        return self._read(environment).get(\"dependency_hash\", \"\")\n\n    def update_dependency_hash(self, environment: EnvironmentInterface, dependency_hash: str) -> None:\n        metadata = self._read(environment)\n        metadata[\"dependency_hash\"] = dependency_hash\n        self._write(environment, metadata)\n\n    def reset(self, environment: EnvironmentInterface) -> None:\n        self._metadata_file(environment).unlink(missing_ok=True)\n\n    def _read(self, environment: EnvironmentInterface) -> dict[str, Any]:\n        import json\n\n        metadata_file = self._metadata_file(environment)\n        if not metadata_file.is_file():\n            return {}\n\n        return json.loads(metadata_file.read_text())\n\n    def _write(self, environment: EnvironmentInterface, metadata: dict[str, Any]) -> None:\n        import json\n\n        metadata_file = self._metadata_file(environment)\n        metadata_file.parent.ensure_dir_exists()\n        metadata_file.write_text(json.dumps(metadata))\n\n    def _metadata_file(self, environment: EnvironmentInterface) -> Path:\n        from hatch.env.internal import is_isolated_environment\n\n        if is_isolated_environment(environment.name, environment.config):\n            return self.__data_dir / \".internal\" / f\"{environment.name}.json\"\n\n        return self._storage_dir / environment.config[\"type\"] / f\"{environment.name}.json\"\n\n    @cached_property\n    def _storage_dir(self) -> Path:\n        return self.__data_dir / self.__project_path.id\n"
  },
  {
    "path": "src/hatch/project/frontend/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/project/frontend/core.py",
    "content": "from __future__ import annotations\n\nimport json\nfrom functools import cache\nfrom typing import TYPE_CHECKING, Any, Literal\n\nfrom hatch.utils.fs import Path\nfrom hatch.utils.runner import ExecutionContext\n\nif TYPE_CHECKING:\n    from hatch.env.plugin.interface import EnvironmentInterface\n    from hatch.project.core import Project\n\n\nclass BuildFrontend:\n    def __init__(self, project: Project, env: EnvironmentInterface) -> None:\n        self.__project = project\n        self.__env = env\n        self.__scripts = StandardBuildFrontendScripts(self.__project, self.__env)\n        self.__hatch = HatchBuildFrontend(self.__project, self.__env)\n\n    @property\n    def scripts(self) -> StandardBuildFrontendScripts:\n        return self.__scripts\n\n    @property\n    def hatch(self) -> HatchBuildFrontend:\n        return self.__hatch\n\n    def build_sdist(self, directory: Path) -> Path:\n        with self.__env.fs_context() as fs_context:\n            output_context = fs_context.join(\"output\")\n            output_context.local_path.ensure_dir_exists()\n            script = self.scripts.build_sdist(project_root=self.__env.project_root, output_dir=output_context.env_path)\n\n            script_context = fs_context.join(\"build_sdist.py\")\n            script_context.local_path.parent.ensure_dir_exists()\n            script_context.local_path.write_text(script)\n            script_context.sync_env()\n\n            context = ExecutionContext(self.__env)\n            context.add_shell_command([\"python\", \"-u\", script_context.env_path])\n            self.__env.app.execute_context(context)\n            output_context.sync_local()\n\n            output_path = output_context.local_path / \"output.json\"\n            output = json.loads(output_path.read_text())\n\n            work_dir = output_context.local_path / \"work\"\n            artifact_path = Path(work_dir / output[\"return_val\"])\n            artifact_path.move(directory)\n            return directory / artifact_path.name\n\n    def build_wheel(self, directory: Path) -> Path:\n        with self.__env.fs_context() as fs_context:\n            output_context = fs_context.join(\"output\")\n            output_context.local_path.ensure_dir_exists()\n            script = self.scripts.build_wheel(project_root=self.__env.project_root, output_dir=output_context.env_path)\n\n            script_context = fs_context.join(\"build_wheel.py\")\n            script_context.local_path.parent.ensure_dir_exists()\n            script_context.local_path.write_text(script)\n            script_context.sync_env()\n\n            context = ExecutionContext(self.__env)\n            context.add_shell_command([\"python\", \"-u\", script_context.env_path])\n            self.__env.app.execute_context(context)\n            output_context.sync_local()\n\n            output_path = output_context.local_path / \"output.json\"\n            output = json.loads(output_path.read_text())\n\n            work_dir = output_context.local_path / \"work\"\n            artifact_path = Path(work_dir / output[\"return_val\"])\n            artifact_path.move(directory)\n            return directory / artifact_path.name\n\n    def get_requires(self, build: Literal[\"sdist\", \"wheel\", \"editable\"]) -> list[str]:\n        with self.__env.fs_context() as fs_context:\n            output_context = fs_context.join(\"output\")\n            output_context.local_path.ensure_dir_exists()\n            script = self.scripts.get_requires(\n                project_root=self.__env.project_root, output_dir=output_context.env_path, build=build\n            )\n\n            script_context = fs_context.join(f\"get_requires_{build}.py\")\n            script_context.local_path.parent.ensure_dir_exists()\n            script_context.local_path.write_text(script)\n            script_context.sync_env()\n\n            context = ExecutionContext(self.__env)\n            context.add_shell_command([\"python\", \"-u\", script_context.env_path])\n            self.__env.app.execute_context(context)\n            output_context.sync_local()\n\n            output_path = output_context.local_path / \"output.json\"\n            output = json.loads(output_path.read_text())\n            return output[\"return_val\"]\n\n    def get_core_metadata(self, *, editable: bool = False) -> dict[str, Any]:\n        from hatchling.metadata.spec import project_metadata_from_core_metadata\n\n        with self.__env.fs_context() as fs_context:\n            output_context = fs_context.join(\"output\")\n            output_context.local_path.ensure_dir_exists()\n            script = self.scripts.prepare_metadata(\n                project_root=self.__env.project_root, output_dir=output_context.env_path, editable=editable\n            )\n\n            script_context = fs_context.join(\"get_core_metadata.py\")\n            script_context.local_path.parent.ensure_dir_exists()\n            script_context.local_path.write_text(script)\n            script_context.sync_env()\n\n            context = ExecutionContext(self.__env)\n            context.add_shell_command([\"python\", \"-u\", script_context.env_path])\n            self.__env.app.execute_context(context)\n            output_context.sync_local()\n\n            output_path = output_context.local_path / \"output.json\"\n            output = json.loads(output_path.read_text())\n\n            work_dir = output_context.local_path / \"work\"\n            metadata_file = Path(work_dir) / output[\"return_val\"] / \"METADATA\"\n            return project_metadata_from_core_metadata(metadata_file.read_text())\n\n\nclass HatchBuildFrontend:\n    def __init__(self, project: Project, env: EnvironmentInterface) -> None:\n        self.__project = project\n        self.__env = env\n        self.__scripts = HatchBuildFrontendScripts(self.__project, self.__env)\n\n    @property\n    def scripts(self) -> HatchBuildFrontendScripts:\n        return self.__scripts\n\n    def get_build_deps(self, targets: list[str]) -> list[str]:\n        with self.__env.fs_context() as fs_context:\n            output_context = fs_context.join(\"output\")\n            output_context.local_path.ensure_dir_exists()\n            script = self.scripts.get_build_deps(\n                project_root=self.__env.project_root, output_dir=output_context.env_path, targets=targets\n            )\n\n            script_context = fs_context.join(f\"get_build_deps_{'_'.join(targets)}.py\")\n            script_context.local_path.parent.ensure_dir_exists()\n            script_context.local_path.write_text(script)\n            script_context.sync_env()\n\n            context = ExecutionContext(self.__env)\n            context.add_shell_command([\"python\", \"-u\", script_context.env_path])\n            self.__env.app.execute_context(context)\n            output_context.sync_local()\n\n            output_path = output_context.local_path / \"output.json\"\n            output: list[str] = json.loads(output_path.read_text())\n            return output\n\n    def get_core_metadata(self) -> dict[str, Any]:\n        with self.__env.fs_context() as fs_context:\n            output_context = fs_context.join(\"output\")\n            output_context.local_path.ensure_dir_exists()\n            script = self.scripts.get_core_metadata(\n                project_root=self.__env.project_root, output_dir=output_context.env_path\n            )\n\n            script_context = fs_context.join(\"get_core_metadata.py\")\n            script_context.local_path.parent.ensure_dir_exists()\n            script_context.local_path.write_text(script)\n            script_context.sync_env()\n\n            context = ExecutionContext(self.__env)\n            context.add_shell_command([\"python\", \"-u\", script_context.env_path])\n            self.__env.app.execute_context(context)\n            output_context.sync_local()\n\n            output_path = output_context.local_path / \"output.json\"\n            output: dict[str, Any] = json.loads(output_path.read_text())\n            return output\n\n    def get_required_build_deps(self, targets: list[str]) -> list[str]:\n        target_dependencies: list[str] = []\n        hooks: set[str] = set()\n        for target in targets:\n            target_config = self.__project.config.build.target(target)\n            target_dependencies.extend(target_config.dependencies)\n            hooks.update(target_config.hook_config)\n\n        # Remove any build hooks that are known to not define any dependencies dynamically\n        hooks.difference_update((\n            # Built-in\n            \"version\",\n            # Popular third-party\n            \"vcs\",\n        ))\n\n        if hooks:\n            return self.get_build_deps(targets)\n\n        return target_dependencies\n\n\nclass BuildFrontendScripts:\n    def __init__(self, project: Project, env: EnvironmentInterface) -> None:\n        self._project = project\n        self._env = env\n\n    @staticmethod\n    def inject_data(script: str, data: dict[str, Any]) -> str:\n        # All scripts have a constant dictionary on top\n        return script.replace(\"{}\", repr(data), 1)\n\n\nclass StandardBuildFrontendScripts(BuildFrontendScripts):\n    def get_runner_script(\n        self,\n        *,\n        project_root: str,\n        output_dir: str,\n        hook: str,\n        kwargs: dict[str, Any],\n    ) -> str:\n        return self.inject_data(\n            runner_script(),\n            {\n                \"project_root\": project_root,\n                \"output_dir\": output_dir,\n                \"hook\": hook,\n                \"kwargs\": kwargs,\n                \"backend\": self._project.metadata.build.build_backend,\n                \"backend_path\": self._env.pathsep.join(self._project.metadata.build.backend_path),\n                \"hook_caller_script\": hook_caller_script(),\n            },\n        )\n\n    def get_requires(\n        self,\n        *,\n        project_root: str,\n        output_dir: str,\n        build: Literal[\"sdist\", \"wheel\", \"editable\"],\n    ) -> str:\n        return self.get_runner_script(\n            project_root=project_root,\n            output_dir=output_dir,\n            hook=f\"get_requires_for_build_{build}\",\n            kwargs={\"config_settings\": None},\n        )\n\n    def prepare_metadata(self, *, output_dir: str, project_root: str, editable: bool = False) -> str:\n        return self.get_runner_script(\n            project_root=project_root,\n            output_dir=output_dir,\n            hook=\"prepare_metadata_for_build_editable\" if editable else \"prepare_metadata_for_build_wheel\",\n            kwargs={\"work_dir\": \"metadata_directory\", \"config_settings\": None, \"_allow_fallback\": True},\n        )\n\n    def build_wheel(self, *, output_dir: str, project_root: str, editable: bool = False) -> str:\n        return self.get_runner_script(\n            project_root=project_root,\n            output_dir=output_dir,\n            hook=\"build_editable\" if editable else \"build_wheel\",\n            kwargs={\"work_dir\": \"wheel_directory\", \"config_settings\": None, \"metadata_directory\": None},\n        )\n\n    def build_sdist(self, *, output_dir: str, project_root: str) -> str:\n        return self.get_runner_script(\n            project_root=project_root,\n            output_dir=output_dir,\n            hook=\"build_sdist\",\n            kwargs={\"work_dir\": \"sdist_directory\", \"config_settings\": None},\n        )\n\n\nclass HatchBuildFrontendScripts(BuildFrontendScripts):\n    def get_build_deps(self, *, output_dir: str, project_root: str, targets: list[str]) -> str:\n        return self.inject_data(\n            hatch_build_deps_script(),\n            {\n                \"project_root\": project_root,\n                \"output_dir\": output_dir,\n                \"targets\": targets,\n            },\n        )\n\n    def get_core_metadata(self, *, output_dir: str, project_root: str) -> str:\n        return self.inject_data(\n            hatch_core_metadata_script(),\n            {\n                \"project_root\": project_root,\n                \"output_dir\": output_dir,\n            },\n        )\n\n\n@cache\ndef hook_caller_script() -> str:\n    from importlib.resources import files\n\n    script = files(\"pyproject_hooks._in_process\") / \"_in_process.py\"\n    return script.read_text(encoding=\"utf-8\")\n\n\n@cache\ndef runner_script() -> str:\n    from importlib.resources import files\n\n    script = files(\"hatch.project.frontend.scripts\") / \"standard.py\"\n    return script.read_text(encoding=\"utf-8\")\n\n\n@cache\ndef hatch_build_deps_script() -> str:\n    from importlib.resources import files\n\n    script = files(\"hatch.project.frontend.scripts\") / \"build_deps.py\"\n    return script.read_text(encoding=\"utf-8\")\n\n\n@cache\ndef hatch_core_metadata_script() -> str:\n    from importlib.resources import files\n\n    script = files(\"hatch.project.frontend.scripts\") / \"core_metadata.py\"\n    return script.read_text(encoding=\"utf-8\")\n"
  },
  {
    "path": "src/hatch/project/frontend/scripts/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/project/frontend/scripts/build_deps.py",
    "content": "from __future__ import annotations\n\nimport json\nimport os\n\nfrom hatchling.bridge.app import Application\nfrom hatchling.metadata.core import ProjectMetadata\nfrom hatchling.plugin.manager import PluginManager\n\nRUNNER: dict = {}\n\n\ndef main() -> None:\n    project_root: str = RUNNER[\"project_root\"]\n    output_dir: str = RUNNER[\"output_dir\"]\n    targets: list[str] = RUNNER[\"targets\"]\n\n    app = Application()\n    plugin_manager = PluginManager()\n    metadata = ProjectMetadata(project_root, plugin_manager)\n\n    dependencies: dict[str, None] = {}\n    for target_name in targets:\n        builder_class = plugin_manager.builder.get(target_name)\n        if builder_class is None:\n            continue\n\n        builder = builder_class(\n            project_root, plugin_manager=plugin_manager, metadata=metadata, app=app.get_safe_application()\n        )\n        for dependency in builder.config.dependencies:\n            dependencies[dependency] = None\n\n    output = json.dumps(list(dependencies))\n    with open(os.path.join(output_dir, \"output.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(output)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "src/hatch/project/frontend/scripts/core_metadata.py",
    "content": "from __future__ import annotations\n\nimport json\nimport os\n\nfrom hatchling.metadata.core import ProjectMetadata\nfrom hatchling.metadata.utils import resolve_metadata_fields\nfrom hatchling.plugin.manager import PluginManager\n\nRUNNER: dict = {}\n\n\ndef main() -> None:\n    project_root: str = RUNNER[\"project_root\"]\n    output_dir: str = RUNNER[\"output_dir\"]\n\n    project_metadata = ProjectMetadata(project_root, PluginManager())\n    core_metadata = resolve_metadata_fields(project_metadata)\n    for key, value in list(core_metadata.items()):\n        if not value:\n            core_metadata.pop(key)\n\n    output = json.dumps(core_metadata)\n    with open(os.path.join(output_dir, \"output.json\"), \"w\", encoding=\"utf-8\") as f:\n        f.write(output)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "src/hatch/project/frontend/scripts/standard.py",
    "content": "from __future__ import annotations\n\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\nfrom tempfile import TemporaryDirectory\n\nRUNNER: dict = {}\n\n\ndef main() -> int:\n    project_root: str = RUNNER[\"project_root\"]\n    output_dir: str = RUNNER[\"output_dir\"]\n    hook: str = RUNNER[\"hook\"]\n    kwargs: dict[str, str] = RUNNER[\"kwargs\"]\n    backend: str = RUNNER[\"backend\"]\n    backend_path: str = RUNNER[\"backend_path\"]\n    hook_caller_script: str = RUNNER[\"hook_caller_script\"]\n\n    with TemporaryDirectory() as d:\n        temp_dir = os.path.realpath(d)\n        control_dir = os.path.join(temp_dir, \"control\")\n        os.mkdir(control_dir)\n        input_file = os.path.join(control_dir, \"input.json\")\n        output_file = os.path.join(control_dir, \"output.json\")\n\n        env_vars = dict(os.environ)\n        env_vars[\"_PYPROJECT_HOOKS_BUILD_BACKEND\"] = backend\n        if backend_path:\n            env_vars[\"_PYPROJECT_HOOKS_BACKEND_PATH\"] = backend_path\n\n        if \"work_dir\" in kwargs:\n            work_dir = os.path.join(temp_dir, \"work\")\n            os.mkdir(work_dir)\n            kwargs[kwargs.pop(\"work_dir\")] = work_dir\n        else:\n            work_dir = \"\"\n\n        with open(input_file, \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps({\"kwargs\": kwargs}))\n\n        script_path = os.path.join(temp_dir, \"script.py\")\n        with open(script_path, \"w\", encoding=\"utf-8\") as f:\n            f.write(hook_caller_script)\n\n        process = subprocess.run(\n            [sys.executable, script_path, hook, str(control_dir)],\n            cwd=project_root,\n            env=env_vars,\n            check=False,\n        )\n        if process.returncode:\n            return process.returncode\n\n        with open(output_file, encoding=\"utf-8\") as f:\n            output = json.loads(f.read())\n\n        if output.get(\"no_backend\", False):\n            sys.stderr.write(f\"{output['traceback']}\\n{output['backend_error']}\\n\")\n            return 1\n\n        if output.get(\"unsupported\", False):\n            sys.stderr.write(output[\"traceback\"])\n            return 1\n\n        if output.get(\"hook_missing\", False):\n            sys.stderr.write(f\"Build backend API `{backend}` is missing hook: {output['missing_hook_name']}\\n\")\n            return 1\n\n        shutil.move(output_file, output_dir)\n        if work_dir:\n            shutil.move(work_dir, output_dir)\n\n    return 0\n\n\nif __name__ == \"__main__\":\n    code = main()\n    sys.stderr.flush()\n    os._exit(code)\n"
  },
  {
    "path": "src/hatch/project/utils.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any\n\nif TYPE_CHECKING:\n    from collections.abc import Iterable\n\n\ndef parse_script_command(command: str) -> tuple[str, str, bool]:\n    possible_script, _, args = command.partition(\" \")\n    if possible_script == \"-\":\n        ignore_exit_code = True\n        possible_script, _, args = args.partition(\" \")\n    else:\n        ignore_exit_code = False\n\n    return possible_script, args, ignore_exit_code\n\n\ndef format_script_commands(*, commands: list[str], args: str, ignore_exit_code: bool) -> Iterable[str]:\n    for command in commands:\n        if args:\n            yield f\"{command} {args}\"\n        elif ignore_exit_code and not command.startswith(\"- \"):\n            yield f\"- {command}\"\n        else:\n            yield command\n\n\ndef parse_inline_script_metadata(script: str) -> dict[str, Any] | None:\n    \"\"\"\n    https://peps.python.org/pep-0723/#reference-implementation\n    \"\"\"\n    import re\n\n    from hatch.utils.toml import load_toml_data\n\n    block_type = \"script\"\n    pattern = re.compile(r\"(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\\s(?P<content>(^#(| .*)$\\s)+)^# ///$\")\n    matches = list(filter(lambda m: m.group(\"type\") == block_type, pattern.finditer(script)))\n    if len(matches) > 1:\n        message = f\"Multiple inline metadata blocks found for type: {block_type}\"\n        raise ValueError(message)\n\n    if len(matches) == 1:\n        content = \"\".join(\n            line[2:] if line.startswith(\"# \") else line[1:]\n            for line in matches[0].group(\"content\").splitlines(keepends=True)\n        )\n        return load_toml_data(content)\n\n    return None\n"
  },
  {
    "path": "src/hatch/publish/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/publish/auth.py",
    "content": "from __future__ import annotations\n\nfrom hatch.utils.fs import Path\n\n\nclass AuthenticationCredentials:\n    def __init__(\n        self,\n        app,\n        cache_dir: Path,\n        options: dict,\n        repo: str,\n        repo_config: dict[str, str],\n    ):\n        self._app = app\n        self._pwu_path = cache_dir / \"previous_working_users.json\"\n        self._options = options\n        self._repo = repo\n        self._repo_config = repo_config\n\n        self.__username: str | None = None\n        self.__password: str | None = None\n        self.__username_was_read = False\n        self.__password_was_read = False\n        self.__pwu_data: dict[str, str] = {}\n\n    @property\n    def password(self) -> str:\n        if self.__password is None:\n            self.__password = self.__get_password()\n        return self.__password\n\n    @property\n    def username(self) -> str:\n        if self.__username is None:\n            self.__username = self.__get_username()\n        return self.__username\n\n    def __get_password(self) -> str:\n        # this method doesn't consider .pypirc as the __password attribute would have\n        # been set when it was looked up during username retrieval\n\n        password = self._options.get(\"auth\") or self._repo_config.get(\"auth\")\n        if password is not None:\n            return password\n\n        import keyring\n\n        keyring_service = self._repo_config[\"url\"]\n        password = keyring.get_password(keyring_service, self.username)\n        if password is not None:\n            return password\n\n        if self._options[\"no_prompt\"]:\n            self._app.abort(\"Missing required option: auth\")\n\n        self.__password_was_read = True\n        return self._app.prompt(\"Password / Token\", hide_input=True)\n\n    def __get_username(self) -> str:\n        username = (\n            self._options.get(\"user\")\n            or self._repo_config.get(\"user\")\n            or self._read_pypirc()\n            or self._read_previous_working_user_data()\n        )\n        if username is not None:\n            return username\n\n        if self._options[\"no_prompt\"]:\n            self._app.abort(\"Missing required option: user\")\n\n        self.__username_was_read = True\n        return self._app.prompt(f\"Username for '{self._repo_config['url']}' [__token__]\") or \"__token__\"\n\n    def _read_previous_working_user_data(self) -> str | None:\n        if self._pwu_path.is_file():\n            contents = self._pwu_path.read_text()\n            if contents:\n                import json\n\n                self.__pwu_data = json.loads(contents)\n        return self.__pwu_data.get(self._repo)\n\n    def _read_pypirc(self) -> str | None:\n        import configparser\n\n        pypirc = configparser.ConfigParser()\n        pypirc.read(Path.home() / \".pypirc\")\n        repo = self._repo or \"pypi\"\n\n        if pypirc.has_section(repo):\n            self.__password = pypirc.get(section=repo, option=\"password\", fallback=None)\n            return pypirc.get(section=repo, option=\"username\", fallback=None)\n\n        repo_url = self._repo_config[\"url\"]\n        for section in pypirc.sections():\n            if pypirc.get(section=section, option=\"repository\", fallback=None) == repo_url:\n                self.__password = pypirc.get(section=section, option=\"password\", fallback=None)\n                return pypirc.get(section=section, option=\"username\", fallback=None)\n\n        return None\n\n    def write_updated_data(self):\n        if self.__username_was_read:\n            import json\n\n            self.__pwu_data[self._repo] = self.__username\n            self._pwu_path.ensure_parent_dir_exists()\n            self._pwu_path.write_text(json.dumps(self.__pwu_data))\n\n        if self.__password_was_read:\n            import keyring\n\n            keyring_service = self._repo_config[\"url\"]\n            keyring.set_password(keyring_service, self.__username, self.__password)\n"
  },
  {
    "path": "src/hatch/publish/index.py",
    "content": "from __future__ import annotations\n\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom hatch.publish.plugin.interface import PublisherInterface\nfrom hatch.utils.fs import Path\nfrom hatchling.metadata.utils import normalize_project_name\n\nif TYPE_CHECKING:\n    from collections.abc import Iterable\n\n\nclass IndexPublisher(PublisherInterface):\n    PLUGIN_NAME = \"index\"\n\n    def get_repos(self):\n        global_plugin_config = self.plugin_config.copy()\n        defined_repos = self.plugin_config.pop(\"repos\", {})\n        self.plugin_config.pop(\"repo\", None)\n\n        # Normalize type\n        repos = {}\n        for repo, data in defined_repos.items():\n            if isinstance(data, str):\n                repos[repo] = {\"url\": data}\n            elif not isinstance(data, dict):\n                self.app.abort(f\"Hatch config field `publish.index.repos.{repo}` must be a string or a mapping\")\n            elif \"url\" not in data:\n                self.app.abort(f\"Hatch config field `publish.index.repos.{repo}` must define a `url` key\")\n            else:\n                repos[repo] = data\n\n        # Ensure PyPI correct\n        for repo, url in (\n            (\"main\", \"https://upload.pypi.org/legacy/\"),\n            (\"test\", \"https://test.pypi.org/legacy/\"),\n        ):\n            repos.setdefault(repo, {})[\"url\"] = url\n\n        # Populate defaults\n        for config in repos.values():\n            for key, value in global_plugin_config.items():\n                config.setdefault(key, value)\n\n        return repos\n\n    def publish(self, artifacts: list, options: dict):\n        \"\"\"\n        https://warehouse.readthedocs.io/api-reference/legacy.html#upload-api\n        \"\"\"\n        from collections import defaultdict\n\n        from hatch.index.core import PackageIndex\n        from hatch.index.publish import get_sdist_form_data, get_wheel_form_data\n        from hatch.publish.auth import AuthenticationCredentials\n\n        if not artifacts:\n            from hatchling.builders.constants import DEFAULT_BUILD_DIRECTORY\n\n            artifacts = [DEFAULT_BUILD_DIRECTORY]\n\n        repo = options[\"repo\"] if \"repo\" in options else self.plugin_config.get(\"repo\", \"main\")\n        repos = self.get_repos()\n        repo_config: dict[str, str] = repos[repo] if repo in repos else {\"url\": repo}\n        credentials = AuthenticationCredentials(\n            app=self.app,\n            cache_dir=self.cache_dir,\n            options=options,\n            repo=repo,\n            repo_config=repo_config,\n        )\n\n        index = PackageIndex(\n            repo_config[\"url\"],\n            user=credentials.username,\n            auth=credentials.password,\n            ca_cert=options.get(\"ca_cert\", repo_config.get(\"ca-cert\")),\n            client_cert=options.get(\"client_cert\", repo_config.get(\"client-cert\")),\n            client_key=options.get(\"client_key\", repo_config.get(\"client-key\")),\n        )\n\n        existing_artifacts: dict[str, set[str]] = {}\n\n        # Use as an ordered set\n        project_versions: dict[str, dict[str, None]] = defaultdict(dict)\n\n        artifacts_found = False\n        for artifact in recurse_artifacts(artifacts, self.root):\n            if artifact.name.endswith(\".whl\"):\n                data = get_wheel_form_data(artifact)\n            elif artifact.name.endswith(\".tar.gz\"):\n                data = get_sdist_form_data(artifact)\n            else:\n                continue\n\n            artifacts_found = True\n\n            for field in (\"name\", \"version\"):\n                if field not in data:\n                    self.app.abort(f\"Missing required field `{field}` in artifact: {artifact}\")\n\n            try:\n                displayed_path = str(artifact.relative_to(self.root))\n            except ValueError:\n                displayed_path = str(artifact)\n\n            self.app.display_info(f\"{displayed_path} ...\", end=\" \")\n\n            project_name = normalize_project_name(data[\"name\"])\n            if project_name not in existing_artifacts:\n                try:\n                    response = index.get_simple_api(project_name)\n                    response.raise_for_status()\n                except Exception:  # no cov  # noqa: BLE001\n                    existing_artifacts[project_name] = set()\n                else:\n                    existing_artifacts[project_name] = set(parse_artifacts(response.text))\n\n            if artifact.name in existing_artifacts[project_name]:\n                self.app.display_warning(\"already exists\")\n                continue\n\n            try:\n                index.upload_artifact(artifact, data)\n            except Exception as e:  # noqa: BLE001\n                self.app.display_error(\"failed\")\n                self.app.abort(f\"Error uploading to repository: {index.repo} - {e}\".replace(index.auth, \"*****\"))\n            else:\n                self.app.display_success(\"success\")\n\n                existing_artifacts[project_name].add(artifact.name)\n                project_versions[project_name][data[\"version\"]] = None\n\n        if not options[\"initialize_auth\"]:\n            if not artifacts_found:\n                self.app.abort(\"No artifacts found\")\n            elif not project_versions:\n                self.app.abort(code=0)\n\n        for project_name, versions in project_versions.items():\n            self.app.display_info()\n            self.app.display_mini_header(project_name)\n            for version in versions:\n                self.app.display_info(str(index.urls.project.child(project_name, version, \"\").to_iri()))\n\n        credentials.write_updated_data()\n\n\ndef recurse_artifacts(artifacts: list, root) -> Iterable[Path]:\n    for raw_artifact in artifacts:\n        artifact = Path(raw_artifact)\n        if not artifact.is_absolute():\n            artifact = root / artifact\n\n        if artifact.is_file():\n            yield artifact\n        elif artifact.is_dir():\n            yield from artifact.iterdir()\n\n\ndef parse_artifacts(artifact_payload):\n    for match in re.finditer(r\"<a [^>]+>([^<]+)</a>\", artifact_payload):\n        yield match.group(1)\n"
  },
  {
    "path": "src/hatch/publish/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/publish/plugin/hooks.py",
    "content": "from hatch.publish.index import IndexPublisher\nfrom hatchling.plugin import hookimpl\n\n\n@hookimpl\ndef hatch_register_publisher():\n    return IndexPublisher\n"
  },
  {
    "path": "src/hatch/publish/plugin/interface.py",
    "content": "from __future__ import annotations\n\nfrom abc import ABC, abstractmethod\n\n\nclass PublisherInterface(ABC):\n    \"\"\"\n    Example usage:\n\n    ```python tab=\"plugin.py\"\n        from hatch.publish.plugin.interface import PublisherInterface\n\n\n        class SpecialPublisher(PublisherInterface):\n            PLUGIN_NAME = 'special'\n            ...\n    ```\n\n    ```python tab=\"hooks.py\"\n        from hatchling.plugin import hookimpl\n\n        from .plugin import SpecialPublisher\n\n\n        @hookimpl\n        def hatch_register_publisher():\n            return SpecialPublisher\n    ```\n    \"\"\"\n\n    PLUGIN_NAME = \"\"\n    \"\"\"The name used for selection.\"\"\"\n\n    def __init__(self, app, root, cache_dir, project_config, plugin_config):\n        self.__app = app\n        self.__root = root\n        self.__cache_dir = cache_dir\n        self.__project_config = project_config\n        self.__plugin_config = plugin_config\n\n        self.__disable = None\n\n    @property\n    def app(self):\n        \"\"\"\n        An instance of [Application](../utilities.md#hatchling.bridge.app.Application).\n        \"\"\"\n        return self.__app\n\n    @property\n    def root(self):\n        \"\"\"\n        The root of the project tree as a path-like object.\n        \"\"\"\n        return self.__root\n\n    @property\n    def cache_dir(self):\n        \"\"\"\n        The directory reserved exclusively for this plugin as a path-like object.\n        \"\"\"\n        return self.__cache_dir\n\n    @property\n    def project_config(self) -> dict:\n        \"\"\"\n        ```toml config-example\n        [tool.hatch.publish.<PLUGIN_NAME>]\n        ```\n        \"\"\"\n        return self.__project_config\n\n    @property\n    def plugin_config(self) -> dict:\n        \"\"\"\n        This is defined in Hatch's [config file](../../config/hatch.md).\n\n        ```toml tab=\"config.toml\"\n        [publish.<PLUGIN_NAME>]\n        ```\n        \"\"\"\n        return self.__plugin_config\n\n    @property\n    def disable(self):\n        \"\"\"\n        Whether this plugin is disabled, thus requiring confirmation when publishing. Local\n        [project configuration](reference.md#hatch.publish.plugin.interface.PublisherInterface.project_config)\n        takes precedence over global\n        [plugin configuration](reference.md#hatch.publish.plugin.interface.PublisherInterface.plugin_config).\n        \"\"\"\n        if self.__disable is None:\n            if \"disable\" in self.project_config:\n                disable = self.project_config[\"disable\"]\n                if not isinstance(disable, bool):\n                    message = f\"Field `tool.hatch.publish.{self.PLUGIN_NAME}.disable` must be a boolean\"\n                    raise TypeError(message)\n            else:\n                disable = self.plugin_config.get(\"disable\", False)\n                if not isinstance(disable, bool):\n                    message = f\"Global plugin configuration `publish.{self.PLUGIN_NAME}.disable` must be a boolean\"\n                    raise TypeError(message)\n\n            self.__disable = disable\n\n        return self.__disable\n\n    @abstractmethod\n    def publish(self, artifacts: list[str], options: dict):\n        \"\"\"\n        :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right:\n\n        This is called directly by the [`publish`](../../cli/reference.md#hatch-publish) command\n        with the arguments and options it receives.\n        \"\"\"\n"
  },
  {
    "path": "src/hatch/py.typed",
    "content": ""
  },
  {
    "path": "src/hatch/python/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/python/core.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any\n\nfrom hatch.python.distributions import DISTRIBUTIONS, ORDERED_DISTRIBUTIONS\nfrom hatch.python.resolve import get_distribution\nfrom hatch.utils.fs import temp_directory\n\nif TYPE_CHECKING:\n    from hatch.python.resolve import Distribution\n    from hatch.utils.fs import Path\n\n\nclass InstalledDistribution:\n    def __init__(self, path: Path, distribution: Distribution, metadata: dict[str, Any]) -> None:\n        self.__path = path\n        self.__current_dist = distribution\n        self.__metadata = metadata\n\n    @property\n    def path(self) -> Path:\n        return self.__path\n\n    @property\n    def name(self) -> str:\n        return self.__current_dist.name\n\n    @property\n    def python_path(self) -> Path:\n        return self.path / self.__current_dist.python_path\n\n    @property\n    def version(self) -> str:\n        return self.__current_dist.version.base_version\n\n    @property\n    def metadata(self) -> dict[str, Any]:\n        return self.__metadata\n\n    def needs_update(self) -> bool:\n        new_dist = get_distribution(self.__current_dist.name)\n        return new_dist.version > self.__current_dist.version\n\n    @classmethod\n    def metadata_filename(cls) -> str:\n        return \"hatch-dist.json\"\n\n\nclass PythonManager:\n    def __init__(self, directory: Path) -> None:\n        self.__directory = directory\n\n    @property\n    def directory(self) -> Path:\n        return self.__directory\n\n    def get_installed(self) -> dict[str, InstalledDistribution]:\n        if not self.directory.is_dir():\n            return {}\n\n        import json\n\n        installed_distributions: list[InstalledDistribution] = []\n        for path in self.directory.iterdir():\n            if not (path.name in DISTRIBUTIONS and path.is_dir()):\n                continue\n\n            metadata_file = path / InstalledDistribution.metadata_filename()\n            if not metadata_file.is_file():\n                continue\n\n            metadata = json.loads(metadata_file.read_text())\n            distribution = get_distribution(path.name, source=metadata.get(\"source\", \"\"))\n            if not (path / distribution.python_path).is_file():\n                continue\n\n            installed_distributions.append(InstalledDistribution(path, distribution, metadata))\n\n        installed_distributions.sort(key=lambda d: ORDERED_DISTRIBUTIONS.index(d.name))\n        return {dist.name: dist for dist in installed_distributions}\n\n    def install(self, identifier: str) -> InstalledDistribution:\n        import json\n\n        from hatch.utils.network import download_file\n\n        dist = get_distribution(identifier)\n        path = self.directory / identifier\n        self.directory.ensure_dir_exists()\n\n        with temp_directory() as temp_dir:\n            archive_path = temp_dir / dist.archive_name\n            unpack_path = temp_dir / identifier\n            download_file(archive_path, dist.source, follow_redirects=True)\n            dist.unpack(archive_path, unpack_path)\n\n            backup_path = path.with_suffix(\".bak\")\n            if backup_path.is_dir():\n                backup_path.wait_for_dir_removed()\n\n            if path.is_dir():\n                path.replace(backup_path)\n\n            try:\n                unpack_path.replace(path)\n            except OSError:\n                import shutil\n\n                try:\n                    shutil.move(str(unpack_path), str(path))\n                except OSError:\n                    path.wait_for_dir_removed()\n                    if backup_path.is_dir():\n                        backup_path.replace(path)\n\n                    raise\n\n        metadata = {\"source\": dist.source, \"python_path\": dist.python_path}\n        metadata_file = path / InstalledDistribution.metadata_filename()\n        metadata_file.write_text(json.dumps(metadata, indent=2))\n\n        return InstalledDistribution(path, dist, metadata)\n\n    @staticmethod\n    def remove(dist: InstalledDistribution) -> None:\n        dist.path.wait_for_dir_removed()\n"
  },
  {
    "path": "src/hatch/python/distributions.py",
    "content": "from __future__ import annotations\n\n# fmt: off\nORDERED_DISTRIBUTIONS: tuple[str, ...] = (\n    '3.7',\n    '3.8',\n    '3.9',\n    '3.10',\n    '3.11',\n    '3.12',\n    '3.13',\n    '3.14',\n    'pypy2.7',\n    'pypy3.9',\n    'pypy3.10',\n    'pypy3.11',\n)\nDISTRIBUTIONS: dict[str, dict[tuple[str, ...], str]] = {\n    '3.14': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'aarch64', 'gnu', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'aarch64', 'musl', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-aarch64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'aarch64', 'musl', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-aarch64-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'armv7', 'gnueabi', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabi', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-armv7-unknown-linux-gnueabi-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'armv7', 'gnueabihf', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabihf', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-armv7-unknown-linux-gnueabihf-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'ppc64le', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'ppc64le', 'gnu', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-ppc64le-unknown-linux-gnu-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'riscv64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'riscv64', 'gnu', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-riscv64-unknown-linux-gnu-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 's390x', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-s390x-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 's390x', 'gnu', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-s390x-unknown-linux-gnu-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'x86_64', 'gnu', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v1', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'x86_64', 'gnu', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v2', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'x86_64', 'gnu', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v3', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'x86_64', 'gnu', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v4', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'x86_64', 'musl', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v1', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'x86_64', 'musl', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v2', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v2-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'x86_64', 'musl', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v3', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v3-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'x86_64', 'musl', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v4', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64_v4-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('windows', 'aarch64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-aarch64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'aarch64', 'msvc', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst',\n        ('windows', 'i386', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-i686-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'i386', 'msvc', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'amd64', 'msvc', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst',\n        ('macos', 'arm64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-aarch64-apple-darwin-install_only_stripped.tar.gz',\n        ('macos', 'arm64', '', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('macos', 'x86_64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64-apple-darwin-install_only_stripped.tar.gz',\n        ('macos', 'x86_64', '', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.14.0%2B20251014-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst',\n    },\n    '3.13': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'aarch64', 'gnu', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'aarch64', 'musl', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-aarch64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'aarch64', 'musl', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-aarch64-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'armv7', 'gnueabi', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabi', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-armv7-unknown-linux-gnueabi-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'armv7', 'gnueabihf', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabihf', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-armv7-unknown-linux-gnueabihf-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'ppc64le', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'ppc64le', 'gnu', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-ppc64le-unknown-linux-gnu-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'riscv64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'riscv64', 'gnu', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-riscv64-unknown-linux-gnu-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 's390x', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-s390x-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 's390x', 'gnu', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-s390x-unknown-linux-gnu-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'x86_64', 'gnu', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v1', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'x86_64', 'gnu', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v2', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'x86_64', 'gnu', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v3', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'x86_64', 'gnu', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v4', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('linux', 'x86_64', 'musl', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v1', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'x86_64', 'musl', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v2', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v2-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'x86_64', 'musl', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v3', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v3-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('linux', 'x86_64', 'musl', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v4', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64_v4-unknown-linux-musl-freethreaded%2Bnoopt-full.tar.zst',\n        ('windows', 'aarch64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-aarch64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'aarch64', 'msvc', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst',\n        ('windows', 'i386', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-i686-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'i386', 'msvc', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'amd64', 'msvc', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst',\n        ('macos', 'arm64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-aarch64-apple-darwin-install_only_stripped.tar.gz',\n        ('macos', 'arm64', '', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst',\n        ('macos', 'x86_64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64-apple-darwin-install_only_stripped.tar.gz',\n        ('macos', 'x86_64', '', '', 'freethreaded'):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.13.9%2B20251014-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst',\n    },\n    '3.12': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'aarch64', 'musl', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-aarch64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabi', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabihf', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz',\n        ('linux', 'ppc64le', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'riscv64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 's390x', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-s390x-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('windows', 'aarch64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-aarch64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'i386', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-i686-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('macos', 'arm64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-aarch64-apple-darwin-install_only_stripped.tar.gz',\n        ('macos', 'x86_64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.12.12%2B20251014-x86_64-apple-darwin-install_only_stripped.tar.gz',\n    },\n    '3.11': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'aarch64', 'musl', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-aarch64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabi', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabihf', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz',\n        ('linux', 'ppc64le', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'riscv64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 's390x', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-s390x-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('windows', 'aarch64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-aarch64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'i386', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-i686-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('macos', 'arm64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-aarch64-apple-darwin-install_only_stripped.tar.gz',\n        ('macos', 'x86_64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.11.14%2B20251014-x86_64-apple-darwin-install_only_stripped.tar.gz',\n        ('linux', 'i686', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz',\n    },\n    '3.10': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'aarch64', 'musl', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-aarch64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabi', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabihf', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz',\n        ('linux', 'ppc64le', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'riscv64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 's390x', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-s390x-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('windows', 'i386', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-i686-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('macos', 'arm64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-aarch64-apple-darwin-install_only_stripped.tar.gz',\n        ('macos', 'x86_64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.10.19%2B20251014-x86_64-apple-darwin-install_only_stripped.tar.gz',\n        ('linux', 'i686', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20230826/cpython-3.10.13%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz',\n    },\n    '3.9': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'aarch64', 'musl', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-aarch64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabi', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz',\n        ('linux', 'armv7', 'gnueabihf', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz',\n        ('linux', 'ppc64le', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'riscv64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 's390x', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-s390x-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v2', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v3', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v4', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('windows', 'i386', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-i686-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('macos', 'arm64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-aarch64-apple-darwin-install_only_stripped.tar.gz',\n        ('macos', 'x86_64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20251014/cpython-3.9.24%2B20251014-x86_64-apple-darwin-install_only_stripped.tar.gz',\n        ('linux', 'i686', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20230826/cpython-3.9.18%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz',\n    },\n    '3.8': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'gnu', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz',\n        ('linux', 'x86_64', 'musl', 'v1', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz',\n        ('windows', 'i386', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz',\n        ('macos', 'arm64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz',\n        ('macos', 'x86_64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz',\n        ('linux', 'i686', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz',\n    },\n    '3.7': {\n        ('linux', 'x86_64', 'gnu', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-gnu-pgo-20200823T0036.tar.zst',\n        ('linux', 'x86_64', 'musl', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-musl-noopt-20200823T0036.tar.zst',\n        ('windows', 'i386', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20200822/cpython-3.7.9-i686-pc-windows-msvc-shared-pgo-20200823T0159.tar.zst',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-pc-windows-msvc-shared-pgo-20200823T0118.tar.zst',\n        ('macos', 'x86_64', '', '', ''):\n            'https://github.com/astral-sh/python-build-standalone/releases/download/20200823/cpython-3.7.9-x86_64-apple-darwin-pgo-20200823T2228.tar.zst',\n    },\n    'pypy3.11': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://downloads.python.org/pypy/pypy3.11-v7.3.20-aarch64.tar.bz2',\n        ('linux', 'x86_64', 'gnu', '', ''):\n            'https://downloads.python.org/pypy/pypy3.11-v7.3.20-linux64.tar.bz2',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://downloads.python.org/pypy/pypy3.11-v7.3.20-win64.zip',\n        ('macos', 'arm64', '', '', ''):\n            'https://downloads.python.org/pypy/pypy3.11-v7.3.20-macos_arm64.tar.bz2',\n        ('macos', 'x86_64', '', '', ''):\n            'https://downloads.python.org/pypy/pypy3.11-v7.3.20-macos_x86_64.tar.bz2',\n    },\n    'pypy3.10': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://downloads.python.org/pypy/pypy3.10-v7.3.19-aarch64.tar.bz2',\n        ('linux', 'x86_64', 'gnu', '', ''):\n            'https://downloads.python.org/pypy/pypy3.10-v7.3.19-linux64.tar.bz2',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://downloads.python.org/pypy/pypy3.10-v7.3.19-win64.zip',\n        ('macos', 'arm64', '', '', ''):\n            'https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_arm64.tar.bz2',\n        ('macos', 'x86_64', '', '', ''):\n            'https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_x86_64.tar.bz2',\n    },\n    'pypy3.9': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://downloads.python.org/pypy/pypy3.9-v7.3.16-aarch64.tar.bz2',\n        ('linux', 'x86_64', 'gnu', '', ''):\n            'https://downloads.python.org/pypy/pypy3.9-v7.3.16-linux64.tar.bz2',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip',\n        ('macos', 'arm64', '', '', ''):\n            'https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2',\n        ('macos', 'x86_64', '', '', ''):\n            'https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2',\n    },\n    'pypy2.7': {\n        ('linux', 'aarch64', 'gnu', '', ''):\n            'https://downloads.python.org/pypy/pypy2.7-v7.3.20-aarch64.tar.bz2',\n        ('linux', 'x86_64', 'gnu', '', ''):\n            'https://downloads.python.org/pypy/pypy2.7-v7.3.20-linux64.tar.bz2',\n        ('windows', 'amd64', 'msvc', '', ''):\n            'https://downloads.python.org/pypy/pypy2.7-v7.3.20-win64.zip',\n        ('macos', 'arm64', '', '', ''):\n            'https://downloads.python.org/pypy/pypy2.7-v7.3.20-macos_arm64.tar.bz2',\n        ('macos', 'x86_64', '', '', ''):\n            'https://downloads.python.org/pypy/pypy2.7-v7.3.20-macos_x86_64.tar.bz2',\n    },\n}\n"
  },
  {
    "path": "src/hatch/python/resolve.py",
    "content": "from __future__ import annotations\n\nimport os\nimport platform\nimport sys\nfrom abc import ABC, abstractmethod\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, Literal\n\nfrom hatch.config.constants import PythonEnvVars\nfrom hatch.errors import PythonDistributionResolutionError, PythonDistributionUnknownError\nfrom hatch.python.distributions import DISTRIBUTIONS, ORDERED_DISTRIBUTIONS\n\nif TYPE_CHECKING:\n    from packaging.version import Version\n\n    from hatch.utils.fs import Path\n\n\n# Use an artificially high epoch to ensure that custom distributions are always considered newer\nCUSTOM_DISTRIBUTION_VERSION_EPOCH = 100\n\n\ndef custom_env_var(prefix: str, name: str) -> str:\n    return f\"{prefix}{name.upper().replace('.', '_')}\"\n\n\ndef get_custom_source(name: str) -> str | None:\n    return os.environ.get(custom_env_var(PythonEnvVars.CUSTOM_SOURCE_PREFIX, name))\n\n\ndef get_custom_version(name: str) -> str | None:\n    return os.environ.get(custom_env_var(PythonEnvVars.CUSTOM_VERSION_PREFIX, name))\n\n\ndef get_custom_path(name: str) -> str | None:\n    return os.environ.get(custom_env_var(PythonEnvVars.CUSTOM_PATH_PREFIX, name))\n\n\nclass Distribution(ABC):\n    def __init__(self, name: str, source: str) -> None:\n        self.__name = name\n        self.__source = source\n\n    @property\n    def name(self) -> str:\n        return self.__name\n\n    @cached_property\n    def source(self) -> str:\n        return self.__source if (custom_source := get_custom_source(self.name)) is None else custom_source\n\n    @cached_property\n    def archive_name(self) -> str:\n        return self.source.rsplit(\"/\", 1)[-1]\n\n    def unpack(self, archive: Path, directory: Path) -> None:\n        if self.source.endswith(\".zip\"):\n            import zipfile\n\n            with zipfile.ZipFile(archive, \"r\") as zf:\n                zf.extractall(directory)\n        elif self.source.endswith((\".tar.gz\", \".tgz\")):\n            self.__unpack_tarfile(archive, directory, \"r:gz\")\n        elif self.source.endswith((\".tar.bz2\", \".bz2\")):\n            self.__unpack_tarfile(archive, directory, \"r:bz2\")\n        elif self.source.endswith((\".tar.zst\", \".tar.zstd\")):\n            self.__unpack_tarfile(archive, directory, \"r:zst\")\n        else:\n            message = f\"Unknown archive type: {archive}\"\n            raise ValueError(message)\n\n    @staticmethod\n    def __unpack_tarfile(archive: Path, directory: Path, mode: Literal[\"r:gz\", \"r:bz2\", \"r:zst\"]) -> None:\n        if sys.version_info >= (3, 14):\n            import tarfile\n        else:\n            # for zstd support (introduced in Python 3.14)\n            # and filter kwarg (introduced in Python 3.12)\n            from backports.zstd import tarfile\n\n        with tarfile.open(archive, mode) as tf:\n            tf.extractall(directory, filter=\"data\")\n\n    @property\n    @abstractmethod\n    def version(self) -> Version:\n        pass\n\n    @property\n    @abstractmethod\n    def python_path(self) -> str:\n        pass\n\n\nclass CPythonStandaloneDistribution(Distribution):\n    @cached_property\n    def version(self) -> Version:\n        from packaging.version import Version\n\n        if (custom_version := get_custom_version(self.name)) is not None:\n            return Version(f\"{CUSTOM_DISTRIBUTION_VERSION_EPOCH}!{custom_version}\")\n\n        # .../cpython-3.12.0%2B20231002-...\n        # .../cpython-3.7.9-...\n        _, _, remaining = self.source.partition(\"/cpython-\")\n        # 3.12.0%2B20231002-...\n        # 3.7.9-...\n        version = remaining.split(\"%2B\")[0] if \"%2B\" in remaining else remaining.split(\"-\")[0]\n        return Version(f\"0!{version}\")\n\n    @cached_property\n    def python_path(self) -> str:\n        if (custom_path := get_custom_path(self.name)) is not None:\n            return custom_path\n\n        if self.name == \"3.7\":\n            if sys.platform == \"win32\":\n                return r\"python\\install\\python.exe\"\n\n            return \"python/install/bin/python3\"\n\n        if sys.platform == \"win32\":\n            return r\"python\\python.exe\"\n\n        return \"python/bin/python3\"\n\n\nclass PyPyOfficialDistribution(Distribution):\n    @cached_property\n    def version(self) -> Version:\n        from packaging.version import Version\n\n        if (custom_version := get_custom_version(self.name)) is not None:\n            return Version(f\"{CUSTOM_DISTRIBUTION_VERSION_EPOCH}!{custom_version}\")\n\n        *_, remaining = self.source.partition(\"/pypy/\")\n        _, version, *_ = remaining.split(\"-\")\n        return Version(f\"0!{version[1:]}\")\n\n    @cached_property\n    def python_path(self) -> str:\n        if (custom_path := get_custom_path(self.name)) is not None:\n            return custom_path\n\n        directory = self.archive_name\n        for extension in (\".tar.bz2\", \".zip\"):\n            if directory.endswith(extension):\n                directory = directory[: -len(extension)]\n                break\n\n        if sys.platform == \"win32\":\n            return rf\"{directory}\\pypy.exe\"\n\n        return f\"{directory}/bin/pypy\"\n\n\ndef get_distribution(name: str, source: str = \"\", variant_cpu: str = \"\", variant_gil: str = \"\") -> Distribution:\n    if source:\n        return _get_distribution_class(source)(name, source)\n\n    if name not in DISTRIBUTIONS:\n        message = f\"Unknown distribution: {name}\"\n        raise PythonDistributionUnknownError(message)\n\n    arch = platform.machine().lower()\n    if sys.platform == \"win32\":\n        system = \"windows\"\n        abi = \"msvc\"\n    elif sys.platform == \"darwin\":\n        system = \"macos\"\n        abi = \"\"\n    else:\n        system = \"linux\"\n        abi = \"gnu\" if any(platform.libc_ver()) else \"musl\"\n\n    if not variant_cpu:\n        variant_cpu = _get_default_variant_cpu(name, system, arch)\n\n    if not variant_gil:\n        variant_gil = _get_default_variant_gil()\n\n    key = (system, arch, abi, variant_cpu, variant_gil)\n\n    keys: dict[tuple, str] = DISTRIBUTIONS[name]\n    if key not in keys:\n        message = f\"Could not find a default source for {name=} {system=} {arch=} {abi=} {variant_cpu=} {variant_gil=}\"\n        raise PythonDistributionResolutionError(message)\n\n    source = keys[key]\n    return _get_distribution_class(source)(name, source)\n\n\ndef get_compatible_distributions() -> dict[str, Distribution]:\n    distributions: dict[str, Distribution] = {}\n    for name in ORDERED_DISTRIBUTIONS:\n        try:\n            dist = get_distribution(name)\n        except PythonDistributionResolutionError:\n            pass\n        else:\n            distributions[name] = dist\n\n    return distributions\n\n\ndef _guess_linux_variant_cpu() -> str:\n    # Use the highest that we know is most common when we can't parse CPU data\n    default = \"v3\"\n    try:\n        # Don't use our utility Path so we can properly mock\n        with open(\"/proc/cpuinfo\", encoding=\"utf-8\") as f:\n            contents = f.read()\n    except OSError:\n        return default\n\n    # See https://clang.llvm.org/docs/UsersManual.html#x86 for the\n    # instructions for each architecture variant and\n    # https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/cpufeatures.h\n    # for the corresponding Linux flags\n    v2_flags = {\"cx16\", \"lahf_lm\", \"popcnt\", \"pni\", \"sse4_1\", \"sse4_2\", \"ssse3\"}\n    v3_flags = {\"avx\", \"avx2\", \"bmi1\", \"bmi2\", \"f16c\", \"fma\", \"movbe\", \"xsave\"} | v2_flags\n    v4_flags = {\"avx512f\", \"avx512bw\", \"avx512cd\", \"avx512dq\", \"avx512vl\"} | v3_flags\n\n    for line in contents.splitlines():\n        key, _, value = line.partition(\":\")\n        if key.strip() == \"flags\":\n            flags = set(value.strip().split())\n\n            if flags.issuperset(v4_flags):\n                return \"v4\"\n\n            if flags.issuperset(v3_flags):\n                return \"v3\"\n\n            if flags.issuperset(v2_flags):\n                return \"v2\"\n\n            return \"v1\"\n\n    return default\n\n\ndef _get_default_variant_cpu(name: str, system: str, arch: str) -> str:\n    # not PyPy\n    if name[0].isdigit():\n        variant = os.environ.get(\n            \"HATCH_PYTHON_VARIANT_CPU\",\n            # Legacy name\n            os.environ.get(f\"HATCH_PYTHON_VARIANT_{system.upper()}\", \"\"),\n        ).lower()\n\n        # https://gregoryszorc.com/docs/python-build-standalone/main/running.html\n        if system == \"linux\" and arch == \"x86_64\":\n            # Intel-specific optimizations depending on age of release\n            if variant:\n                return variant\n\n            if name == \"3.8\":\n                return \"v1\"\n\n            if name != \"3.7\":\n                return _guess_linux_variant_cpu()\n\n    return \"\"\n\n\ndef _get_default_variant_gil() -> str:\n    return os.environ.get(\"HATCH_PYTHON_VARIANT_GIL\", \"\").lower()\n\n\ndef _get_distribution_class(source: str) -> type[Distribution]:\n    if \"/python-build-standalone/releases/download/\" in source:\n        return CPythonStandaloneDistribution\n    if source.startswith(\"https://downloads.python.org/pypy/\"):\n        return PyPyOfficialDistribution\n\n    message = f\"Unknown distribution source: {source}\"\n    raise ValueError(message)\n"
  },
  {
    "path": "src/hatch/template/__init__.py",
    "content": "from __future__ import annotations\n\nfrom contextlib import suppress\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from hatch.utils.fs import Path\n\n\nclass File:\n    def __init__(self, path: Path | None, contents: str = \"\"):\n        self.path = path\n        self.contents = contents\n        self.feature = None\n\n    def write(self, root):\n        if self.path is None:  # no cov\n            return\n\n        path = root / self.path\n        path.ensure_parent_dir_exists()\n        path.write_text(self.contents, encoding=\"utf-8\")\n\n\ndef find_template_files(module):\n    for name in dir(module):\n        obj = getattr(module, name)\n        if obj is File:\n            continue\n\n        with suppress(TypeError):\n            if issubclass(obj, File):\n                yield obj\n"
  },
  {
    "path": "src/hatch/template/default.py",
    "content": "from hatch.template import File, files_default, find_template_files\nfrom hatch.template.plugin.interface import TemplateInterface\nfrom hatch.utils.fs import Path\nfrom hatch.utils.network import download_file\n\n\nclass DefaultTemplate(TemplateInterface):\n    PLUGIN_NAME = \"default\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self.plugin_config.setdefault(\"ci\", False)\n        self.plugin_config.setdefault(\"src-layout\", True)\n        self.plugin_config.setdefault(\"tests\", True)\n\n    def initialize_config(self, config):\n        # Default values\n        config[\"readme_file_path\"] = \"README.md\"\n        config[\"package_metadata_file_path\"] = f\"src/{config['package_name']}/__about__.py\"\n\n        license_data = {}\n\n        # Licenses\n        license_ids = config[\"licenses\"][\"default\"]\n        if not license_ids:\n            config[\"license_data\"] = license_data\n            config[\"license_expression\"] = \"\"\n            config[\"license_files\"] = \"\"\n            config[\"license_header\"] = \"\"\n            return\n\n        cached_licenses_dir = self.cache_dir / \"licenses\"\n        cached_licenses_dir.ensure_dir_exists()\n\n        license_ids = sorted(set(license_ids))\n        for license_id in sorted(set(license_ids)):\n            license_file_name = f\"{license_id}.txt\"\n            cached_license_path = cached_licenses_dir / license_file_name\n            if not cached_license_path.is_file():\n                from packaging.licenses._spdx import VERSION  # noqa: PLC2701\n\n                url = f\"https://raw.githubusercontent.com/spdx/license-list-data/v{VERSION}/text/{license_file_name}\"\n                for _ in range(5):\n                    try:\n                        download_file(cached_license_path, url)\n                    except Exception:  # noqa: BLE001, S112\n                        continue\n                    else:\n                        break\n\n            license_data[license_id] = cached_license_path.read_text(encoding=\"utf-8\")\n\n        config[\"license_data\"] = license_data\n        config[\"license_expression\"] = \" OR \".join(license_data)\n        config[\"license_header\"] = (\n            \"\"\n            if not config[\"licenses\"][\"headers\"]\n            else f\"\"\"\\\n# SPDX-FileCopyrightText: {self.creation_time.year}-present {config[\"name\"]} <{config[\"email\"]}>\n#\n# SPDX-License-Identifier: {config[\"license_expression\"]}\n\"\"\"\n        )\n        if len(license_ids) == 1:\n            config[\"license_files\"] = \"\"\n        else:\n            config[\"license_files\"] = '\\nlicense-files = { globs = [\"LICENSES/*\"] }'\n\n        if config[\"args\"][\"cli\"]:\n            config[\"dependencies\"].add(\"click\")\n\n        if not self.plugin_config[\"src-layout\"]:\n            config[\"package_metadata_file_path\"] = f\"{config['package_metadata_file_path'][4:]}\"\n\n    def get_files(self, config):\n        files = list(find_template_files(files_default))\n\n        # Add any licenses\n        license_data = config[\"license_data\"]\n        if license_data:\n            if len(license_data) == 1:\n                license_id, text = next(iter(license_data.items()))\n                license_text = get_license_text(config, license_id, text, self.creation_time)\n                files.append(File(Path(\"LICENSE.txt\"), license_text))\n            else:\n                # https://reuse.software/faq/#multi-licensing\n                for license_id, text in license_data.items():\n                    license_text = get_license_text(config, license_id, text, self.creation_time)\n                    files.append(File(Path(\"LICENSES\", f\"{license_id}.txt\"), license_text))\n\n        if config[\"args\"][\"cli\"]:\n            from hatch.template import files_feature_cli\n\n            files.extend(find_template_files(files_feature_cli))\n\n        if self.plugin_config[\"tests\"]:\n            from hatch.template import files_feature_tests\n\n            files.extend(find_template_files(files_feature_tests))\n\n        if self.plugin_config[\"ci\"]:\n            from hatch.template import files_feature_ci\n\n            files.extend(find_template_files(files_feature_ci))\n\n        return files\n\n    def finalize_files(self, config, files):\n        if config[\"licenses\"][\"headers\"] and config[\"license_data\"]:\n            for template_file in files:\n                if template_file.path.name.endswith(\".py\"):\n                    template_file.contents = config[\"license_header\"] + template_file.contents\n\n        if self.plugin_config[\"src-layout\"]:\n            for template_file in files:\n                if template_file.path.parts[0] == config[\"package_name\"]:\n                    template_file.path = Path(\"src\", template_file.path)\n\n\ndef get_license_text(config, license_id, license_text, creation_time):\n    if license_id == \"MIT\":\n        license_text = license_text.replace(\"<year>\", f\"{creation_time.year}-present\", 1)\n        license_text = license_text.replace(\"<copyright holders>\", f\"{config['name']} <{config['email']}>\", 1)\n    elif license_id == \"BSD-3-Clause\":\n        license_text = license_text.replace(\"<year>\", f\"{creation_time.year}-present\", 1)\n        license_text = license_text.replace(\"<owner>\", f\"{config['name']} <{config['email']}>\", 1)\n\n    return f\"{license_text.rstrip()}\\n\"\n"
  },
  {
    "path": "src/hatch/template/files_default.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\n\nclass PackageRoot(File):\n    def __init__(\n        self,\n        template_config: dict,\n        plugin_config: dict,  # noqa: ARG002\n    ):\n        super().__init__(Path(template_config[\"package_name\"], \"__init__.py\"), \"\")\n\n\nclass MetadataFile(File):\n    def __init__(\n        self,\n        template_config: dict,\n        plugin_config: dict,  # noqa: ARG002\n    ):\n        super().__init__(Path(template_config[\"package_name\"], \"__about__.py\"), '__version__ = \"0.0.1\"\\n')\n\n\nclass Readme(File):\n    TEMPLATE = \"\"\"\\\n# {project_name}\n\n[![PyPI - Version](https://img.shields.io/pypi/v/{project_name_normalized}.svg)](https://pypi.org/project/{project_name_normalized})\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{project_name_normalized}.svg)](https://pypi.org/project/{project_name_normalized})\n{extra_badges}\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n{extra_toc}\n## Installation\n\n```console\npip install {project_name_normalized}\n```{license_info}\n\"\"\"\n\n    def __init__(\n        self,\n        template_config: dict,\n        plugin_config: dict,  # noqa: ARG002\n    ):\n        extra_badges = \"\"\n        extra_toc = \"\"\n\n        license_info = \"\"\n        if template_config[\"license_data\"]:\n            extra_toc += \"- [License](#license)\\n\"\n            license_info += (\n                f\"\\n\\n## License\\n\\n`{template_config['project_name_normalized']}` is distributed under the terms of \"\n            )\n\n            license_data = template_config[\"license_data\"]\n            if len(license_data) == 1:\n                license_id = next(iter(license_data))\n                license_info += f\"the [{license_id}](https://spdx.org/licenses/{license_id}.html) license.\"\n            else:\n                license_info += \"any of the following licenses:\\n\"\n                for license_id in sorted(license_data):\n                    license_info += f\"\\n- [{license_id}](https://spdx.org/licenses/{license_id}.html)\"\n\n        super().__init__(\n            Path(template_config[\"readme_file_path\"]),\n            self.TEMPLATE.format(\n                extra_badges=extra_badges, extra_toc=extra_toc, license_info=license_info, **template_config\n            ),\n        )\n\n\nclass PyProject(File):\n    TEMPLATE = \"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"{project_name_normalized}\"\ndynamic = [\"version\"]\ndescription = {description!r}\nreadme = \"{readme_file_path}\"\nrequires-python = \">=3.8\"\nlicense = \"{license_expression}\"{license_files}\nkeywords = []\nauthors = [\n  {{ name = \"{name}\", email = \"{email}\" }},\n]\nclassifiers = [\n  \"Development Status :: 4 - Beta\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = {dependency_data}\n\n[project.urls]{project_url_data}{cli_scripts}\n\n[tool.hatch.version]\npath = \"{package_metadata_file_path}\"{tests_section}\n\"\"\"\n\n    def __init__(self, template_config: dict, plugin_config: dict):\n        template_config = dict(template_config)\n        template_config[\"name\"] = repr(template_config[\"name\"])[1:-1]\n\n        project_url_data = \"\"\n        project_urls = (\n            plugin_config[\"project_urls\"]\n            if \"project_urls\" in plugin_config\n            else {\n                \"Documentation\": \"https://github.com/{name}/{project_name_normalized}#readme\",\n                \"Issues\": \"https://github.com/{name}/{project_name_normalized}/issues\",\n                \"Source\": \"https://github.com/{name}/{project_name_normalized}\",\n            }\n        )\n        if project_urls:\n            for label, url in project_urls.items():\n                normalized_label = f'\"{label}\"' if \" \" in label else label\n                project_url_data += f'\\n{normalized_label} = \"{url.format(**template_config)}\"'\n\n        dependency_data = \"[\"\n        if template_config[\"dependencies\"]:\n            for dependency in sorted(template_config[\"dependencies\"]):\n                dependency_data += f'\\n  \"{dependency}\",\\n'\n        dependency_data += \"]\"\n\n        cli_scripts = \"\"\n        if template_config[\"args\"][\"cli\"]:\n            cli_scripts = f\"\"\"\n\n[project.scripts]\n{template_config[\"project_name_normalized\"]} = \"{template_config[\"package_name\"]}.cli:{template_config[\"package_name\"]}\"\\\n\"\"\"\n\n        tests_section = \"\"\n        if plugin_config[\"tests\"]:\n            package_location = \"src/\" if plugin_config[\"src-layout\"] else \"\"\n            tests_section = f\"\"\"\n\n[tool.hatch.envs.types]\nextra-dependencies = [\n  \"mypy>=1.0.0\",\n]\n[tool.hatch.envs.types.scripts]\ncheck = \"mypy --install-types --non-interactive {{args:{package_location}{template_config[\"package_name\"]} tests}}\"\n\n[tool.coverage.run]\nsource_pkgs = [\"{template_config[\"package_name\"]}\", \"tests\"]\nbranch = true\nparallel = true\nomit = [\n  \"{package_location}{template_config[\"package_name\"]}/__about__.py\",\n]\n\n[tool.coverage.paths]\n{template_config[\"package_name\"]} = [\"{package_location}{template_config[\"package_name\"]}\", \"*/{template_config[\"project_name_normalized\"]}/{package_location}{template_config[\"package_name\"]}\"]\ntests = [\"tests\", \"*/{template_config[\"project_name_normalized\"]}/tests\"]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"no cov\",\n  \"if __name__ == .__main__.:\",\n  \"if TYPE_CHECKING:\",\n]\"\"\"\n\n        super().__init__(\n            Path(\"pyproject.toml\"),\n            self.TEMPLATE.format(\n                project_url_data=project_url_data,\n                dependency_data=dependency_data,\n                cli_scripts=cli_scripts,\n                tests_section=tests_section,\n                **template_config,\n            ),\n        )\n"
  },
  {
    "path": "src/hatch/template/files_feature_ci.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\n\nclass CommandLinePackage(File):\n    TEMPLATE = \"\"\"\\\nname: test\n\non:\n  push:\n    branches: [main, master]\n  pull_request:\n    branches: [main, master]\n\nconcurrency:\n  group: test-${{ github.head_ref }}\n  cancel-in-progress: true\n\nenv:\n  PYTHONUNBUFFERED: \"1\"\n  FORCE_COLOR: \"1\"\n\njobs:\n  run:\n    name: Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v4\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install Hatch\n      run: pip install --upgrade hatch\n\n    - name: Run static analysis\n      run: hatch fmt --check\n\n    - name: Run tests\n      run: hatch test --python ${{ matrix.python-version }} --cover --randomize --parallel --retries 2 --retry-delay 1\n\"\"\"\n\n    def __init__(\n        self,\n        template_config: dict,  # noqa: ARG002\n        plugin_config: dict,  # noqa: ARG002\n    ):\n        super().__init__(Path(\".github\", \"workflows\", \"test.yml\"), self.TEMPLATE)\n"
  },
  {
    "path": "src/hatch/template/files_feature_cli.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\n\nclass PackageEntryPoint(File):\n    TEMPLATE = \"\"\"\\\nimport sys\n\nif __name__ == \"__main__\":\n    from {package_name}.cli import {package_name}\n\n    sys.exit({package_name}())\n\"\"\"\n\n    def __init__(\n        self,\n        template_config: dict,\n        plugin_config: dict,  # noqa: ARG002\n    ):\n        super().__init__(Path(template_config[\"package_name\"], \"__main__.py\"), self.TEMPLATE.format(**template_config))\n\n\nclass CommandLinePackage(File):\n    TEMPLATE = \"\"\"\\\nimport click\n\nfrom {package_name}.__about__ import __version__\n\n\n@click.group(context_settings={{\"help_option_names\": [\"-h\", \"--help\"]}}, invoke_without_command=True)\n@click.version_option(version=__version__, prog_name=\"{project_name}\")\ndef {package_name}():\n    click.echo(\"Hello world!\")\n\"\"\"\n\n    def __init__(\n        self,\n        template_config: dict,\n        plugin_config: dict,  # noqa: ARG002\n    ):\n        super().__init__(\n            Path(template_config[\"package_name\"], \"cli\", \"__init__.py\"), self.TEMPLATE.format(**template_config)\n        )\n"
  },
  {
    "path": "src/hatch/template/files_feature_tests.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\n\nclass TestsPackageRoot(File):\n    def __init__(\n        self,\n        template_config: dict,  # noqa: ARG002\n        plugin_config: dict,  # noqa: ARG002\n    ):\n        super().__init__(Path(\"tests\", \"__init__.py\"))\n"
  },
  {
    "path": "src/hatch/template/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/template/plugin/hooks.py",
    "content": "from hatch.template.default import DefaultTemplate\nfrom hatchling.plugin import hookimpl\n\n\n@hookimpl\ndef hatch_register_template():\n    return DefaultTemplate\n"
  },
  {
    "path": "src/hatch/template/plugin/interface.py",
    "content": "class TemplateInterface:\n    PLUGIN_NAME = \"\"\n    PRIORITY = 100\n\n    def __init__(self, plugin_config: dict, cache_dir, creation_time):\n        self.plugin_config = plugin_config\n        self.cache_dir = cache_dir\n        self.creation_time = creation_time\n\n    def initialize_config(self, config):\n        \"\"\"\n        Allow modification of the configuration passed to every file for new projects\n        before the list of files are determined.\n        \"\"\"\n\n    def get_files(self, config):  # noqa: ARG002, PLR6301\n        \"\"\"Add to the list of files for new projects that are written to the file system.\"\"\"\n        return []\n\n    def finalize_files(self, config, files):\n        \"\"\"Allow modification of files for new projects before they are written to the file system.\"\"\"\n"
  },
  {
    "path": "src/hatch/utils/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/utils/ci.py",
    "content": "import os\n\n\ndef running_in_ci() -> bool:\n    return any(os.environ.get(env_var) in {\"true\", \"1\"} for env_var in (\"CI\", \"GITHUB_ACTIONS\"))\n"
  },
  {
    "path": "src/hatch/utils/dep.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any\n\nfrom hatchling.metadata.utils import get_normalized_dependency, normalize_project_name\n\nif TYPE_CHECKING:\n    from packaging.requirements import Requirement\n\n    from hatch.dep.core import Dependency\n\n\ndef normalize_marker_quoting(text: str) -> str:\n    # All TOML writers use double quotes, so allow copy/pasting to avoid escaping\n    return text.replace('\"', \"'\")\n\n\ndef get_normalized_dependencies(requirements: list[Requirement]) -> list[str]:\n    normalized_dependencies = {get_normalized_dependency(requirement) for requirement in requirements}\n    return sorted(normalized_dependencies)\n\n\ndef hash_dependencies(requirements: list[Dependency]) -> str:\n    from hashlib import sha256\n\n    data = \"\".join(\n        sorted(\n            # Internal spacing is ignored by PEP 440\n            normalized_dependency.replace(\" \", \"\")\n            for normalized_dependency in {get_normalized_dependency(req) for req in requirements}\n        )\n    ).encode(\"utf-8\")\n\n    return sha256(data).hexdigest()\n\n\ndef get_complex_dependencies(dependencies: list[str]) -> dict[str, Dependency]:\n    from hatch.dep.core import Dependency\n\n    dependencies_complex = {}\n    for dependency in dependencies:\n        dependencies_complex[dependency] = Dependency(dependency)\n\n    return dependencies_complex\n\n\ndef get_complex_features(features: dict[str, list[str]]) -> dict[str, dict[str, Dependency]]:\n    from hatch.dep.core import Dependency\n\n    optional_dependencies_complex = {}\n    for feature, optional_dependencies in features.items():\n        optional_dependencies_complex[feature] = {\n            optional_dependency: Dependency(optional_dependency) for optional_dependency in optional_dependencies\n        }\n\n    return optional_dependencies_complex\n\n\ndef get_complex_dependency_group(\n    dependency_groups: dict[str, Any], group: str, past_groups: tuple[str, ...] = ()\n) -> list[Dependency]:\n    from hatch.dep.core import Dependency\n\n    if group in past_groups:\n        msg = f\"Cyclic dependency group include: {group} -> {past_groups}\"\n        raise ValueError(msg)\n\n    if group not in dependency_groups:\n        msg = f\"Dependency group '{group}' not found\"\n        raise LookupError(msg)\n\n    raw_group = dependency_groups[group]\n    if not isinstance(raw_group, list):\n        msg = f\"Dependency group '{group}' is not a list\"\n        raise TypeError(msg)\n\n    realized_group = []\n    for item in raw_group:\n        if isinstance(item, str):\n            realized_group.append(Dependency(item))\n        elif isinstance(item, dict):\n            if tuple(item.keys()) != (\"include-group\",):\n                msg = f\"Invalid dependency group item: {item}\"\n                raise ValueError(msg)\n\n            include_group = normalize_project_name(next(iter(item.values())))\n            realized_group.extend(get_complex_dependency_group(dependency_groups, include_group, (*past_groups, group)))\n        else:\n            msg = f\"Invalid dependency group item: {item}\"\n            raise TypeError(msg)\n\n    return realized_group\n"
  },
  {
    "path": "src/hatch/utils/env.py",
    "content": "from __future__ import annotations\n\nfrom ast import literal_eval\nfrom typing import TYPE_CHECKING, Any\n\nif TYPE_CHECKING:\n    from hatch.utils.platform import Platform\n\n\nclass PythonInfo:\n    def __init__(self, platform: Platform, executable: str = \"python\") -> None:\n        self.platform = platform\n        self.executable = executable\n\n        self.__dep_check_data: dict[str, Any] | None = None\n        self.__environment: dict[str, str] | None = None\n        self.__sys_path: list[str] | None = None\n\n    @property\n    def dep_check_data(self) -> dict[str, Any]:\n        if self.__dep_check_data is None:\n            process = self.platform.check_command(\n                [self.executable, \"-W\", \"ignore\", \"-\"], capture_output=True, input=DEP_CHECK_DATA_SCRIPT\n            )\n\n            self.__dep_check_data = literal_eval(process.stdout.strip().decode(\"utf-8\"))\n\n        return self.__dep_check_data\n\n    @property\n    def environment(self) -> dict[str, str]:\n        if self.__environment is None:\n            self.__environment = self.dep_check_data[\"environment\"]\n\n        return self.__environment\n\n    @property\n    def sys_path(self) -> list[str]:\n        if self.__sys_path is None:\n            self.__sys_path = self.dep_check_data[\"sys_path\"]\n\n        return self.__sys_path\n\n\n# Keep support for Python 2 for a while:\n# https://github.com/pypa/packaging/blob/20.9/packaging/markers.py#L267-L300\nDEP_CHECK_DATA_SCRIPT = b\"\"\"\\\nimport os\nimport platform\nimport sys\n\nif hasattr(sys, 'implementation'):\n    info = sys.implementation.version\n    iver = '{0.major}.{0.minor}.{0.micro}'.format(info)\n    kind = info.releaselevel\n    if kind != 'final':\n        iver += kind[0] + str(info.serial)\n    implementation_name = sys.implementation.name\nelse:\n    iver = '0'\n    implementation_name = ''\n\nenvironment = {\n    'implementation_name': implementation_name,\n    'implementation_version': iver,\n    'os_name': os.name,\n    'platform_machine': platform.machine(),\n    'platform_python_implementation': platform.python_implementation(),\n    'platform_release': platform.release(),\n    'platform_system': platform.system(),\n    'platform_version': platform.version(),\n    'python_full_version': platform.python_version(),\n    'python_version': '.'.join(platform.python_version_tuple()[:2]),\n    'sys_platform': sys.platform,\n}\nsys_path = [path for path in sys.path if path]\n\nprint({'environment': environment, 'sys_path': sys_path})\n\"\"\"\n"
  },
  {
    "path": "src/hatch/utils/fs.py",
    "content": "from __future__ import annotations\n\nimport os\nimport pathlib\nimport sys\nfrom contextlib import contextmanager, suppress\nfrom functools import cached_property\nfrom typing import TYPE_CHECKING, Any\n\nfrom hatch.utils.structures import EnvVars\n\nif TYPE_CHECKING:\n    from collections.abc import Generator\n\n    from _typeshed import FileDescriptorLike\n\n\n# There is special recognition in Mypy for `sys.platform`, not `os.name`\n# https://github.com/python/cpython/blob/09d7319bfe0006d9aa3fc14833b69c24ccafdca6/Lib/pathlib.py#L957\nif sys.platform == \"win32\":\n    _PathBase = pathlib.WindowsPath\nelse:\n    _PathBase = pathlib.PosixPath\n\ndisk_sync = os.fsync\n# https://mjtsai.com/blog/2022/02/17/apple-ssd-benchmarks-and-f_fullsync/\n# https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html\nif sys.platform == \"darwin\":\n    import fcntl\n\n    if hasattr(fcntl, \"F_FULLFSYNC\"):\n\n        def disk_sync(fd: FileDescriptorLike) -> None:\n            fcntl.fcntl(fd, fcntl.F_FULLFSYNC)\n\n\nclass Path(_PathBase):\n    @cached_property\n    def long_id(self) -> str:\n        from base64 import urlsafe_b64encode\n        from hashlib import sha256\n\n        path = str(self)\n        if sys.platform == \"win32\" or sys.platform == \"darwin\":\n            path = path.casefold()\n\n        digest = sha256(path.encode(\"utf-8\")).digest()\n        return urlsafe_b64encode(digest).decode(\"utf-8\")\n\n    @cached_property\n    def id(self) -> str:\n        return self.long_id[:8]\n\n    def ensure_dir_exists(self) -> None:\n        self.mkdir(parents=True, exist_ok=True)\n\n    def ensure_parent_dir_exists(self) -> None:\n        self.parent.mkdir(parents=True, exist_ok=True)\n\n    def expand(self) -> Path:\n        return Path(os.path.expanduser(os.path.expandvars(self)))\n\n    def remove(self) -> None:\n        if self.is_file():\n            os.remove(self)\n        elif self.is_dir():\n            import shutil\n\n            shutil.rmtree(self, ignore_errors=False)\n\n    def move(self, target):\n        try:\n            self.replace(target)\n        except OSError:\n            import shutil\n\n            shutil.copy2(self, target)\n            self.unlink()\n\n    def wait_for_dir_removed(self, timeout: int = 5) -> None:\n        import shutil\n        import time\n\n        for _ in range(timeout * 2):\n            if self.is_dir():\n                shutil.rmtree(self, ignore_errors=True)\n                time.sleep(0.5)\n            else:\n                return\n\n        if self.is_dir():\n            shutil.rmtree(self, ignore_errors=False)\n\n    def write_atomic(self, data: str | bytes, *args: Any, **kwargs: Any) -> None:\n        from tempfile import mkstemp\n\n        fd, path = mkstemp(dir=self.parent)\n        with os.fdopen(fd, *args, **kwargs) as f:\n            f.write(data)\n            f.flush()\n            disk_sync(fd)\n\n        os.replace(path, self)\n\n    @contextmanager\n    def as_cwd(self, *args: Any, **kwargs: Any) -> Generator[Path, None, None]:\n        origin = os.getcwd()\n        os.chdir(self)\n\n        try:\n            if args or kwargs:\n                with EnvVars(*args, **kwargs):\n                    yield self\n            else:\n                yield self\n        finally:\n            os.chdir(origin)\n\n    @contextmanager\n    def temp_hide(self) -> Generator[Path, None, None]:\n        import shutil\n\n        with temp_directory() as temp_dir:\n            temp_path = Path(temp_dir, self.name)\n            with suppress(FileNotFoundError):\n                shutil.move(str(self), temp_dir / self.name)\n\n            try:\n                yield temp_path\n            finally:\n                with suppress(FileNotFoundError):\n                    shutil.move(str(temp_path), self)\n\n    if sys.platform == \"win32\":\n\n        @classmethod\n        def from_uri(cls, path: str) -> Path:\n            return cls(path.replace(\"file:///\", \"\", 1))\n\n    else:\n\n        @classmethod\n        def from_uri(cls, path: str) -> Path:\n            return cls(path.replace(\"file://\", \"\", 1))\n\n\n@contextmanager\ndef temp_directory() -> Generator[Path, None, None]:\n    from tempfile import TemporaryDirectory\n\n    with TemporaryDirectory() as d:\n        yield Path(d).resolve()\n\n\n@contextmanager\ndef temp_chdir(env_vars: dict[str, str] | None = None) -> Generator[Path, None, None]:\n    with temp_directory() as d, d.as_cwd(env_vars=env_vars):\n        yield d\n"
  },
  {
    "path": "src/hatch/utils/metadata.py",
    "content": "from __future__ import annotations\n\nimport re\n\n\ndef normalize_project_name(name: str) -> str:\n    # https://peps.python.org/pep-0503/#normalized-names\n    return re.sub(r\"[-_.]+\", \"-\", name).lower()\n"
  },
  {
    "path": "src/hatch/utils/network.py",
    "content": "from __future__ import annotations\n\nimport time\nfrom contextlib import contextmanager\nfrom typing import TYPE_CHECKING, Any\n\nif TYPE_CHECKING:\n    from collections.abc import Generator\n\n    import httpx\n\n    from hatch.utils.fs import Path\n\nMINIMUM_SLEEP = 2\nMAXIMUM_SLEEP = 20\n# The timeout should be slightly larger than a multiple of 3,\n# which is the default TCP packet retransmission window. See:\n# https://tools.ietf.org/html/rfc2988\nDEFAULT_TIMEOUT = 10\n\n\n@contextmanager\ndef streaming_response(*args: Any, **kwargs: Any) -> Generator[httpx.Response, None, None]:\n    from secrets import choice\n\n    import httpx\n\n    attempts = 0\n    while True:\n        attempts += 1\n        try:\n            with httpx.stream(*args, **kwargs) as response:\n                response.raise_for_status()\n                yield response\n\n            break\n        except httpx.HTTPError:\n            sleep = min(MAXIMUM_SLEEP, MINIMUM_SLEEP * 2**attempts)\n            if sleep == MAXIMUM_SLEEP:\n                raise\n\n            time.sleep(choice(range(sleep + 1)))\n\n\ndef download_file(path: Path, *args: Any, **kwargs: Any) -> None:\n    kwargs.setdefault(\"timeout\", DEFAULT_TIMEOUT)\n\n    with path.open(mode=\"wb\", buffering=0) as f, streaming_response(\"GET\", *args, **kwargs) as response:\n        for chunk in response.iter_bytes(16384):\n            f.write(chunk)\n"
  },
  {
    "path": "src/hatch/utils/platform.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\nfrom functools import cache\nfrom importlib import import_module\nfrom typing import TYPE_CHECKING, Any, cast\n\nif TYPE_CHECKING:\n    from collections.abc import Callable, Iterable\n    from subprocess import CompletedProcess, Popen\n    from types import ModuleType\n\n    from hatch.utils.fs import Path\n\n\n@cache\ndef get_platform_name() -> str:\n    import platform\n\n    return normalize_platform_name(platform.system())\n\n\ndef normalize_platform_name(platform_name: str) -> str:\n    platform_name = platform_name.lower()\n    return \"macos\" if platform_name == \"darwin\" else platform_name\n\n\nclass Platform:\n    def __init__(self, display_func: Callable = print) -> None:\n        self.__display_func = display_func\n\n        # Lazily loaded constants\n        self.__default_shell: str | None = None\n        self.__format_file_uri: Callable[[str], str] | None = None\n        self.__join_command_args: Callable[[list[str]], str] | None = None\n        self.__name: str | None = None\n        self.__display_name: str | None = None\n        self.__home: Path | None = None\n\n        # Whether or not an interactive status is being displayed\n        self.displaying_status = False\n\n        self.__modules = LazilyLoadedModules()\n\n    @property\n    def modules(self) -> LazilyLoadedModules:\n        \"\"\"\n        Accessor for lazily loading modules that either take multiple milliseconds to import\n        (like `shutil` and `subprocess`) or are not used on all platforms (like `shlex`).\n        \"\"\"\n        return self.__modules\n\n    def format_for_subprocess(self, command: str | list[str], *, shell: bool) -> str | list[str]:\n        \"\"\"\n        Format the given command in a cross-platform manner for immediate consumption by subprocess utilities.\n        \"\"\"\n        if self.windows:\n            # Manually locate executables on Windows to avoid multiple cases in which `shell=True` is required:\n            #\n            # - If the `PATH` environment variable has been modified, see:\n            #   https://github.com/python/cpython/issues/52803\n            # - Executables that do not have the extension `.exe`, see:\n            #   https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw\n            if not shell and not isinstance(command, str):\n                executable = command[0]\n                new_command = [self.modules.shutil.which(executable) or executable]\n                new_command.extend(command[1:])\n                return new_command\n        elif not shell and isinstance(command, str):\n            return self.modules.shlex.split(command)\n\n        return command\n\n    @staticmethod\n    def exit_with_code(code: str | int | None) -> None:\n        sys.exit(code)\n\n    def _run_command_integrated(\n        self, command: str | list[str], *, shell: bool = False, **kwargs: Any\n    ) -> CompletedProcess:\n        with self.capture_process(command, shell=shell, **kwargs) as process:\n            for line in self.stream_process_output(process):\n                self.__display_func(line, end=\"\")\n\n            stdout, stderr = process.communicate()\n\n        return self.modules.subprocess.CompletedProcess(process.args, process.poll(), stdout, stderr)\n\n    def run_command(self, command: str | list[str], *, shell: bool = False, **kwargs: Any) -> CompletedProcess:\n        \"\"\"\n        Equivalent to the standard library's\n        [subprocess.run](https://docs.python.org/3/library/subprocess.html#subprocess.run),\n        with the command first being\n        [properly formatted](utilities.md#hatch.utils.platform.Platform.format_for_subprocess).\n        \"\"\"\n        if self.displaying_status and not kwargs.get(\"capture_output\"):\n            return self._run_command_integrated(command, shell=shell, **kwargs)\n\n        self.populate_default_popen_kwargs(kwargs, shell=shell)\n        return self.modules.subprocess.run(self.format_for_subprocess(command, shell=shell), shell=shell, **kwargs)\n\n    def check_command(self, command: str | list[str], *, shell: bool = False, **kwargs: Any) -> CompletedProcess:\n        \"\"\"\n        Equivalent to [run_command](utilities.md#hatch.utils.platform.Platform.run_command),\n        but non-zero exit codes will gracefully end program execution.\n        \"\"\"\n        process = self.run_command(command, shell=shell, **kwargs)\n        if process.returncode:\n            self.exit_with_code(process.returncode)\n\n        return process\n\n    def check_command_output(self, command: str | list[str], *, shell: bool = False, **kwargs: Any) -> str:\n        \"\"\"\n        Equivalent to the output from the process returned by\n        [capture_process](utilities.md#hatch.utils.platform.Platform.capture_process),\n        but non-zero exit codes will gracefully end program execution.\n        \"\"\"\n        kwargs.setdefault(\"stdout\", self.modules.subprocess.PIPE)\n        kwargs.setdefault(\"stderr\", self.modules.subprocess.STDOUT)\n        self.populate_default_popen_kwargs(kwargs, shell=shell)\n\n        process = self.modules.subprocess.run(self.format_for_subprocess(command, shell=shell), shell=shell, **kwargs)\n        if process.returncode:\n            # Callers might not want to merge both streams so try stderr first\n            self.__display_func((process.stderr or process.stdout).decode(\"utf-8\"))\n            self.exit_with_code(process.returncode)\n\n        return process.stdout.decode(\"utf-8\")\n\n    def capture_process(self, command: str | list[str], *, shell: bool = False, **kwargs: Any) -> Popen:\n        \"\"\"\n        Equivalent to the standard library's\n        [subprocess.Popen](https://docs.python.org/3/library/subprocess.html#subprocess.Popen),\n        with all output captured by `stdout` and the command first being\n        [properly formatted](utilities.md#hatch.utils.platform.Platform.format_for_subprocess).\n        \"\"\"\n        self.populate_default_popen_kwargs(kwargs, shell=shell)\n        return self.modules.subprocess.Popen(\n            self.format_for_subprocess(command, shell=shell),\n            shell=shell,\n            stdout=self.modules.subprocess.PIPE,\n            stderr=self.modules.subprocess.STDOUT,\n            **kwargs,\n        )\n\n    def populate_default_popen_kwargs(self, kwargs: dict[str, Any], *, shell: bool) -> None:\n        # https://support.apple.com/en-us/HT204899\n        # https://en.wikipedia.org/wiki/System_Integrity_Protection\n        if (\n            \"executable\" not in kwargs\n            and self.macos\n            and shell\n            and any(env_var.startswith((\"DYLD_\", \"LD_\")) for env_var in os.environ)\n        ):\n            default_paths = os.environ.get(\"PATH\", os.defpath).split(os.pathsep)\n            unprotected_paths = []\n            for path in default_paths:\n                normalized_path = os.path.normpath(path)\n                if not normalized_path.startswith((\n                    \"/System\",\n                    \"/usr\",\n                    \"/bin\",\n                    \"/sbin\",\n                    \"/var\",\n                )) or normalized_path.startswith(\"/usr/local\"):\n                    unprotected_paths.append(path)\n\n            search_path = os.pathsep.join(unprotected_paths)\n            for exe_name in (\"sh\", \"bash\", \"zsh\", \"fish\"):\n                executable = self.modules.shutil.which(exe_name, path=search_path)\n                if executable:\n                    kwargs[\"executable\"] = executable\n                    break\n\n    @staticmethod\n    def stream_process_output(process: Popen) -> Iterable[str]:\n        # To avoid blocking never use a pipe's file descriptor iterator. See https://bugs.python.org/issue3907\n        for line in iter(process.stdout.readline, b\"\"):  # type: ignore[union-attr]\n            yield line.decode(\"utf-8\")\n\n    @property\n    def default_shell(self) -> str:\n        \"\"\"\n        Returns the default shell of the system.\n\n        On Windows systems first try the `SHELL` environment variable, if present, followed by\n        the `COMSPEC` environment variable, defaulting to `cmd`. On all other platforms only\n        the `SHELL` environment variable will be used, defaulting to `bash`.\n        \"\"\"\n        if self.__default_shell is None:\n            if self.windows:\n                self.__default_shell = cast(str, os.environ.get(\"SHELL\", os.environ.get(\"COMSPEC\", \"cmd\")))\n            else:\n                self.__default_shell = cast(str, os.environ.get(\"SHELL\", \"bash\"))\n        return self.__default_shell\n\n    @property\n    def join_command_args(self) -> Callable[[list[str]], str]:\n        if self.__join_command_args is None:\n            if self.windows:\n                self.__join_command_args = self.modules.subprocess.list2cmdline\n            else:\n                self.__join_command_args = self.modules.shlex.join\n\n        return self.__join_command_args\n\n    @property\n    def format_file_uri(self) -> Callable[[str], str]:\n        if self.__format_file_uri is None:\n            if self.windows:\n                self.__format_file_uri = lambda p: f\"file:///{p}\".replace(\"\\\\\", \"/\")\n            else:\n                self.__format_file_uri = lambda p: f\"file://{p}\"\n\n        return self.__format_file_uri\n\n    @property\n    def windows(self) -> bool:\n        \"\"\"\n        Indicates whether Hatch is running on Windows.\n        \"\"\"\n        return self.name == \"windows\"\n\n    @property\n    def macos(self) -> bool:\n        \"\"\"\n        Indicates whether Hatch is running on macOS.\n        \"\"\"\n        return self.name == \"macos\"\n\n    @property\n    def linux(self) -> bool:\n        \"\"\"\n        Indicates whether Hatch is running on neither Windows nor macOS.\n        \"\"\"\n        return not (self.windows or self.macos)\n\n    def exit_with_command(self, command: list[str]) -> None:\n        \"\"\"\n        Run the given command and exit with its exit code. On non-Windows systems, this uses the standard library's\n        [os.execvp](https://docs.python.org/3/library/os.html#os.execvp).\n        \"\"\"\n        if self.windows:\n            process = self.run_command(command)\n            self.exit_with_code(process.returncode)\n        else:\n            os.execvp(command[0], command)  # noqa: S606\n\n    @property\n    def name(self) -> str:\n        \"\"\"\n        One of the following:\n\n        - `linux`\n        - `windows`\n        - `macos`\n        \"\"\"\n        if self.__name is None:\n            self.__name = get_platform_name()\n\n        return self.__name\n\n    @property\n    def display_name(self) -> str:\n        \"\"\"\n        One of the following:\n\n        - `Linux`\n        - `Windows`\n        - `macOS`\n        \"\"\"\n        if self.__display_name is None:\n            self.__display_name = \"macOS\" if self.macos else self.name.capitalize()\n\n        return self.__display_name\n\n    @property\n    def home(self) -> Path:\n        \"\"\"\n        The user's home directory as a path-like object.\n        \"\"\"\n        if self.__home is None:\n            from hatch.utils.fs import Path\n\n            self.__home = Path(os.path.expanduser(\"~\"))\n\n        return self.__home\n\n\nclass LazilyLoadedModules:\n    def __getattr__(self, name: str) -> ModuleType:\n        module = import_module(name)\n        setattr(self, name, module)\n        return module\n"
  },
  {
    "path": "src/hatch/utils/runner.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any\n\nif TYPE_CHECKING:\n    from hatch.env.plugin.interface import EnvironmentInterface\n\n\nclass ExecutionContext:\n    def __init__(\n        self,\n        environment: EnvironmentInterface,\n        *,\n        shell_commands: list[str] | None = None,\n        env_vars: dict[str, str] | None = None,\n        force_continue: bool = False,\n        show_code_on_error: bool = False,\n        hide_commands: bool = False,\n        source: str = \"cmd\",\n    ) -> None:\n        self.env = environment\n        self.shell_commands: list[str] = shell_commands or []\n        self.env_vars: dict[str, str] = env_vars or {}\n        self.force_continue = force_continue\n        self.show_code_on_error = show_code_on_error\n        self.hide_commands = hide_commands\n        self.source = source\n\n    def add_shell_command(self, command: str | list[str]) -> None:\n        self.shell_commands.append(command if isinstance(command, str) else self.env.join_command_args(command))\n\n\ndef parse_matrix_variables(specs: tuple[str, ...]) -> dict[str, set[str]]:\n    variables: dict[str, set[str]] = {}\n    for spec in specs:\n        variable, _, values = spec.partition(\"=\")\n        if variable == \"py\":\n            variable = \"python\"\n\n        if variable in variables:\n            raise ValueError(variable)\n\n        variables[variable] = set(values.split(\",\")) if values else set()\n\n    return variables\n\n\ndef select_environments(\n    environments: dict[str, dict[str, Any]],\n    included_variables: dict[str, set[str]],\n    excluded_variables: dict[str, set[str]],\n):\n    selected_environments = []\n    for env_name, variables in environments.items():\n        exclude = False\n        for excluded_variable, excluded_values in excluded_variables.items():\n            if excluded_variable not in variables:\n                continue\n\n            value = variables[excluded_variable]\n            if not excluded_values or value in excluded_values:\n                exclude = True\n                break\n\n        if exclude:\n            continue\n\n        for included_variable, included_values in included_variables.items():\n            if included_variable not in variables:\n                exclude = True\n                break\n\n            value = variables[included_variable]\n            if included_values and value not in included_values:\n                exclude = True\n                break\n\n        if not exclude:\n            selected_environments.append(env_name)\n\n    return selected_environments\n"
  },
  {
    "path": "src/hatch/utils/shells.py",
    "content": "from __future__ import annotations\n\nimport sys\nfrom typing import TYPE_CHECKING\n\nfrom hatch.utils.fs import Path\n\nif TYPE_CHECKING:\n    from collections.abc import Callable, Iterable\n    from types import FrameType\n\n    from hatch.env.plugin.interface import EnvironmentInterface\n    from hatch.utils.platform import Platform\n\n\ndef detect_shell(platform: Platform) -> tuple[str, str]:\n    import shellingham\n\n    try:\n        return shellingham.detect_shell()\n    except shellingham.ShellDetectionFailure:\n        path = platform.default_shell\n        return Path(path).stem, path\n\n\nclass ShellManager:\n    def __init__(self, environment: EnvironmentInterface) -> None:\n        self.environment = environment\n\n    def enter_cmd(self, path: str, args: Iterable[str], exe_dir: Path) -> None:  # noqa: ARG002\n        self.environment.platform.exit_with_command([path or \"cmd\", \"/k\", str(exe_dir / \"activate.bat\")])\n\n    def enter_powershell(self, path: str, args: Iterable[str], exe_dir: Path) -> None:  # noqa: ARG002\n        self.environment.platform.exit_with_command([\n            path or \"powershell\",\n            \"-executionpolicy\",\n            \"bypass\",\n            \"-NoExit\",\n            \"-NoLogo\",\n            \"-File\",\n            str(exe_dir / \"activate.ps1\"),\n        ])\n\n    def enter_pwsh(self, path: str, args: Iterable[str], exe_dir: Path) -> None:\n        self.enter_powershell(path or \"pwsh\", args, exe_dir)\n\n    def enter_xonsh(self, path: str, args: Iterable[str], exe_dir: Path) -> None:\n        if self.environment.platform.windows:\n            with self.environment:\n                self.environment.platform.exit_with_command([\n                    path or \"xonsh\",\n                    *(args or [\"-i\"]),\n                    \"-D\",\n                    f\"VIRTUAL_ENV={exe_dir.parent.name}\",\n                ])\n        else:\n            self.spawn_linux_shell(\n                path or \"xonsh\",\n                [*(args or [\"-i\"]), \"-D\", f\"VIRTUAL_ENV={exe_dir.parent.name}\"],\n                # Just in case pyenv works with xonsh, supersede it.\n                callback=lambda terminal: terminal.sendline(f\"$PATH.insert(0, {str(exe_dir)!r})\"),\n            )\n\n    def enter_bash(self, path: str, args: Iterable[str], exe_dir: Path) -> None:\n        if self.environment.platform.windows:\n            self.environment.platform.exit_with_command([\n                path or \"bash\",\n                \"--init-file\",\n                exe_dir / \"activate\",\n                *(args or [\"-i\"]),\n            ])\n        else:\n            self.spawn_linux_shell(path or \"bash\", args or [\"-i\"], script=exe_dir / \"activate\")\n\n    def enter_fish(self, path: str, args: Iterable[str], exe_dir: Path) -> None:\n        self.spawn_linux_shell(path or \"fish\", args or [\"-i\"], script=exe_dir / \"activate.fish\")\n\n    def enter_zsh(self, path: str, args: Iterable[str], exe_dir: Path) -> None:\n        self.spawn_linux_shell(path or \"zsh\", args or [\"-i\"], script=exe_dir / \"activate\")\n\n    def enter_ash(self, path: str, args: Iterable[str], exe_dir: Path) -> None:\n        self.spawn_linux_shell(path or \"ash\", args or [\"-i\"], script=exe_dir / \"activate\")\n\n    def enter_nu(self, path: str, args: Iterable[str], exe_dir: Path) -> None:  # noqa: ARG002\n        executable = path or \"nu\"\n        activation_script = exe_dir / \"activate.nu\"\n        self.environment.platform.exit_with_command([executable, \"-e\", f\"overlay use {str(activation_script)!r}\"])\n\n    def enter_tcsh(self, path: str, args: Iterable[str], exe_dir: Path) -> None:\n        self.spawn_linux_shell(path or \"tcsh\", args or [\"-i\"], script=exe_dir / \"activate.csh\")\n\n    def enter_csh(self, path: str, args: Iterable[str], exe_dir: Path) -> None:\n        self.spawn_linux_shell(path or \"csh\", args or [\"-i\"], script=exe_dir / \"activate.csh\")\n\n    if sys.platform == \"win32\":\n\n        def spawn_linux_shell(\n            self,\n            path: str,\n            args: Iterable[str] | None = None,\n            *,\n            script: Path | None = None,\n            callback: Callable | None = None,\n        ) -> None:\n            raise NotImplementedError\n\n    else:\n\n        def spawn_linux_shell(\n            self,\n            path: str,\n            args: Iterable[str] | None = None,\n            *,\n            script: Path | None = None,\n            callback: Callable | None = None,\n        ) -> None:\n            import shutil\n            import signal\n\n            import pexpect\n\n            columns, lines = shutil.get_terminal_size()\n            # pexpect only accepts lists\n            terminal = pexpect.spawn(path, args=list(args or ()), dimensions=(lines, columns))\n\n            def sigwinch_passthrough(sig: int, data: FrameType | None) -> None:  # noqa: ARG001\n                new_columns, new_lines = shutil.get_terminal_size()\n                terminal.setwinsize(new_lines, new_columns)\n\n            signal.signal(signal.SIGWINCH, sigwinch_passthrough)\n\n            if script is not None:\n                terminal.sendline(f'source \"{script}\"')\n\n            if callback is not None:\n                callback(terminal)\n\n            terminal.interact(escape_character=None)\n            terminal.close()\n\n            self.environment.platform.exit_with_code(terminal.exitstatus)\n"
  },
  {
    "path": "src/hatch/utils/structures.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom fnmatch import fnmatch\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from types import TracebackType\n\n\nclass EnvVars(dict):\n    def __init__(\n        self, env_vars: dict | None = None, include: list[str] | None = None, exclude: list[str] | None = None\n    ) -> None:\n        super().__init__(os.environ)\n        self.old_env = dict(self)\n\n        if include:\n            self.clear()\n            for env_var, value in self.old_env.items():\n                for pattern in include:\n                    if fnmatch(env_var, pattern):\n                        self[env_var] = value\n                        break\n\n        if exclude:\n            for env_var in list(self):\n                for pattern in exclude:\n                    if fnmatch(env_var, pattern):\n                        self.pop(env_var)\n                        break\n\n        if env_vars:\n            self.update(env_vars)\n\n    def __enter__(self) -> None:\n        os.environ.clear()\n        os.environ.update(self)\n\n    def __exit__(\n        self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None\n    ) -> None:\n        os.environ.clear()\n        os.environ.update(self.old_env)\n"
  },
  {
    "path": "src/hatch/utils/toml.py",
    "content": "from __future__ import annotations\n\nimport sys\nfrom typing import Any\n\nif sys.version_info >= (3, 11):\n    import tomllib\nelse:\n    import tomli as tomllib\n\n\ndef load_toml_data(data: str) -> dict[str, Any]:\n    return tomllib.loads(data)\n\n\ndef load_toml_file(path: str) -> dict[str, Any]:\n    with open(path, encoding=\"utf-8\") as f:\n        return tomllib.loads(f.read())\n"
  },
  {
    "path": "src/hatch/venv/__init__.py",
    "content": ""
  },
  {
    "path": "src/hatch/venv/core.py",
    "content": "import os\nfrom tempfile import TemporaryDirectory\n\nfrom hatch.env.utils import add_verbosity_flag\nfrom hatch.utils.env import PythonInfo\nfrom hatch.utils.fs import Path\nfrom hatch.venv.utils import get_random_venv_name\n\n\nclass VirtualEnv:\n    IGNORED_ENV_VARS = (\"__PYVENV_LAUNCHER__\", \"PYTHONHOME\")\n\n    def __init__(self, directory, platform, verbosity=0):\n        self.directory = directory\n        self.platform = platform\n        self.verbosity = verbosity\n        self.python_info = PythonInfo(platform)\n\n        self._env_vars_to_restore = {}\n        self._executables_directory = None\n\n    def activate(self):\n        self._env_vars_to_restore[\"VIRTUAL_ENV\"] = os.environ.pop(\"VIRTUAL_ENV\", None)\n        os.environ[\"VIRTUAL_ENV\"] = str(self.directory)\n\n        old_path = os.environ.pop(\"PATH\", None)\n        self._env_vars_to_restore[\"PATH\"] = old_path\n        if old_path is None:\n            os.environ[\"PATH\"] = f\"{self.executables_directory}{os.pathsep}{os.defpath}\"\n        else:\n            os.environ[\"PATH\"] = f\"{self.executables_directory}{os.pathsep}{old_path}\"\n\n        for env_var in self.IGNORED_ENV_VARS:\n            self._env_vars_to_restore[env_var] = os.environ.pop(env_var, None)\n\n    def deactivate(self):\n        for env_var, value in self._env_vars_to_restore.items():\n            if value is None:\n                os.environ.pop(env_var, None)\n            else:\n                os.environ[env_var] = value\n\n        self._env_vars_to_restore.clear()\n\n    def create(self, python, *, allow_system_packages=False):\n        # WARNING: extremely slow import\n        from virtualenv import cli_run\n\n        self.directory.ensure_parent_dir_exists()\n\n        command = [str(self.directory), \"--no-download\", \"--no-periodic-update\", \"--python\", python]\n\n        if allow_system_packages:\n            command.append(\"--system-site-packages\")\n\n        # Decrease verbosity since the virtualenv CLI defaults to something like +2 verbosity\n        add_verbosity_flag(command, self.verbosity, adjustment=-1)\n\n        cli_run(command)\n\n    def remove(self):\n        self.directory.remove()\n\n    def exists(self):\n        return self.directory.is_dir()\n\n    @property\n    def executables_directory(self):\n        if self._executables_directory is None:\n            exe_dir = self.directory / (\"Scripts\" if self.platform.windows else \"bin\")\n            if exe_dir.is_dir():\n                self._executables_directory = exe_dir\n            # PyPy\n            elif self.platform.windows:\n                exe_dir = self.directory / \"bin\"\n                if exe_dir.is_dir():\n                    self._executables_directory = exe_dir\n                else:\n                    msg = f\"Unable to locate executables directory within: {self.directory}\"\n                    raise OSError(msg)\n            # Debian\n            elif (self.directory / \"local\").is_dir():  # no cov\n                exe_dir = self.directory / \"local\" / \"bin\"\n                if exe_dir.is_dir():\n                    self._executables_directory = exe_dir\n                else:\n                    msg = f\"Unable to locate executables directory within: {self.directory}\"\n                    raise OSError(msg)\n            else:\n                msg = f\"Unable to locate executables directory within: {self.directory}\"\n                raise OSError(msg)\n\n        return self._executables_directory\n\n    @property\n    def environment(self):\n        return self.python_info.environment\n\n    @property\n    def sys_path(self):\n        return self.python_info.sys_path\n\n    def __enter__(self):\n        self.activate()\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        self.deactivate()\n\n\nclass TempVirtualEnv(VirtualEnv):\n    def __init__(self, parent_python, platform, verbosity=0):\n        self.parent_python = parent_python\n        self.parent_dir = TemporaryDirectory()\n        directory = Path(self.parent_dir.name).resolve() / get_random_venv_name()\n\n        super().__init__(directory, platform, verbosity)\n\n    def remove(self):\n        super().remove()\n        self.parent_dir.cleanup()\n\n    def __enter__(self):\n        self.create(self.parent_python)\n        return super().__enter__()\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        super().__exit__(exc_type, exc_value, traceback)\n        self.remove()\n\n\nclass UVVirtualEnv(VirtualEnv):\n    def create(self, python, *, allow_system_packages=False):\n        command = [os.environ.get(\"HATCH_UV\", \"uv\"), \"venv\", str(self.directory), \"--python\", python]\n        if allow_system_packages:\n            command.append(\"--system-site-packages\")\n\n        add_verbosity_flag(command, self.verbosity, adjustment=-1)\n        self.platform.run_command(command)\n\n\nclass TempUVVirtualEnv(TempVirtualEnv, UVVirtualEnv): ...\n"
  },
  {
    "path": "src/hatch/venv/utils.py",
    "content": "from base64 import urlsafe_b64encode\nfrom os import urandom\n\n\ndef get_random_venv_name():\n    # Will be length 4\n    return urlsafe_b64encode(urandom(3)).decode(\"ascii\")\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/builders/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/builders/hooks/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/builders/hooks/test_custom.py",
    "content": "import re\n\nimport pytest\n\nfrom hatchling.builders.hooks.custom import CustomBuildHook\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\n\ndef test_no_path(isolation):\n    config = {\"path\": \"\"}\n\n    with pytest.raises(ValueError, match=\"Option `path` for build hook `custom` must not be empty if defined\"):\n        CustomBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n\ndef test_path_not_string(isolation):\n    config = {\"path\": 3}\n\n    with pytest.raises(TypeError, match=\"Option `path` for build hook `custom` must be a string\"):\n        CustomBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n\ndef test_nonexistent(isolation):\n    config = {\"path\": \"test.py\"}\n\n    with pytest.raises(OSError, match=\"Build script does not exist: test.py\"):\n        CustomBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n\ndef test_default(temp_dir, helpers):\n    config = {}\n\n    file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def foo(self):\n                    return self.PLUGIN_NAME, self.root\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        hook = CustomBuildHook(str(temp_dir), config, None, None, \"\", \"\")\n\n    assert hook.foo() == (\"custom\", str(temp_dir))\n\n\ndef test_explicit_path(temp_dir, helpers):\n    config = {\"path\": f\"foo/{DEFAULT_BUILD_SCRIPT}\"}\n\n    file_path = temp_dir / \"foo\" / DEFAULT_BUILD_SCRIPT\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def foo(self):\n                    return self.PLUGIN_NAME, self.root\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        hook = CustomBuildHook(str(temp_dir), config, None, None, \"\", \"\")\n\n    assert hook.foo() == (\"custom\", str(temp_dir))\n\n\ndef test_no_subclass(temp_dir, helpers):\n    config = {\"path\": f\"foo/{DEFAULT_BUILD_SCRIPT}\"}\n\n    file_path = temp_dir / \"foo\" / DEFAULT_BUILD_SCRIPT\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            foo = None\n            bar = 'baz'\n\n            class CustomHook:\n                pass\n            \"\"\"\n        )\n    )\n\n    with (\n        pytest.raises(\n            ValueError,\n            match=re.escape(\n                f\"Unable to find a subclass of `BuildHookInterface` in `foo/{DEFAULT_BUILD_SCRIPT}`: {temp_dir}\"\n            ),\n        ),\n        temp_dir.as_cwd(),\n    ):\n        CustomBuildHook(str(temp_dir), config, None, None, \"\", \"\")\n"
  },
  {
    "path": "tests/backend/builders/hooks/test_version.py",
    "content": "import pytest\n\nfrom hatchling.builders.hooks.version import VersionBuildHook\nfrom hatchling.metadata.core import ProjectMetadata\nfrom hatchling.plugin.manager import PluginManager\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\n\nclass TestConfigPath:\n    def test_correct(self, isolation):\n        config = {\"path\": \"foo/bar.py\"}\n        hook = VersionBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n        assert hook.config_path == hook.config_path == \"foo/bar.py\"\n\n    def test_missing(self, isolation):\n        config = {\"path\": \"\"}\n        hook = VersionBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n        with pytest.raises(ValueError, match=\"Option `path` for build hook `version` is required\"):\n            _ = hook.config_path\n\n    def test_not_string(self, isolation):\n        config = {\"path\": 9000}\n        hook = VersionBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n        with pytest.raises(TypeError, match=\"Option `path` for build hook `version` must be a string\"):\n            _ = hook.config_path\n\n\nclass TestConfigTemplate:\n    def test_correct(self, isolation):\n        config = {\"template\": \"foo\"}\n        hook = VersionBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n        assert hook.config_template == hook.config_template == \"foo\"\n\n    def test_not_string(self, isolation):\n        config = {\"template\": 9000}\n        hook = VersionBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n        with pytest.raises(TypeError, match=\"Option `template` for build hook `version` must be a string\"):\n            _ = hook.config_template\n\n\nclass TestConfigPattern:\n    def test_correct(self, isolation):\n        config = {\"pattern\": \"foo\"}\n        hook = VersionBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n        assert hook.config_pattern == hook.config_pattern == \"foo\"\n\n    def test_not_string(self, isolation):\n        config = {\"pattern\": 9000}\n        hook = VersionBuildHook(str(isolation), config, None, None, \"\", \"\")\n\n        with pytest.raises(TypeError, match=\"Option `pattern` for build hook `version` must be a string\"):\n            _ = hook.config_pattern\n\n\nclass TestTemplate:\n    def test_default(self, temp_dir, helpers):\n        config = {\"path\": \"baz.py\"}\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"dynamic\": [\"version\"]},\n                \"tool\": {\"hatch\": {\"metadata\": {\"hooks\": {\"custom\": {}}}}},\n            },\n        )\n\n        file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n                class CustomHook(MetadataHookInterface):\n                    def update(self, metadata):\n                        metadata['version'] = '1.2.3'\n                \"\"\"\n            )\n        )\n\n        build_data = {\"artifacts\": []}\n        hook = VersionBuildHook(str(temp_dir), config, None, metadata, \"\", \"\")\n        hook.initialize([], build_data)\n\n        expected_file = temp_dir / \"baz.py\"\n        assert expected_file.is_file()\n        assert expected_file.read_text() == helpers.dedent(\n            \"\"\"\n            # This file is auto-generated by Hatchling. As such, do not:\n            #   - modify\n            #   - track in version control e.g. be sure to add to .gitignore\n            __version__ = VERSION = '1.2.3'\n            \"\"\"\n        )\n        assert build_data[\"artifacts\"] == [\"/baz.py\"]\n\n    def test_create_necessary_directories(self, temp_dir, helpers):\n        config = {\"path\": \"bar/baz.py\"}\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"dynamic\": [\"version\"]},\n                \"tool\": {\"hatch\": {\"metadata\": {\"hooks\": {\"custom\": {}}}}},\n            },\n        )\n\n        file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n                class CustomHook(MetadataHookInterface):\n                    def update(self, metadata):\n                        metadata['version'] = '1.2.3'\n                \"\"\"\n            )\n        )\n\n        build_data = {\"artifacts\": []}\n        hook = VersionBuildHook(str(temp_dir), config, None, metadata, \"\", \"\")\n        hook.initialize([], build_data)\n\n        expected_file = temp_dir / \"bar\" / \"baz.py\"\n        assert expected_file.is_file()\n        assert expected_file.read_text() == helpers.dedent(\n            \"\"\"\n            # This file is auto-generated by Hatchling. As such, do not:\n            #   - modify\n            #   - track in version control e.g. be sure to add to .gitignore\n            __version__ = VERSION = '1.2.3'\n            \"\"\"\n        )\n        assert build_data[\"artifacts\"] == [\"/bar/baz.py\"]\n\n    def test_custom(self, temp_dir, helpers):\n        config = {\"path\": \"baz.py\", \"template\": \"VER = {version!r}\\n\"}\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"dynamic\": [\"version\"]},\n                \"tool\": {\"hatch\": {\"metadata\": {\"hooks\": {\"custom\": {}}}}},\n            },\n        )\n\n        file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n                class CustomHook(MetadataHookInterface):\n                    def update(self, metadata):\n                        metadata['version'] = '1.2.3'\n                \"\"\"\n            )\n        )\n\n        build_data = {\"artifacts\": []}\n        hook = VersionBuildHook(str(temp_dir), config, None, metadata, \"\", \"\")\n        hook.initialize([], build_data)\n\n        expected_file = temp_dir / \"baz.py\"\n        assert expected_file.is_file()\n        assert expected_file.read_text() == helpers.dedent(\n            \"\"\"\n            VER = '1.2.3'\n            \"\"\"\n        )\n        assert build_data[\"artifacts\"] == [\"/baz.py\"]\n\n\nclass TestPattern:\n    def test_default(self, temp_dir, helpers):\n        config = {\"path\": \"baz.py\", \"pattern\": True}\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"dynamic\": [\"version\"]},\n                \"tool\": {\"hatch\": {\"metadata\": {\"hooks\": {\"custom\": {}}}}},\n            },\n        )\n\n        file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n                class CustomHook(MetadataHookInterface):\n                    def update(self, metadata):\n                        metadata['version'] = '1.2.3'\n                \"\"\"\n            )\n        )\n        version_file = temp_dir / \"baz.py\"\n        version_file.write_text(\n            helpers.dedent(\n                \"\"\"\n                __version__ = '0.0.0'\n                \"\"\"\n            )\n        )\n\n        build_data = {\"artifacts\": []}\n        hook = VersionBuildHook(str(temp_dir), config, None, metadata, \"\", \"\")\n        hook.initialize([], build_data)\n\n        assert version_file.read_text() == helpers.dedent(\n            \"\"\"\n            __version__ = '1.2.3'\n            \"\"\"\n        )\n        assert build_data[\"artifacts\"] == [\"/baz.py\"]\n\n    def test_custom(self, temp_dir, helpers):\n        config = {\"path\": \"baz.py\", \"pattern\": 'v = \"(?P<version>.+)\"'}\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"dynamic\": [\"version\"]},\n                \"tool\": {\"hatch\": {\"metadata\": {\"hooks\": {\"custom\": {}}}}},\n            },\n        )\n\n        file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n                class CustomHook(MetadataHookInterface):\n                    def update(self, metadata):\n                        metadata['version'] = '1.2.3'\n                \"\"\"\n            )\n        )\n        version_file = temp_dir / \"baz.py\"\n        version_file.write_text(\n            helpers.dedent(\n                \"\"\"\n                v = \"0.0.0\"\n                \"\"\"\n            )\n        )\n\n        build_data = {\"artifacts\": []}\n        hook = VersionBuildHook(str(temp_dir), config, None, metadata, \"\", \"\")\n        hook.initialize([], build_data)\n\n        assert version_file.read_text() == helpers.dedent(\n            \"\"\"\n            v = \"1.2.3\"\n            \"\"\"\n        )\n        assert build_data[\"artifacts\"] == [\"/baz.py\"]\n"
  },
  {
    "path": "tests/backend/builders/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/builders/plugin/test_interface.py",
    "content": "from os.path import sep as path_sep\n\nimport pytest\n\nfrom hatchling.builders.constants import EXCLUDED_DIRECTORIES, EXCLUDED_FILES\nfrom hatchling.metadata.core import ProjectMetadata\nfrom hatchling.plugin.manager import PluginManager\n\nfrom ..utils import MockBuilder\n\n\nclass TestClean:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n        builder.clean(None, None)\n\n\nclass TestPluginManager:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert isinstance(builder.plugin_manager, PluginManager)\n\n    def test_reuse(self, isolation):\n        plugin_manager = PluginManager()\n        builder = MockBuilder(str(isolation), plugin_manager=plugin_manager)\n\n        assert builder.plugin_manager is plugin_manager\n\n\nclass TestRawConfig:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.raw_config == builder.raw_config == {}\n\n    def test_reuse(self, isolation):\n        config = {}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.raw_config is builder.raw_config is config\n\n    def test_read(self, temp_dir):\n        project_file = temp_dir / \"pyproject.toml\"\n        project_file.write_text(\"foo = 5\")\n\n        with temp_dir.as_cwd():\n            builder = MockBuilder(str(temp_dir))\n\n            assert builder.raw_config == builder.raw_config == {\"foo\": 5}\n\n\nclass TestMetadata:\n    def test_base(self, isolation):\n        config = {\"project\": {\"name\": \"foo\"}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert isinstance(builder.metadata, ProjectMetadata)\n        assert builder.metadata.core.name == \"foo\"\n\n    def test_core(self, isolation):\n        config = {\"project\": {}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.project_config == builder.project_config == config[\"project\"]\n\n    def test_hatch(self, isolation):\n        config = {\"tool\": {\"hatch\": {}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.hatch_config is builder.hatch_config is config[\"tool\"][\"hatch\"]\n\n    def test_build_config(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.build_config is builder.build_config is config[\"tool\"][\"hatch\"][\"build\"]\n\n    def test_build_config_not_table(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": \"foo\"}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build` must be a table\"):\n            _ = builder.build_config\n\n    def test_target_config(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.target_config is builder.target_config is config[\"tool\"][\"hatch\"][\"build\"][\"targets\"][\"foo\"]\n\n    def test_target_config_not_table(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": \"bar\"}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo` must be a table\"):\n            _ = builder.target_config\n\n\nclass TestProjectID:\n    def test_normalization(self, isolation):\n        config = {\"project\": {\"name\": \"my-app\", \"version\": \"1.0.0-rc.1\"}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.project_id == builder.project_id == \"my_app-1.0.0rc1\"\n\n\nclass TestBuildValidation:\n    def test_unknown_version(self, isolation):\n        config = {\n            \"project\": {\"name\": \"foo\", \"version\": \"0.1.0\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"versions\": [\"1\"]}}}}},\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n        builder.get_version_api = lambda: {\"1\": str}\n\n        with pytest.raises(ValueError, match=\"Unknown versions for target `foo`: 42, 9000\"):\n            next(builder.build(directory=str(isolation), versions=[\"9000\", \"42\"]))\n\n    def test_invalid_metadata(self, isolation):\n        config = {\n            \"project\": {\"name\": \"foo\", \"version\": \"0.1.0\", \"dynamic\": [\"version\"]},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"versions\": [\"1\"]}}}}},\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError,\n            match=\"Metadata field `version` cannot be both statically defined and listed in field `project.dynamic`\",\n        ):\n            next(builder.build(directory=str(isolation)))\n\n\nclass TestHookConfig:\n    def test_unknown(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"bar\": \"baz\"}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(ValueError, match=\"Unknown build hook: foo\"):\n            _ = builder.get_build_hooks(str(isolation))\n\n\nclass TestDirectoryRecursion:\n    @pytest.mark.requires_unix\n    def test_infinite_loop_prevention(self, temp_dir):\n        project_dir = temp_dir / \"project\"\n        project_dir.ensure_dir_exists()\n\n        with project_dir.as_cwd():\n            config = {\"tool\": {\"hatch\": {\"build\": {\"include\": [\"foo\", \"README.md\"]}}}}\n            builder = MockBuilder(str(project_dir), config=config)\n\n            (project_dir / \"README.md\").touch()\n            foo = project_dir / \"foo\"\n            foo.ensure_dir_exists()\n            (foo / \"bar.txt\").touch()\n\n            (foo / \"baz\").symlink_to(project_dir)\n\n            assert [f.path for f in builder.recurse_included_files()] == [\n                str(project_dir / \"README.md\"),\n                str(project_dir / \"foo\" / \"bar.txt\"),\n            ]\n\n    def test_only_include(self, temp_dir):\n        project_dir = temp_dir / \"project\"\n        project_dir.ensure_dir_exists()\n\n        with project_dir.as_cwd():\n            config = {\"tool\": {\"hatch\": {\"build\": {\"only-include\": [\"foo\"], \"artifacts\": [\"README.md\"]}}}}\n            builder = MockBuilder(str(project_dir), config=config)\n\n            (project_dir / \"README.md\").touch()\n            foo = project_dir / \"foo\"\n            foo.ensure_dir_exists()\n            (foo / \"bar.txt\").touch()\n\n            assert [f.path for f in builder.recurse_included_files()] == [str(project_dir / \"foo\" / \"bar.txt\")]\n\n    def test_no_duplication_force_include_only(self, temp_dir):\n        project_dir = temp_dir / \"project\"\n        project_dir.ensure_dir_exists()\n\n        with project_dir.as_cwd():\n            config = {\n                \"tool\": {\n                    \"hatch\": {\n                        \"build\": {\n                            \"force-include\": {\n                                \"../external.txt\": \"new/target2.txt\",\n                                \"old\": \"new\",\n                            },\n                        }\n                    }\n                }\n            }\n            builder = MockBuilder(str(project_dir), config=config)\n\n            (project_dir / \"foo.txt\").touch()\n            old = project_dir / \"old\"\n            old.ensure_dir_exists()\n            (old / \"target1.txt\").touch()\n            (old / \"target2.txt\").touch()\n\n            (temp_dir / \"external.txt\").touch()\n\n            build_data = builder.get_default_build_data()\n            builder.set_build_data_defaults(build_data)\n            with builder.config.set_build_data(build_data):\n                assert [(f.path, f.distribution_path) for f in builder.recurse_included_files()] == [\n                    (str(project_dir / \"foo.txt\"), \"foo.txt\"),\n                    (str(project_dir / \"old\" / \"target1.txt\"), f\"new{path_sep}target1.txt\"),\n                    (str(temp_dir / \"external.txt\"), f\"new{path_sep}target2.txt\"),\n                ]\n\n    def test_no_duplication_force_include_and_selection(self, temp_dir):\n        project_dir = temp_dir / \"project\"\n        project_dir.ensure_dir_exists()\n\n        with project_dir.as_cwd():\n            config = {\n                \"tool\": {\n                    \"hatch\": {\n                        \"build\": {\n                            \"include\": [\"foo.txt\", \"bar.txt\", \"baz.txt\"],\n                            \"force-include\": {\"../external.txt\": \"new/file.txt\"},\n                        }\n                    }\n                }\n            }\n            builder = MockBuilder(str(project_dir), config=config)\n\n            (project_dir / \"foo.txt\").touch()\n            (project_dir / \"bar.txt\").touch()\n            (project_dir / \"baz.txt\").touch()\n            (temp_dir / \"external.txt\").touch()\n\n            build_data = builder.get_default_build_data()\n            builder.set_build_data_defaults(build_data)\n            build_data[\"force_include\"][\"bar.txt\"] = \"bar.txt\"\n\n            with builder.config.set_build_data(build_data):\n                assert [(f.path, f.distribution_path) for f in builder.recurse_included_files()] == [\n                    (str(project_dir / \"baz.txt\"), \"baz.txt\"),\n                    (str(project_dir / \"foo.txt\"), \"foo.txt\"),\n                    (str(temp_dir / \"external.txt\"), f\"new{path_sep}file.txt\"),\n                    (str(project_dir / \"bar.txt\"), \"bar.txt\"),\n                ]\n\n    def test_no_duplication_force_include_with_sources(self, temp_dir):\n        project_dir = temp_dir / \"project\"\n        project_dir.ensure_dir_exists()\n\n        with project_dir.as_cwd():\n            config = {\n                \"tool\": {\n                    \"hatch\": {\n                        \"build\": {\n                            \"include\": [\"src\"],\n                            \"sources\": [\"src\"],\n                            \"force-include\": {\"../external.txt\": \"new/file.txt\"},\n                        }\n                    }\n                }\n            }\n            builder = MockBuilder(str(project_dir), config=config)\n\n            src_dir = project_dir / \"src\"\n            src_dir.mkdir()\n            (src_dir / \"foo.txt\").touch()\n            (src_dir / \"bar.txt\").touch()\n            (src_dir / \"baz.txt\").touch()\n            (temp_dir / \"external.txt\").touch()\n\n            build_data = builder.get_default_build_data()\n            builder.set_build_data_defaults(build_data)\n            build_data[\"force_include\"][\"src/bar.txt\"] = \"bar.txt\"\n\n            with builder.config.set_build_data(build_data):\n                assert [(f.path, f.distribution_path) for f in builder.recurse_included_files()] == [\n                    (str(src_dir / \"baz.txt\"), \"baz.txt\"),\n                    (str(src_dir / \"foo.txt\"), \"foo.txt\"),\n                    (str(temp_dir / \"external.txt\"), f\"new{path_sep}file.txt\"),\n                    (str(src_dir / \"bar.txt\"), \"bar.txt\"),\n                ]\n\n    def test_exists(self, temp_dir):\n        project_dir = temp_dir / \"project\"\n        project_dir.ensure_dir_exists()\n\n        with project_dir.as_cwd():\n            config = {\n                \"tool\": {\n                    \"hatch\": {\n                        \"build\": {\n                            \"force-include\": {\n                                \"../notfound\": \"target.txt\",\n                            },\n                        }\n                    }\n                }\n            }\n            builder = MockBuilder(str(project_dir), config=config)\n            build_data = builder.get_default_build_data()\n            builder.set_build_data_defaults(build_data)\n            with (\n                builder.config.set_build_data(build_data),\n                pytest.raises(FileNotFoundError, match=\"Forced include not found\"),\n            ):\n                list(builder.recurse_included_files())\n\n    def test_order(self, temp_dir):\n        project_dir = temp_dir / \"project\"\n        project_dir.ensure_dir_exists()\n\n        with project_dir.as_cwd():\n            config = {\n                \"tool\": {\n                    \"hatch\": {\n                        \"build\": {\n                            \"sources\": [\"src\"],\n                            \"include\": [\"src/foo\", \"bar\", \"README.md\", \"tox.ini\"],\n                            \"exclude\": [\"**/foo/baz.txt\"],\n                            \"force-include\": {\n                                \"../external1.txt\": \"nested/target2.txt\",\n                                \"../external2.txt\": \"nested/target1.txt\",\n                                \"../external\": \"nested\",\n                            },\n                        }\n                    }\n                }\n            }\n            builder = MockBuilder(str(project_dir), config=config)\n\n            foo = project_dir / \"src\" / \"foo\"\n            foo.ensure_dir_exists()\n            (foo / \"bar.txt\").touch()\n            (foo / \"baz.txt\").touch()\n\n            bar = project_dir / \"bar\"\n            bar.ensure_dir_exists()\n            (bar / \"foo.txt\").touch()\n\n            # Excluded\n            for name in EXCLUDED_DIRECTORIES:\n                excluded_dir = bar / name\n                excluded_dir.ensure_dir_exists()\n                (excluded_dir / \"file.ext\").touch()\n            for name in EXCLUDED_FILES:\n                excluded_file = bar / name\n                excluded_file.touch()\n\n            (project_dir / \"README.md\").touch()\n            (project_dir / \"tox.ini\").touch()\n\n            (temp_dir / \"external1.txt\").touch()\n            (temp_dir / \"external2.txt\").touch()\n\n            external = temp_dir / \"external\"\n            external.ensure_dir_exists()\n            (external / \"external1.txt\").touch()\n            (external / \"external2.txt\").touch()\n\n            # Excluded\n            for name in EXCLUDED_DIRECTORIES:\n                excluded_dir = external / name\n                excluded_dir.ensure_dir_exists()\n                (excluded_dir / \"file.ext\").touch()\n            for name in EXCLUDED_FILES:\n                excluded_file = external / name\n                excluded_file.touch()\n\n            assert [(f.path, f.distribution_path) for f in builder.recurse_included_files()] == [\n                (str(project_dir / \"README.md\"), \"README.md\"),\n                (str(project_dir / \"tox.ini\"), \"tox.ini\"),\n                (\n                    str(project_dir / \"bar\" / \"foo.txt\"),\n                    f\"bar{path_sep}foo.txt\",\n                ),\n                (str(project_dir / \"src\" / \"foo\" / \"bar.txt\"), f\"foo{path_sep}bar.txt\"),\n                (str(temp_dir / \"external\" / \"external1.txt\"), f\"nested{path_sep}external1.txt\"),\n                (str(temp_dir / \"external\" / \"external2.txt\"), f\"nested{path_sep}external2.txt\"),\n                (str(temp_dir / \"external2.txt\"), f\"nested{path_sep}target1.txt\"),\n                (str(temp_dir / \"external1.txt\"), f\"nested{path_sep}target2.txt\"),\n            ]\n"
  },
  {
    "path": "tests/backend/builders/test_binary.py",
    "content": "from __future__ import annotations\n\nimport os\nimport subprocess\nimport sys\nfrom typing import Any\n\nimport pytest\n\nfrom hatch.utils.fs import Path\nfrom hatch.utils.structures import EnvVars\nfrom hatchling.builders.app import AppBuilder\nfrom hatchling.builders.binary import BinaryBuilder\nfrom hatchling.builders.plugin.interface import BuilderInterface\n\npytestmark = [pytest.mark.requires_cargo, pytest.mark.requires_internet]\n\n\nclass ExpectedEnvVars:\n    def __init__(self, env_vars: dict):\n        self.env_vars = env_vars\n\n    def __eq__(self, other):\n        return all(not (key not in other or other[key] != value) for key, value in self.env_vars.items())\n\n    def __hash__(self):  # no cov\n        return hash(self.env_vars)\n\n\ndef cargo_install(*args: Any, **_kwargs: Any) -> subprocess.CompletedProcess:\n    executable_name = \"pyapp.exe\" if sys.platform == \"win32\" else \"pyapp\"\n    install_command: list[str] = args[0]\n    repo_path = os.environ.get(\"PYAPP_REPO\", \"\")\n    if repo_path:\n        temp_dir = install_command[install_command.index(\"--target-dir\") + 1]\n\n        build_target = os.environ.get(\"CARGO_BUILD_TARGET\", \"\")\n        if build_target:\n            executable = Path(temp_dir, build_target, \"release\", executable_name)\n        else:\n            executable = Path(temp_dir, \"release\", executable_name)\n\n        executable.parent.ensure_dir_exists()\n        executable.touch()\n    else:\n        temp_dir = install_command[install_command.index(\"--root\") + 1]\n\n        executable = Path(temp_dir, \"bin\", executable_name)\n        executable.parent.ensure_dir_exists()\n        executable.touch()\n\n    return subprocess.CompletedProcess(install_command, returncode=0, stdout=None, stderr=None)\n\n\ndef test_class():\n    assert issubclass(BinaryBuilder, BuilderInterface)\n\n\ndef test_class_legacy():\n    assert issubclass(AppBuilder, BinaryBuilder)\n\n\ndef test_default_versions(isolation):\n    builder = BinaryBuilder(str(isolation))\n\n    assert builder.get_default_versions() == [\"bootstrap\"]\n\n\nclass TestScripts:\n    def test_unset(self, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}}\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        assert builder.config.scripts == builder.config.scripts == []\n\n    def test_default(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"scripts\": {\"b\": \"foo.bar.baz:cli\", \"a\": \"baz.bar.foo:cli\"},\n            }\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        assert builder.config.scripts == [\"a\", \"b\"]\n\n    def test_specific(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"scripts\": {\"b\": \"foo.bar.baz:cli\", \"a\": \"baz.bar.foo:cli\"},\n            },\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"binary\": {\"scripts\": [\"a\", \"a\"]}}}}},\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        assert builder.config.scripts == [\"a\"]\n\n    def test_not_array(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"scripts\": {\"b\": \"foo.bar.baz:cli\", \"a\": \"baz.bar.foo:cli\"},\n            },\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"binary\": {\"scripts\": 9000}}}}},\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.binary.scripts` must be an array\"):\n            _ = builder.config.scripts\n\n    def test_script_not_string(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"scripts\": {\"b\": \"foo.bar.baz:cli\", \"a\": \"baz.bar.foo:cli\"},\n            },\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"binary\": {\"scripts\": [9000]}}}}},\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError, match=\"Script #1 of field `tool.hatch.build.targets.binary.scripts` must be a string\"\n        ):\n            _ = builder.config.scripts\n\n    def test_unknown_script(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"scripts\": {\"b\": \"foo.bar.baz:cli\", \"a\": \"baz.bar.foo:cli\"},\n            },\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"binary\": {\"scripts\": [\"c\"]}}}}},\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        with pytest.raises(ValueError, match=\"Unknown script in field `tool.hatch.build.targets.binary.scripts`: c\"):\n            _ = builder.config.scripts\n\n\nclass TestPythonVersion:\n    def test_default_no_source(self, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}}\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        assert builder.config.python_version == builder.config.python_version == builder.config.SUPPORTED_VERSIONS[0]\n\n    def test_default_explicit_source(self, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}}\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        with EnvVars({\"PYAPP_DISTRIBUTION_SOURCE\": \"url\"}):\n            assert builder.config.python_version == builder.config.python_version == \"\"\n\n    def test_set(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n            },\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"binary\": {\"python-version\": \"4.0\"}}}}},\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        assert builder.config.python_version == \"4.0\"\n\n    def test_not_string(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n            },\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"binary\": {\"python-version\": 9000}}}}},\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.binary.python-version` must be a string\"):\n            _ = builder.config.python_version\n\n    def test_compatibility(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"requires-python\": \"<3.11\",\n            },\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        assert builder.config.python_version == \"3.10\"\n\n    def test_incompatible(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"requires-python\": \">9000\",\n            },\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError, match=\"Field `project.requires-python` is incompatible with the known distributions\"\n        ):\n            _ = builder.config.python_version\n\n\nclass TestPyAppVersion:\n    def test_default(self, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}}\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        assert builder.config.pyapp_version == builder.config.pyapp_version == \"\"\n\n    def test_set(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n            },\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"binary\": {\"pyapp-version\": \"9000\"}}}}},\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        assert builder.config.pyapp_version == \"9000\"\n\n    def test_not_string(self, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n            },\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"binary\": {\"pyapp-version\": 9000}}}}},\n        }\n        builder = BinaryBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.binary.pyapp-version` must be a string\"):\n            _ = builder.config.pyapp_version\n\n\nclass TestBuildBootstrap:\n    def test_default(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"install\", \"pyapp\", \"--force\", \"--root\", mocker.ANY],\n            cwd=mocker.ANY,\n            env=ExpectedEnvVars({\"PYAPP_PROJECT_NAME\": \"my-app\", \"PYAPP_PROJECT_VERSION\": \"0.1.0\"}),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (build_path / \"binary\" / (\"my-app-0.1.0.exe\" if sys.platform == \"win32\" else \"my-app-0.1.0\")).is_file()\n\n    def test_default_build_target(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd({\"CARGO_BUILD_TARGET\": \"target\"}):\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"install\", \"pyapp\", \"--force\", \"--root\", mocker.ANY],\n            cwd=mocker.ANY,\n            env=ExpectedEnvVars({\"PYAPP_PROJECT_NAME\": \"my-app\", \"PYAPP_PROJECT_VERSION\": \"0.1.0\"}),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (\n            build_path / \"binary\" / (\"my-app-0.1.0-target.exe\" if sys.platform == \"win32\" else \"my-app-0.1.0-target\")\n        ).is_file()\n\n    def test_scripts(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\", \"scripts\": {\"foo\": \"bar.baz:cli\"}},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"install\", \"pyapp\", \"--force\", \"--root\", mocker.ANY],\n            cwd=mocker.ANY,\n            env=ExpectedEnvVars({\n                \"PYAPP_PROJECT_NAME\": \"my-app\",\n                \"PYAPP_PROJECT_VERSION\": \"0.1.0\",\n                \"PYAPP_EXEC_SPEC\": \"bar.baz:cli\",\n            }),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (build_path / \"binary\" / (\"foo-0.1.0.exe\" if sys.platform == \"win32\" else \"foo-0.1.0\")).is_file()\n\n    def test_scripts_build_target(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\", \"scripts\": {\"foo\": \"bar.baz:cli\"}},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd({\"CARGO_BUILD_TARGET\": \"target\"}):\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"install\", \"pyapp\", \"--force\", \"--root\", mocker.ANY],\n            cwd=mocker.ANY,\n            env=ExpectedEnvVars({\n                \"PYAPP_PROJECT_NAME\": \"my-app\",\n                \"PYAPP_PROJECT_VERSION\": \"0.1.0\",\n                \"PYAPP_EXEC_SPEC\": \"bar.baz:cli\",\n            }),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (\n            build_path / \"binary\" / (\"foo-0.1.0-target.exe\" if sys.platform == \"win32\" else \"foo-0.1.0-target\")\n        ).is_file()\n\n    def test_custom_cargo(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd({\"CARGO\": \"cross\"}):\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cross\", \"install\", \"pyapp\", \"--force\", \"--root\", mocker.ANY],\n            cwd=mocker.ANY,\n            env=ExpectedEnvVars({\"PYAPP_PROJECT_NAME\": \"my-app\", \"PYAPP_PROJECT_VERSION\": \"0.1.0\"}),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (build_path / \"binary\" / (\"my-app-0.1.0.exe\" if sys.platform == \"win32\" else \"my-app-0.1.0\")).is_file()\n\n    def test_no_cargo(self, hatch, temp_dir, mocker):\n        mocker.patch(\"shutil.which\", return_value=None)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        with pytest.raises(OSError, match=\"Executable `cargo` could not be found on PATH\"), project_path.as_cwd():\n            next(builder.build())\n\n    def test_python_version(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"], \"python-version\": \"4.0\"}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"install\", \"pyapp\", \"--force\", \"--root\", mocker.ANY],\n            cwd=mocker.ANY,\n            env=ExpectedEnvVars({\n                \"PYAPP_PROJECT_NAME\": \"my-app\",\n                \"PYAPP_PROJECT_VERSION\": \"0.1.0\",\n                \"PYAPP_PYTHON_VERSION\": \"4.0\",\n            }),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (build_path / \"binary\" / (\"my-app-0.1.0.exe\" if sys.platform == \"win32\" else \"my-app-0.1.0\")).is_file()\n\n    def test_pyapp_version(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"], \"pyapp-version\": \"9000\"}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"install\", \"pyapp\", \"--force\", \"--root\", mocker.ANY, \"--version\", \"9000\"],\n            cwd=mocker.ANY,\n            env=ExpectedEnvVars({\"PYAPP_PROJECT_NAME\": \"my-app\", \"PYAPP_PROJECT_VERSION\": \"0.1.0\"}),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (build_path / \"binary\" / (\"my-app-0.1.0.exe\" if sys.platform == \"win32\" else \"my-app-0.1.0\")).is_file()\n\n    def test_verbosity(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd({\"HATCH_QUIET\": \"1\"}):\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"install\", \"pyapp\", \"--force\", \"--root\", mocker.ANY],\n            cwd=mocker.ANY,\n            env=ExpectedEnvVars({\"PYAPP_PROJECT_NAME\": \"my-app\", \"PYAPP_PROJECT_VERSION\": \"0.1.0\"}),\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (build_path / \"binary\" / (\"my-app-0.1.0.exe\" if sys.platform == \"win32\" else \"my-app-0.1.0\")).is_file()\n\n    def test_local_build_with_build_target(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd({\"PYAPP_REPO\": \"test-path\", \"CARGO_BUILD_TARGET\": \"target\"}):\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"build\", \"--release\", \"--target-dir\", mocker.ANY],\n            cwd=\"test-path\",\n            env=ExpectedEnvVars({\"PYAPP_PROJECT_NAME\": \"my-app\", \"PYAPP_PROJECT_VERSION\": \"0.1.0\"}),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (\n            build_path / \"binary\" / (\"my-app-0.1.0-target.exe\" if sys.platform == \"win32\" else \"my-app-0.1.0-target\")\n        ).is_file()\n\n    def test_local_build_no_build_target(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"binary\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = BinaryBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd({\"PYAPP_REPO\": \"test-path\"}):\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"build\", \"--release\", \"--target-dir\", mocker.ANY],\n            cwd=\"test-path\",\n            env=ExpectedEnvVars({\"PYAPP_PROJECT_NAME\": \"my-app\", \"PYAPP_PROJECT_VERSION\": \"0.1.0\"}),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (build_path / \"binary\" / (\"my-app-0.1.0.exe\" if sys.platform == \"win32\" else \"my-app-0.1.0\")).is_file()\n\n    def test_legacy(self, hatch, temp_dir, mocker):\n        subprocess_run = mocker.patch(\"subprocess.run\", side_effect=cargo_install)\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"version\": \"0.1.0\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"targets\": {\"app\": {\"versions\": [\"bootstrap\"]}}},\n                },\n            },\n        }\n        builder = AppBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        subprocess_run.assert_called_once_with(\n            [\"cargo\", \"install\", \"pyapp\", \"--force\", \"--root\", mocker.ANY],\n            cwd=mocker.ANY,\n            env=ExpectedEnvVars({\"PYAPP_PROJECT_NAME\": \"my-app\", \"PYAPP_PROJECT_VERSION\": \"0.1.0\"}),\n        )\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert (build_path / \"app\" / (\"my-app-0.1.0.exe\" if sys.platform == \"win32\" else \"my-app-0.1.0\")).is_file()\n"
  },
  {
    "path": "tests/backend/builders/test_config.py",
    "content": "import os\nimport re\nfrom os.path import join as pjoin\n\nimport pathspec\nimport pytest\n\nfrom hatch.utils.structures import EnvVars\nfrom hatchling.builders.constants import BuildEnvVars\n\nfrom .utils import MockBuilder\n\n\nclass TestDirectory:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.directory == builder.config.directory == str(isolation / \"dist\")\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"directory\": \"bar\"}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.directory == str(isolation / \"bar\")\n\n    def test_target_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"directory\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.directory` must be a string\"):\n            _ = builder.config.directory\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"directory\": \"bar\"}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.directory == str(isolation / \"bar\")\n\n    def test_global_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"directory\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.directory` must be a string\"):\n            _ = builder.config.directory\n\n    def test_target_overrides_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"directory\": \"bar\", \"targets\": {\"foo\": {\"directory\": \"baz\"}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.directory == str(isolation / \"baz\")\n\n    def test_absolute_path(self, isolation):\n        absolute_path = str(isolation)\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"directory\": absolute_path}}}}}}\n        builder = MockBuilder(absolute_path, config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.directory == absolute_path\n\n\nclass TestSkipExcludedDirs:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.skip_excluded_dirs is builder.config.skip_excluded_dirs is False\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"skip-excluded-dirs\": True}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.skip_excluded_dirs is True\n\n    def test_target_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"skip-excluded-dirs\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Field `tool.hatch.build.targets.foo.skip-excluded-dirs` must be a boolean\"\n        ):\n            _ = builder.config.skip_excluded_dirs\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"skip-excluded-dirs\": True}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.skip_excluded_dirs is True\n\n    def test_global_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"skip-excluded-dirs\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.skip-excluded-dirs` must be a boolean\"):\n            _ = builder.config.skip_excluded_dirs\n\n    def test_target_overrides_global(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\"build\": {\"skip-excluded-dirs\": True, \"targets\": {\"foo\": {\"skip-excluded-dirs\": False}}}}\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.skip_excluded_dirs is False\n\n\nclass TestIgnoreVCS:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.ignore_vcs is builder.config.ignore_vcs is False\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"ignore-vcs\": True}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.ignore_vcs is True\n\n    def test_target_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"ignore-vcs\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.ignore-vcs` must be a boolean\"):\n            _ = builder.config.ignore_vcs\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"ignore-vcs\": True}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.ignore_vcs is True\n\n    def test_global_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"ignore-vcs\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.ignore-vcs` must be a boolean\"):\n            _ = builder.config.ignore_vcs\n\n    def test_target_overrides_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"ignore-vcs\": True, \"targets\": {\"foo\": {\"ignore-vcs\": False}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.ignore_vcs is False\n\n\nclass TestRequireRuntimeDependencies:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.require_runtime_dependencies is builder.config.require_runtime_dependencies is False\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"require-runtime-dependencies\": True}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.require_runtime_dependencies is True\n\n    def test_target_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"require-runtime-dependencies\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError,\n            match=\"Field `tool.hatch.build.targets.foo.require-runtime-dependencies` must be a boolean\",\n        ):\n            _ = builder.config.require_runtime_dependencies\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"require-runtime-dependencies\": True}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.require_runtime_dependencies is True\n\n    def test_global_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"require-runtime-dependencies\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.require-runtime-dependencies` must be a boolean\"):\n            _ = builder.config.require_runtime_dependencies\n\n    def test_target_overrides_global(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"require-runtime-dependencies\": True,\n                        \"targets\": {\"foo\": {\"require-runtime-dependencies\": False}},\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.require_runtime_dependencies is False\n\n\nclass TestRequireRuntimeFeatures:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.require_runtime_features == builder.config.require_runtime_features == []\n\n    def test_target(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"foo\": [], \"bar\": []}},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"require-runtime-features\": [\"foo\", \"bar\"]}}}}},\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.require_runtime_features == [\"foo\", \"bar\"]\n\n    def test_target_not_array(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"require-runtime-features\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Field `tool.hatch.build.targets.foo.require-runtime-features` must be an array\"\n        ):\n            _ = builder.config.require_runtime_features\n\n    def test_target_feature_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"require-runtime-features\": [9000]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError,\n            match=\"Feature #1 of field `tool.hatch.build.targets.foo.require-runtime-features` must be a string\",\n        ):\n            _ = builder.config.require_runtime_features\n\n    def test_target_feature_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"require-runtime-features\": [\"\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Feature #1 of field `tool.hatch.build.targets.foo.require-runtime-features` cannot be an empty string\"\n            ),\n        ):\n            _ = builder.config.require_runtime_features\n\n    def test_target_feature_unknown(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"require-runtime-features\": [\"foo_bar\"]}}}}},\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Feature `foo-bar` of field `tool.hatch.build.targets.foo.require-runtime-features` is not defined in \"\n                \"field `project.optional-dependencies`\"\n            ),\n        ):\n            _ = builder.config.require_runtime_features\n\n    def test_global(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"foo\": [], \"bar\": []}},\n            \"tool\": {\"hatch\": {\"build\": {\"require-runtime-features\": [\"foo\", \"bar\"]}}},\n        }\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.require_runtime_features == [\"foo\", \"bar\"]\n\n    def test_global_not_array(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"require-runtime-features\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.require-runtime-features` must be an array\"):\n            _ = builder.config.require_runtime_features\n\n    def test_global_feature_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"require-runtime-features\": [9000]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError, match=\"Feature #1 of field `tool.hatch.build.require-runtime-features` must be a string\"\n        ):\n            _ = builder.config.require_runtime_features\n\n    def test_global_feature_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"require-runtime-features\": [\"\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=\"Feature #1 of field `tool.hatch.build.require-runtime-features` cannot be an empty string\",\n        ):\n            _ = builder.config.require_runtime_features\n\n    def test_global_feature_unknown(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"require-runtime-features\": [\"foo_bar\"]}}},\n        }\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Feature `foo-bar` of field `tool.hatch.build.require-runtime-features` is not defined in \"\n                \"field `project.optional-dependencies`\"\n            ),\n        ):\n            _ = builder.config.require_runtime_features\n\n    def test_target_overrides_global(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"foo_bar\": [], \"bar_baz\": []}},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"require-runtime-features\": [\"bar_baz\"],\n                        \"targets\": {\"foo\": {\"require-runtime-features\": [\"foo_bar\"]}},\n                    }\n                }\n            },\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.require_runtime_features == [\"foo-bar\"]\n\n\nclass TestOnlyPackages:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.only_packages is builder.config.only_packages is False\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"only-packages\": True}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.only_packages is True\n\n    def test_target_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"only-packages\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.only-packages` must be a boolean\"):\n            _ = builder.config.only_packages\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"only-packages\": True}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.only_packages is True\n\n    def test_global_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"only-packages\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.only-packages` must be a boolean\"):\n            _ = builder.config.only_packages\n\n    def test_target_overrides_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"only-packages\": True, \"targets\": {\"foo\": {\"only-packages\": False}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.only_packages is False\n\n\nclass TestReproducible:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.reproducible is builder.config.reproducible is True\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"reproducible\": False}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.reproducible is False\n\n    def test_target_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"reproducible\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.reproducible` must be a boolean\"):\n            _ = builder.config.reproducible\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"reproducible\": False}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.reproducible is False\n\n    def test_global_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"reproducible\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.reproducible` must be a boolean\"):\n            _ = builder.config.reproducible\n\n    def test_target_overrides_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"reproducible\": False, \"targets\": {\"foo\": {\"reproducible\": True}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.reproducible is True\n\n\nclass TestDevModeDirs:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.dev_mode_dirs == builder.config.dev_mode_dirs == []\n\n    def test_global_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"dev-mode-dirs\": \"\"}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.dev-mode-dirs` must be an array of strings\"):\n            _ = builder.config.dev_mode_dirs\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"dev-mode-dirs\": [\"foo\", \"bar/baz\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.dev_mode_dirs == [\"foo\", \"bar/baz\"]\n\n    def test_global_pattern_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"dev-mode-dirs\": [0]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Directory #1 in field `tool.hatch.build.dev-mode-dirs` must be a string\"):\n            _ = builder.config.dev_mode_dirs\n\n    def test_global_pattern_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"dev-mode-dirs\": [\"\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError, match=\"Directory #1 in field `tool.hatch.build.dev-mode-dirs` cannot be an empty string\"\n        ):\n            _ = builder.config.dev_mode_dirs\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"dev-mode-dirs\": [\"foo\", \"bar/baz\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dev_mode_dirs == [\"foo\", \"bar/baz\"]\n\n    def test_target_pattern_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"dev-mode-dirs\": [0]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Directory #1 in field `tool.hatch.build.targets.foo.dev-mode-dirs` must be a string\"\n        ):\n            _ = builder.config.dev_mode_dirs\n\n    def test_target_pattern_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"dev-mode-dirs\": [\"\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError,\n            match=\"Directory #1 in field `tool.hatch.build.targets.foo.dev-mode-dirs` cannot be an empty string\",\n        ):\n            _ = builder.config.dev_mode_dirs\n\n    def test_target_overrides_global(self, isolation):\n        config = {\n            \"tool\": {\"hatch\": {\"build\": {\"dev-mode-dirs\": [\"foo\"], \"targets\": {\"foo\": {\"dev-mode-dirs\": [\"bar\"]}}}}}\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dev_mode_dirs == [\"bar\"]\n\n\nclass TestDevModeExact:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.dev_mode_exact is builder.config.dev_mode_exact is False\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"dev-mode-exact\": True}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dev_mode_exact is True\n\n    def test_target_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"dev-mode-exact\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.dev-mode-exact` must be a boolean\"):\n            _ = builder.config.dev_mode_exact\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"dev-mode-exact\": True}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dev_mode_exact is True\n\n    def test_global_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"dev-mode-exact\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.dev-mode-exact` must be a boolean\"):\n            _ = builder.config.dev_mode_exact\n\n    def test_target_overrides_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"dev-mode-exact\": True, \"targets\": {\"foo\": {\"dev-mode-exact\": False}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dev_mode_exact is False\n\n\nclass TestPackages:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.packages == builder.config.packages == []\n\n    def test_global_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"packages\": \"\"}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.packages` must be an array of strings\"):\n            _ = builder.config.packages\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"packages\": [\"src/foo\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert len(builder.config.packages) == 1\n        assert builder.config.packages[0] == pjoin(\"src\", \"foo\")\n\n    def test_global_package_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"packages\": [0]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Package #1 in field `tool.hatch.build.packages` must be a string\"):\n            _ = builder.config.packages\n\n    def test_global_package_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"packages\": [\"\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError, match=\"Package #1 in field `tool.hatch.build.packages` cannot be an empty string\"\n        ):\n            _ = builder.config.packages\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"packages\": [\"src/foo\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert len(builder.config.packages) == 1\n        assert builder.config.packages[0] == pjoin(\"src\", \"foo\")\n\n    def test_target_package_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"packages\": [0]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Package #1 in field `tool.hatch.build.targets.foo.packages` must be a string\"\n        ):\n            _ = builder.config.packages\n\n    def test_target_package_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"packages\": [\"\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError, match=\"Package #1 in field `tool.hatch.build.targets.foo.packages` cannot be an empty string\"\n        ):\n            _ = builder.config.packages\n\n    def test_target_overrides_global(self, isolation):\n        config = {\n            \"tool\": {\"hatch\": {\"build\": {\"packages\": [\"src/foo\"], \"targets\": {\"foo\": {\"packages\": [\"pkg/foo\"]}}}}}\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert len(builder.config.packages) == 1\n        assert builder.config.packages[0] == pjoin(\"pkg\", \"foo\")\n\n    def test_no_source(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"packages\": [\"foo\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert len(builder.config.packages) == 1\n        assert builder.config.packages[0] == pjoin(\"foo\")\n\n\nclass TestSources:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.sources == builder.config.sources == {}\n        assert builder.config.get_distribution_path(pjoin(\"src\", \"foo\", \"bar.py\")) == pjoin(\"src\", \"foo\", \"bar.py\")\n\n    def test_global_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": \"\"}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.sources` must be a mapping or array of strings\"):\n            _ = builder.config.sources\n\n    def test_global_array(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": [\"src\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[pjoin(\"src\", \"\")] == \"\"\n        assert builder.config.get_distribution_path(pjoin(\"src\", \"foo\", \"bar.py\")) == pjoin(\"foo\", \"bar.py\")\n\n    def test_global_array_source_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": [0]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Source #1 in field `tool.hatch.build.sources` must be a string\"):\n            _ = builder.config.sources\n\n    def test_global_array_source_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": [\"\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(ValueError, match=\"Source #1 in field `tool.hatch.build.sources` cannot be an empty string\"):\n            _ = builder.config.sources\n\n    def test_global_mapping(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": {\"src/foo\": \"renamed\"}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[pjoin(\"src\", \"foo\", \"\")] == pjoin(\"renamed\", \"\")\n        assert builder.config.get_distribution_path(pjoin(\"src\", \"foo\", \"bar.py\")) == pjoin(\"renamed\", \"bar.py\")\n\n    def test_global_mapping_source_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": {\"\": \"renamed\"}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[\"\"] == pjoin(\"renamed\", \"\")\n        assert builder.config.get_distribution_path(\"bar.py\") == pjoin(\"renamed\", \"bar.py\")\n        assert builder.config.get_distribution_path(pjoin(\"foo\", \"bar.py\")) == pjoin(\"renamed\", \"foo\", \"bar.py\")\n\n    def test_global_mapping_path_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": {\"src/foo\": \"\"}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[pjoin(\"src\", \"foo\", \"\")] == \"\"\n        assert builder.config.get_distribution_path(pjoin(\"src\", \"foo\", \"bar.py\")) == \"bar.py\"\n\n    def test_global_mapping_replacement_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": {\"src/foo\": 0}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError, match=\"Path for source `src/foo` in field `tool.hatch.build.sources` must be a string\"\n        ):\n            _ = builder.config.sources\n\n    def test_target_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"sources\": \"\"}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Field `tool.hatch.build.targets.foo.sources` must be a mapping or array of strings\"\n        ):\n            _ = builder.config.sources\n\n    def test_target_array(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"sources\": [\"src\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[pjoin(\"src\", \"\")] == \"\"\n        assert builder.config.get_distribution_path(pjoin(\"src\", \"foo\", \"bar.py\")) == pjoin(\"foo\", \"bar.py\")\n\n    def test_target_array_source_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"sources\": [0]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Source #1 in field `tool.hatch.build.targets.foo.sources` must be a string\"\n        ):\n            _ = builder.config.sources\n\n    def test_target_array_source_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"sources\": [\"\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError, match=\"Source #1 in field `tool.hatch.build.targets.foo.sources` cannot be an empty string\"\n        ):\n            _ = builder.config.sources\n\n    def test_target_mapping(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"sources\": {\"src/foo\": \"renamed\"}}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[pjoin(\"src\", \"foo\", \"\")] == pjoin(\"renamed\", \"\")\n        assert builder.config.get_distribution_path(pjoin(\"src\", \"foo\", \"bar.py\")) == pjoin(\"renamed\", \"bar.py\")\n\n    def test_target_mapping_source_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"sources\": {\"\": \"renamed\"}}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[\"\"] == pjoin(\"renamed\", \"\")\n        assert builder.config.get_distribution_path(pjoin(\"bar.py\")) == pjoin(\"renamed\", \"bar.py\")\n\n    def test_target_mapping_path_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"sources\": {\"src/foo\": \"\"}}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[pjoin(\"src\", \"foo\", \"\")] == \"\"\n        assert builder.config.get_distribution_path(pjoin(\"src\", \"foo\", \"bar.py\")) == \"bar.py\"\n\n    def test_target_mapping_replacement_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"sources\": {\"src/foo\": 0}}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError,\n            match=\"Path for source `src/foo` in field `tool.hatch.build.targets.foo.sources` must be a string\",\n        ):\n            _ = builder.config.sources\n\n    def test_target_overrides_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": [\"src\"], \"targets\": {\"foo\": {\"sources\": [\"pkg\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[pjoin(\"pkg\", \"\")] == \"\"\n        assert builder.config.get_distribution_path(pjoin(\"pkg\", \"foo\", \"bar.py\")) == pjoin(\"foo\", \"bar.py\")\n        assert builder.config.get_distribution_path(pjoin(\"src\", \"foo\", \"bar.py\")) == pjoin(\"src\", \"foo\", \"bar.py\")\n\n    def test_no_source(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": [\"bar\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[pjoin(\"bar\", \"\")] == \"\"\n        assert builder.config.get_distribution_path(pjoin(\"foo\", \"bar.py\")) == pjoin(\"foo\", \"bar.py\")\n\n    def test_compatible_with_packages(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"sources\": {\"src/foo\": \"renamed\"}, \"packages\": [\"src/foo\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert len(builder.config.sources) == 1\n        assert builder.config.sources[pjoin(\"src\", \"foo\", \"\")] == pjoin(\"renamed\", \"\")\n        assert builder.config.get_distribution_path(pjoin(\"src\", \"foo\", \"bar.py\")) == pjoin(\"renamed\", \"bar.py\")\n\n\nclass TestForceInclude:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.force_include == builder.config.force_include == {}\n\n    def test_global_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"force-include\": \"\"}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.force-include` must be a mapping\"):\n            _ = builder.config.force_include\n\n    def test_global_absolute(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"force-include\": {str(isolation / \"source\"): \"/target/\"}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.force_include == {str(isolation / \"source\"): \"target\"}\n\n    def test_global_relative(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"force-include\": {\"../source\": \"/target/\"}}}}}\n        builder = MockBuilder(str(isolation / \"foo\"), config=config)\n\n        assert builder.config.force_include == {str(isolation / \"source\"): \"target\"}\n\n    def test_global_source_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"force-include\": {\"\": \"/target/\"}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError, match=\"Source #1 in field `tool.hatch.build.force-include` cannot be an empty string\"\n        ):\n            _ = builder.config.force_include\n\n    def test_global_relative_path_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"force-include\": {\"source\": 0}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError, match=\"Path for source `source` in field `tool.hatch.build.force-include` must be a string\"\n        ):\n            _ = builder.config.force_include\n\n    def test_global_relative_path_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"force-include\": {\"source\": \"\"}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=\"Path for source `source` in field `tool.hatch.build.force-include` cannot be an empty string\",\n        ):\n            _ = builder.config.force_include\n\n    def test_target_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"force-include\": \"\"}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.force-include` must be a mapping\"):\n            _ = builder.config.force_include\n\n    def test_target_absolute(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\"build\": {\"targets\": {\"foo\": {\"force-include\": {str(isolation / \"source\"): \"/target/\"}}}}}\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.force_include == {str(isolation / \"source\"): \"target\"}\n\n    def test_target_relative(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"force-include\": {\"../source\": \"/target/\"}}}}}}}\n        builder = MockBuilder(str(isolation / \"foo\"), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.force_include == {str(isolation / \"source\"): \"target\"}\n\n    def test_target_source_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"force-include\": {\"\": \"/target/\"}}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError,\n            match=\"Source #1 in field `tool.hatch.build.targets.foo.force-include` cannot be an empty string\",\n        ):\n            _ = builder.config.force_include\n\n    def test_target_relative_path_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"force-include\": {\"source\": 0}}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError,\n            match=\"Path for source `source` in field `tool.hatch.build.targets.foo.force-include` must be a string\",\n        ):\n            _ = builder.config.force_include\n\n    def test_target_relative_path_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"force-include\": {\"source\": \"\"}}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Path for source `source` in field `tool.hatch.build.targets.foo.force-include` \"\n                \"cannot be an empty string\"\n            ),\n        ):\n            _ = builder.config.force_include\n\n    def test_order(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"force-include\": {\n                            \"../very-nested\": \"target1/embedded\",\n                            \"../source1\": \"/target2/\",\n                            \"../source2\": \"/target1/\",\n                        }\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation / \"foo\"), config=config)\n\n        assert builder.config.force_include == {\n            str(isolation / \"source2\"): \"target1\",\n            str(isolation / \"very-nested\"): f\"target1{os.sep}embedded\",\n            str(isolation / \"source1\"): \"target2\",\n        }\n\n\nclass TestOnlyInclude:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.only_include == builder.config.only_include == {}\n\n    def test_global_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"only-include\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.only-include` must be an array\"):\n            _ = builder.config.only_include\n\n    def test_global_path_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"only-include\": [9000]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Path #1 in field `tool.hatch.build.only-include` must be a string\"):\n            _ = builder.config.only_include\n\n    @pytest.mark.parametrize(\"path\", [\"/\", \"~/foo\", \"../foo\"])\n    def test_global_not_relative(self, isolation, path):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"only-include\": [path]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError, match=f\"Path #1 in field `tool.hatch.build.only-include` must be relative: {path}\"\n        ):\n            _ = builder.config.only_include\n\n    def test_global_duplicate(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"only-include\": [\"/foo//bar\", \"foo//bar/\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError, match=re.escape(f\"Duplicate path in field `tool.hatch.build.only-include`: foo{os.sep}bar\")\n        ):\n            _ = builder.config.only_include\n\n    def test_global_correct(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"only-include\": [\"/foo//bar/\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.only_include == {f\"{isolation}{os.sep}foo{os.sep}bar\": f\"foo{os.sep}bar\"}\n\n    def test_target_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"only-include\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.only-include` must be an array\"):\n            _ = builder.config.only_include\n\n    def test_target_path_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"only-include\": [9000]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Path #1 in field `tool.hatch.build.targets.foo.only-include` must be a string\"\n        ):\n            _ = builder.config.only_include\n\n    @pytest.mark.parametrize(\"path\", [\"/\", \"~/foo\", \"../foo\"])\n    def test_target_not_relative(self, isolation, path):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"only-include\": [path]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError, match=f\"Path #1 in field `tool.hatch.build.targets.foo.only-include` must be relative: {path}\"\n        ):\n            _ = builder.config.only_include\n\n    def test_target_duplicate(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"only-include\": [\"/foo//bar\", \"foo//bar/\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError,\n            match=re.escape(f\"Duplicate path in field `tool.hatch.build.targets.foo.only-include`: foo{os.sep}bar\"),\n        ):\n            _ = builder.config.only_include\n\n    def test_target_correct(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"only-include\": [\"/foo//bar/\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.only_include == {f\"{isolation}{os.sep}foo{os.sep}bar\": f\"foo{os.sep}bar\"}\n\n\nclass TestVersions:\n    def test_default_known(self, isolation):\n        builder = MockBuilder(str(isolation))\n        builder.PLUGIN_NAME = \"foo\"\n        builder.get_version_api = lambda: {\"2\": str, \"1\": str}\n\n        assert builder.config.versions == builder.config.versions == [\"2\", \"1\"]\n\n    def test_default_override(self, isolation):\n        builder = MockBuilder(str(isolation))\n        builder.PLUGIN_NAME = \"foo\"\n        builder.get_default_versions = lambda: [\"old\", \"new\", \"new\"]\n\n        assert builder.config.versions == builder.config.versions == [\"old\", \"new\"]\n\n    def test_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"versions\": \"\"}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Field `tool.hatch.build.targets.foo.versions` must be an array of strings\"\n        ):\n            _ = builder.config.versions\n\n    def test_correct(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"versions\": [\"3.14\", \"1\", \"3.14\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n        builder.get_version_api = lambda: {\"3.14\": str, \"42\": str, \"1\": str}\n\n        assert builder.config.versions == builder.config.versions == [\"3.14\", \"1\"]\n\n    def test_empty_default(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"versions\": []}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n        builder.get_default_versions = lambda: [\"old\", \"new\"]\n\n        assert builder.config.versions == builder.config.versions == [\"old\", \"new\"]\n\n    def test_version_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"versions\": [1]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Version #1 in field `tool.hatch.build.targets.foo.versions` must be a string\"\n        ):\n            _ = builder.config.versions\n\n    def test_version_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"versions\": [\"\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError, match=\"Version #1 in field `tool.hatch.build.targets.foo.versions` cannot be an empty string\"\n        ):\n            _ = builder.config.versions\n\n    def test_unknown_version(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"versions\": [\"9000\", \"1\", \"42\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n        builder.get_version_api = lambda: {\"1\": str}\n\n        with pytest.raises(\n            ValueError, match=\"Unknown versions in field `tool.hatch.build.targets.foo.versions`: 42, 9000\"\n        ):\n            _ = builder.config.versions\n\n\nclass TestHookConfig:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.hook_config == builder.config.hook_config == {}\n\n    def test_target_not_table(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"hooks\": \"bar\"}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.hooks` must be a table\"):\n            _ = builder.config.hook_config\n\n    def test_target_hook_not_table(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"hooks\": {\"bar\": \"baz\"}}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.hooks.bar` must be a table\"):\n            _ = builder.config.hook_config\n\n    def test_global_not_table(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": \"foo\"}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.hooks` must be a table\"):\n            _ = builder.config.hook_config\n\n    def test_global_hook_not_table(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": \"bar\"}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.hooks.foo` must be a table\"):\n            _ = builder.config.hook_config\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"bar\": \"baz\"}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.hook_config[\"foo\"][\"bar\"] == \"baz\"\n\n    def test_order(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"hooks\": {\"foo\": {\"bar\": \"baz\"}}, \"targets\": {\"foo\": {\"hooks\": {\"baz\": {\"foo\": \"bar\"}}}}}\n                }\n            },\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.hook_config == {\"foo\": {\"bar\": \"baz\"}, \"baz\": {\"foo\": \"bar\"}}\n\n    def test_target_overrides_global(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\"hooks\": {\"foo\": {\"bar\": \"baz\"}}, \"targets\": {\"foo\": {\"hooks\": {\"foo\": {\"baz\": \"bar\"}}}}}\n                }\n            },\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.hook_config[\"foo\"][\"baz\"] == \"bar\"\n\n    def test_env_var_no_hooks(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"bar\": \"baz\"}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with EnvVars({BuildEnvVars.NO_HOOKS: \"true\"}):\n            assert builder.config.hook_config == {}\n\n    def test_enable_by_default(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\"bar\": \"baz\", \"enable-by-default\": False},\n                            \"bar\": {\"foo\": \"baz\", \"enable-by-default\": False},\n                            \"baz\": {\"foo\": \"bar\"},\n                        }\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.hook_config == {\"baz\": {\"foo\": \"bar\"}}\n\n    def test_env_var_all_override_enable_by_default(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\"bar\": \"baz\", \"enable-by-default\": False},\n                            \"bar\": {\"foo\": \"baz\", \"enable-by-default\": False},\n                            \"baz\": {\"foo\": \"bar\"},\n                        }\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n\n        with EnvVars({BuildEnvVars.HOOKS_ENABLE: \"true\"}):\n            assert builder.config.hook_config == {\n                \"foo\": {\"bar\": \"baz\", \"enable-by-default\": False},\n                \"bar\": {\"foo\": \"baz\", \"enable-by-default\": False},\n                \"baz\": {\"foo\": \"bar\"},\n            }\n\n    def test_env_var_specific_override_enable_by_default(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\"bar\": \"baz\", \"enable-by-default\": False},\n                            \"bar\": {\"foo\": \"baz\", \"enable-by-default\": False},\n                            \"baz\": {\"foo\": \"bar\"},\n                        }\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n\n        with EnvVars({f\"{BuildEnvVars.HOOK_ENABLE_PREFIX}FOO\": \"true\"}):\n            assert builder.config.hook_config == {\n                \"foo\": {\"bar\": \"baz\", \"enable-by-default\": False},\n                \"baz\": {\"foo\": \"bar\"},\n            }\n\n\nclass TestDependencies:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.dependencies == builder.config.dependencies == []\n\n    def test_target_not_array(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"dependencies\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.dependencies` must be an array\"):\n            _ = builder.config.dependencies\n\n    def test_target_dependency_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"dependencies\": [9000]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Dependency #1 of field `tool.hatch.build.targets.foo.dependencies` must be a string\"\n        ):\n            _ = builder.config.dependencies\n\n    def test_global_not_array(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"dependencies\": 9000}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.dependencies` must be an array\"):\n            _ = builder.config.dependencies\n\n    def test_global_dependency_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"dependencies\": [9000]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Dependency #1 of field `tool.hatch.build.dependencies` must be a string\"):\n            _ = builder.config.dependencies\n\n    def test_hook_require_runtime_dependencies_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"require-runtime-dependencies\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Option `require-runtime-dependencies` of build hook `foo` must be a boolean\"\n        ):\n            _ = builder.config.dependencies\n\n    def test_hook_require_runtime_features_not_array(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"require-runtime-features\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Option `require-runtime-features` of build hook `foo` must be an array\"):\n            _ = builder.config.dependencies\n\n    def test_hook_require_runtime_features_feature_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"require-runtime-features\": [9000]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Feature #1 of option `require-runtime-features` of build hook `foo` must be a string\"\n        ):\n            _ = builder.config.dependencies\n\n    def test_hook_require_runtime_features_feature_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"require-runtime-features\": [\"\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError,\n            match=\"Feature #1 of option `require-runtime-features` of build hook `foo` cannot be an empty string\",\n        ):\n            _ = builder.config.dependencies\n\n    def test_hook_require_runtime_features_feature_unknown(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"require-runtime-features\": [\"foo_bar\"]}}}}},\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Feature `foo-bar` of option `require-runtime-features` of build hook `foo` is not defined in \"\n                \"field `project.optional-dependencies`\"\n            ),\n        ):\n            _ = builder.config.dependencies\n\n    def test_hook_dependencies_not_array(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"dependencies\": 9000}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(TypeError, match=\"Option `dependencies` of build hook `foo` must be an array\"):\n            _ = builder.config.dependencies\n\n    def test_hook_dependency_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"hooks\": {\"foo\": {\"dependencies\": [9000]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Dependency #1 of option `dependencies` of build hook `foo` must be a string\"\n        ):\n            _ = builder.config.dependencies\n\n    def test_correct(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"dependencies\": [\"bar\"],\n                        \"hooks\": {\"foobar\": {\"dependencies\": [\"test1\"]}},\n                        \"targets\": {\"foo\": {\"dependencies\": [\"baz\"], \"hooks\": {\"foobar\": {\"dependencies\": [\"test2\"]}}}},\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dependencies == [\"baz\", \"bar\", \"test2\"]\n\n    def test_require_runtime_dependencies(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\", \"dependencies\": [\"foo\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"require-runtime-dependencies\": True,\n                        \"dependencies\": [\"bar\"],\n                        \"hooks\": {\"foobar\": {\"dependencies\": [\"test1\"]}},\n                        \"targets\": {\"foo\": {\"dependencies\": [\"baz\"], \"hooks\": {\"foobar\": {\"dependencies\": [\"test2\"]}}}},\n                    }\n                }\n            },\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dependencies == [\"baz\", \"bar\", \"test2\", \"foo\"]\n\n    def test_require_runtime_features(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"bar_baz\": [\"foo\"]}},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"require-runtime-features\": [\"bar-baz\"],\n                        \"dependencies\": [\"bar\"],\n                        \"hooks\": {\"foobar\": {\"dependencies\": [\"test1\"]}},\n                        \"targets\": {\"foo\": {\"dependencies\": [\"baz\"], \"hooks\": {\"foobar\": {\"dependencies\": [\"test2\"]}}}},\n                    }\n                }\n            },\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dependencies == [\"baz\", \"bar\", \"test2\", \"foo\"]\n\n    def test_env_var_no_hooks(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\"dependencies\": [\"foo\"]},\n                            \"bar\": {\"dependencies\": [\"bar\"]},\n                            \"baz\": {\"dependencies\": [\"baz\"]},\n                        },\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with EnvVars({BuildEnvVars.NO_HOOKS: \"true\"}):\n            assert builder.config.dependencies == []\n\n    def test_hooks_enable_by_default(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\"dependencies\": [\"foo\"], \"enable-by-default\": False},\n                            \"bar\": {\"dependencies\": [\"bar\"], \"enable-by-default\": False},\n                            \"baz\": {\"dependencies\": [\"baz\"]},\n                        },\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dependencies == [\"baz\"]\n\n    def test_hooks_env_var_all_override_enable_by_default(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\"dependencies\": [\"foo\"], \"enable-by-default\": False},\n                            \"bar\": {\"dependencies\": [\"bar\"], \"enable-by-default\": False},\n                            \"baz\": {\"dependencies\": [\"baz\"]},\n                        },\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with EnvVars({BuildEnvVars.HOOKS_ENABLE: \"true\"}):\n            assert builder.config.dependencies == [\"foo\", \"bar\", \"baz\"]\n\n    def test_hooks_env_var_specific_override_enable_by_default(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\"dependencies\": [\"foo\"], \"enable-by-default\": False},\n                            \"bar\": {\"dependencies\": [\"bar\"], \"enable-by-default\": False},\n                            \"baz\": {\"dependencies\": [\"baz\"]},\n                        },\n                    }\n                }\n            }\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with EnvVars({f\"{BuildEnvVars.HOOK_ENABLE_PREFIX}FOO\": \"true\"}):\n            assert builder.config.dependencies == [\"foo\", \"baz\"]\n\n    def test_hooks_require_runtime_dependencies(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\", \"dependencies\": [\"baz\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\"dependencies\": [\"foo\"], \"enable-by-default\": False},\n                            \"bar\": {\"dependencies\": [\"bar\"], \"require-runtime-dependencies\": True},\n                        },\n                    }\n                }\n            },\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dependencies == [\"bar\", \"baz\"]\n\n    def test_hooks_require_runtime_dependencies_disabled(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\", \"dependencies\": [\"baz\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\n                                \"dependencies\": [\"foo\"],\n                                \"enable-by-default\": False,\n                                \"require-runtime-dependencies\": True,\n                            },\n                            \"bar\": {\"dependencies\": [\"bar\"]},\n                        },\n                    }\n                }\n            },\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dependencies == [\"bar\"]\n\n    def test_hooks_require_runtime_features(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"foo_bar\": [\"baz\"]}},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\"dependencies\": [\"foo\"], \"enable-by-default\": False},\n                            \"bar\": {\"dependencies\": [\"bar\"], \"require-runtime-features\": [\"foo-bar\"]},\n                        },\n                    }\n                }\n            },\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dependencies == [\"bar\", \"baz\"]\n\n    def test_hooks_require_runtime_features_disabled(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"foo_bar\": [\"baz\"]}},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"hooks\": {\n                            \"foo\": {\n                                \"dependencies\": [\"foo\"],\n                                \"enable-by-default\": False,\n                                \"require-runtime-features\": [\"foo-bar\"],\n                            },\n                            \"bar\": {\"dependencies\": [\"bar\"]},\n                        },\n                    }\n                }\n            },\n        }\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.dependencies == [\"bar\"]\n\n\nclass TestFileSelectionDefaults:\n    def test_include(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.default_include() == []\n\n    def test_exclude(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.default_exclude() == []\n\n    def test_packages(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.default_packages() == []\n\n    def test_only_include(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.default_only_include() == []\n\n    def test_global_exclude(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.default_global_exclude() == [\"*.py[cdo]\", \"/dist\"]\n\n\nclass TestPatternInclude:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.include_spec is None\n\n    def test_global_becomes_spec(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"include\": [\"foo\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert isinstance(builder.config.include_spec, pathspec.GitIgnoreSpec)\n\n    def test_global_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"include\": \"\"}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.include` must be an array of strings\"):\n            _ = builder.config.include_spec\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_global(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"include\": [\"foo\", \"bar/baz\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.include_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.include_spec.match_file(f\"bar{separator}baz{separator}file.py\")\n        assert not builder.config.include_spec.match_file(f\"bar{separator}file.py\")\n\n    def test_global_pattern_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"include\": [0]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Pattern #1 in field `tool.hatch.build.include` must be a string\"):\n            _ = builder.config.include_spec\n\n    def test_global_pattern_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"include\": [\"\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError, match=\"Pattern #1 in field `tool.hatch.build.include` cannot be an empty string\"\n        ):\n            _ = builder.config.include_spec\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_global_packages_included(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"packages\": [\"bar\"], \"include\": [\"foo\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.include_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.include_spec.match_file(f\"bar{separator}baz{separator}file.py\")\n        assert not builder.config.include_spec.match_file(f\"baz{separator}bar{separator}file.py\")\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_target(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"include\": [\"foo\", \"bar/baz\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.include_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.include_spec.match_file(f\"bar{separator}baz{separator}file.py\")\n        assert not builder.config.include_spec.match_file(f\"bar{separator}file.py\")\n\n    def test_target_pattern_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"include\": [0]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Pattern #1 in field `tool.hatch.build.targets.foo.include` must be a string\"\n        ):\n            _ = builder.config.include_spec\n\n    def test_target_pattern_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"include\": [\"\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError, match=\"Pattern #1 in field `tool.hatch.build.targets.foo.include` cannot be an empty string\"\n        ):\n            _ = builder.config.include_spec\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_target_overrides_global(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"include\": [\"foo\"], \"targets\": {\"foo\": {\"include\": [\"bar\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert not builder.config.include_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.include_spec.match_file(f\"bar{separator}file.py\")\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_target_packages_included(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"packages\": [\"bar\"], \"include\": [\"foo\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.include_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.include_spec.match_file(f\"bar{separator}baz{separator}file.py\")\n        assert not builder.config.include_spec.match_file(f\"baz{separator}bar{separator}file.py\")\n\n\nclass TestPatternExclude:\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_default(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        builder = MockBuilder(str(isolation))\n\n        assert isinstance(builder.config.exclude_spec, pathspec.GitIgnoreSpec)\n        assert builder.config.exclude_spec.match_file(f\"dist{separator}file.py\")\n\n    def test_global_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": \"\"}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.exclude` must be an array of strings\"):\n            _ = builder.config.exclude_spec\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_global(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": [\"foo\", \"bar/baz\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.exclude_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.exclude_spec.match_file(f\"bar{separator}baz{separator}file.py\")\n        assert not builder.config.exclude_spec.match_file(f\"bar{separator}file.py\")\n\n    def test_global_pattern_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": [0]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Pattern #1 in field `tool.hatch.build.exclude` must be a string\"):\n            _ = builder.config.exclude_spec\n\n    def test_global_pattern_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": [\"\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError, match=\"Pattern #1 in field `tool.hatch.build.exclude` cannot be an empty string\"\n        ):\n            _ = builder.config.exclude_spec\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_target(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"exclude\": [\"foo\", \"bar/baz\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.exclude_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.exclude_spec.match_file(f\"bar{separator}baz{separator}file.py\")\n        assert not builder.config.exclude_spec.match_file(f\"bar{separator}file.py\")\n\n    def test_target_pattern_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"exclude\": [0]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Pattern #1 in field `tool.hatch.build.targets.foo.exclude` must be a string\"\n        ):\n            _ = builder.config.exclude_spec\n\n    def test_target_pattern_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"exclude\": [\"\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError, match=\"Pattern #1 in field `tool.hatch.build.targets.foo.exclude` cannot be an empty string\"\n        ):\n            _ = builder.config.exclude_spec\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_target_overrides_global(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": [\"foo\"], \"targets\": {\"foo\": {\"exclude\": [\"bar\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert not builder.config.exclude_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.exclude_spec.match_file(f\"bar{separator}file.py\")\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_vcs_git(self, temp_dir, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        with temp_dir.as_cwd():\n            config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": [\"foo\"]}}}}\n            builder = MockBuilder(str(temp_dir), config=config)\n\n            vcs_ignore_file = temp_dir / \".gitignore\"\n            vcs_ignore_file.write_text(\"/bar\\n*.pyc\")\n\n            assert builder.config.exclude_spec.match_file(f\"foo{separator}file.py\")\n            assert builder.config.exclude_spec.match_file(f\"bar{separator}file.py\")\n            assert builder.config.exclude_spec.match_file(f\"baz{separator}bar{separator}file.pyc\")\n            assert not builder.config.exclude_spec.match_file(f\"baz{separator}bar{separator}file.py\")\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_ignore_vcs_git(self, temp_dir, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        with temp_dir.as_cwd():\n            config = {\"tool\": {\"hatch\": {\"build\": {\"ignore-vcs\": True, \"exclude\": [\"foo\"]}}}}\n            builder = MockBuilder(str(temp_dir), config=config)\n\n            vcs_ignore_file = temp_dir / \".gitignore\"\n            vcs_ignore_file.write_text(\"/bar\\n*.pyc\")\n\n            assert builder.config.exclude_spec.match_file(f\"foo{separator}file.py\")\n            assert not builder.config.exclude_spec.match_file(f\"bar{separator}file.py\")\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_vcs_git_boundary(self, temp_dir, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        project_dir = temp_dir / \"project\"\n        project_dir.mkdir()\n        (project_dir / \".git\").mkdir()\n\n        with project_dir.as_cwd():\n            config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": [\"foo\"]}}}}\n            builder = MockBuilder(str(project_dir), config=config)\n\n            vcs_ignore_file = temp_dir / \".gitignore\"\n            vcs_ignore_file.write_text(\"/bar\\n*.pyc\")\n\n            assert builder.config.exclude_spec.match_file(f\"foo{separator}file.py\")\n            assert not builder.config.exclude_spec.match_file(f\"bar{separator}file.py\")\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_vcs_git_exclude_whitelisted_file(self, temp_dir, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        with temp_dir.as_cwd():\n            config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": [\"foo/bar\"]}}}}\n            builder = MockBuilder(str(temp_dir), config=config)\n\n            vcs_ignore_file = temp_dir / \".gitignore\"\n            vcs_ignore_file.write_text(\"foo/*\\n!foo/bar\")\n\n            assert builder.config.path_is_excluded(f\"foo{separator}deb\") is True\n            assert builder.config.path_is_excluded(f\"foo{separator}bar\") is True\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_vcs_mercurial(self, temp_dir, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        with temp_dir.as_cwd():\n            config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": [\"foo\"]}}}}\n            builder = MockBuilder(str(temp_dir), config=config)\n\n            vcs_ignore_file = temp_dir / \".hgignore\"\n            vcs_ignore_file.write_text(\"syntax: glob\\n/bar\\n*.pyc\")\n\n            assert builder.config.exclude_spec.match_file(f\"foo{separator}file.py\")\n            assert builder.config.exclude_spec.match_file(f\"bar{separator}file.py\")\n            assert builder.config.exclude_spec.match_file(f\"baz{separator}bar{separator}file.pyc\")\n            assert not builder.config.exclude_spec.match_file(f\"baz{separator}bar{separator}file.py\")\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_ignore_vcs_mercurial(self, temp_dir, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        with temp_dir.as_cwd():\n            config = {\"tool\": {\"hatch\": {\"build\": {\"ignore-vcs\": True, \"exclude\": [\"foo\"]}}}}\n            builder = MockBuilder(str(temp_dir), config=config)\n\n            vcs_ignore_file = temp_dir / \".hgignore\"\n            vcs_ignore_file.write_text(\"syntax: glob\\n/bar\\n*.pyc\")\n\n            assert builder.config.exclude_spec.match_file(f\"foo{separator}file.py\")\n            assert not builder.config.exclude_spec.match_file(f\"bar{separator}file.py\")\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_vcs_mercurial_boundary(self, temp_dir, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        project_dir = temp_dir / \"project\"\n        project_dir.mkdir()\n        (project_dir / \".hg\").mkdir()\n\n        with project_dir.as_cwd():\n            config = {\"tool\": {\"hatch\": {\"build\": {\"exclude\": [\"foo\"]}}}}\n            builder = MockBuilder(str(project_dir), config=config)\n\n            vcs_ignore_file = temp_dir / \".hgignore\"\n            vcs_ignore_file.write_text(\"syntax: glob\\n/bar\\n*.pyc\")\n\n            assert builder.config.exclude_spec.match_file(f\"foo{separator}file.py\")\n            assert not builder.config.exclude_spec.match_file(f\"bar{separator}file.py\")\n\n    def test_override_default_global_exclude_patterns(self, isolation):\n        builder = MockBuilder(str(isolation))\n        builder.config.default_global_exclude = list\n\n        assert builder.config.exclude_spec is None\n        assert not builder.config.path_is_excluded(\".git/file\")\n\n\nclass TestPatternArtifacts:\n    def test_default(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.artifact_spec is None\n\n    def test_global_becomes_spec(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"artifacts\": [\"foo\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert isinstance(builder.config.artifact_spec, pathspec.GitIgnoreSpec)\n\n    def test_global_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"artifacts\": \"\"}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.artifacts` must be an array of strings\"):\n            _ = builder.config.artifact_spec\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_global(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"artifacts\": [\"foo\", \"bar/baz\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.artifact_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.artifact_spec.match_file(f\"bar{separator}baz{separator}file.py\")\n        assert not builder.config.artifact_spec.match_file(f\"bar{separator}file.py\")\n\n    def test_global_pattern_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"artifacts\": [0]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Pattern #1 in field `tool.hatch.build.artifacts` must be a string\"):\n            _ = builder.config.artifact_spec\n\n    def test_global_pattern_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"artifacts\": [\"\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError, match=\"Pattern #1 in field `tool.hatch.build.artifacts` cannot be an empty string\"\n        ):\n            _ = builder.config.artifact_spec\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_target(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"artifacts\": [\"foo\", \"bar/baz\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert builder.config.artifact_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.artifact_spec.match_file(f\"bar{separator}baz{separator}file.py\")\n        assert not builder.config.artifact_spec.match_file(f\"bar{separator}file.py\")\n\n    def test_target_pattern_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"artifacts\": [0]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            TypeError, match=\"Pattern #1 in field `tool.hatch.build.targets.foo.artifacts` must be a string\"\n        ):\n            _ = builder.config.artifact_spec\n\n    def test_target_pattern_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"foo\": {\"artifacts\": [\"\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        with pytest.raises(\n            ValueError, match=\"Pattern #1 in field `tool.hatch.build.targets.foo.artifacts` cannot be an empty string\"\n        ):\n            _ = builder.config.artifact_spec\n\n    @pytest.mark.parametrize(\"separator\", [\"/\", \"\\\\\"])\n    def test_target_overrides_global(self, isolation, separator, platform):\n        if separator == \"\\\\\" and not platform.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        config = {\"tool\": {\"hatch\": {\"build\": {\"artifacts\": [\"foo\"], \"targets\": {\"foo\": {\"artifacts\": [\"bar\"]}}}}}}\n        builder = MockBuilder(str(isolation), config=config)\n        builder.PLUGIN_NAME = \"foo\"\n\n        assert not builder.config.artifact_spec.match_file(f\"foo{separator}file.py\")\n        assert builder.config.artifact_spec.match_file(f\"bar{separator}file.py\")\n\n\nclass TestPatternMatching:\n    def test_include_explicit(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"include\": [\"foo\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.include_path(\"foo/file.py\")\n        assert not builder.config.include_path(\"bar/file.py\")\n\n    def test_no_include_greedy(self, isolation):\n        builder = MockBuilder(str(isolation))\n\n        assert builder.config.include_path(\"foo/file.py\")\n        assert builder.config.include_path(\"bar/file.py\")\n\n    def test_exclude_precedence(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"include\": [\"foo\"], \"exclude\": [\"foo\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert not builder.config.include_path(\"foo/file.py\")\n        assert not builder.config.include_path(\"bar/file.py\")\n\n    def test_artifact_super_precedence(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"include\": [\"foo\"], \"exclude\": [\"foo\"], \"artifacts\": [\"foo\"]}}}}\n        builder = MockBuilder(str(isolation), config=config)\n\n        assert builder.config.include_path(\"foo/file.py\")\n        assert not builder.config.include_path(\"bar/file.py\")\n"
  },
  {
    "path": "tests/backend/builders/test_custom.py",
    "content": "import re\nimport zipfile\n\nimport pytest\n\nfrom hatchling.builders.custom import CustomBuilder\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\n\ndef test_target_config_not_table(isolation):\n    config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"custom\": 9000}}}}}\n\n    with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.custom` must be a table\"):\n        CustomBuilder(str(isolation), config=config)\n\n\ndef test_no_path(isolation):\n    config = {\n        \"tool\": {\n            \"hatch\": {\n                \"build\": {\"targets\": {\"custom\": {\"path\": \"\"}}},\n            },\n        },\n    }\n\n    with pytest.raises(ValueError, match=\"Option `path` for builder `custom` must not be empty if defined\"):\n        CustomBuilder(str(isolation), config=config)\n\n\ndef test_path_not_string(isolation):\n    config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"custom\": {\"path\": 3}}}}}}\n\n    with pytest.raises(TypeError, match=\"Option `path` for builder `custom` must be a string\"):\n        CustomBuilder(str(isolation), config=config)\n\n\ndef test_nonexistent(isolation):\n    config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"custom\": {\"path\": \"test.py\"}}}}}}\n\n    with pytest.raises(OSError, match=\"Build script does not exist: test.py\"):\n        CustomBuilder(str(isolation), config=config)\n\n\ndef test_default(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    config = {\n        \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n        \"tool\": {\n            \"hatch\": {\n                \"version\": {\"path\": \"my_app/__about__.py\"},\n                \"build\": {\"targets\": {\"custom\": {}}},\n            },\n        },\n    }\n\n    file_path = project_path / DEFAULT_BUILD_SCRIPT\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            import os\n\n            from hatchling.builders.wheel import WheelBuilder\n\n            def get_builder():\n                return CustomWheelBuilder\n\n            class CustomWheelBuilder(WheelBuilder):\n                def build(self, **kwargs):\n                    for i, artifact in enumerate(super().build(**kwargs)):\n                        build_dir = os.path.dirname(artifact)\n                        new_path = os.path.join(build_dir, f'{self.PLUGIN_NAME}-{i}.whl')\n                        os.replace(artifact, new_path)\n                        yield new_path\n            \"\"\"\n        )\n    )\n    builder = CustomBuilder(str(project_path), config=config)\n\n    build_path = project_path / \"dist\"\n\n    with project_path.as_cwd():\n        artifacts = list(builder.build())\n\n    assert len(artifacts) == 1\n    expected_artifact = artifacts[0]\n\n    build_artifacts = list(build_path.iterdir())\n    assert len(build_artifacts) == 1\n    assert expected_artifact == str(build_artifacts[0])\n    assert expected_artifact == str(build_path / \"custom-0.whl\")\n\n    extraction_directory = temp_dir / \"_archive\"\n    extraction_directory.mkdir()\n\n    with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n        zip_archive.extractall(str(extraction_directory))\n\n    metadata_directory = f\"{builder.project_id}.dist-info\"\n    expected_files = helpers.get_template_files(\n        \"wheel.standard_default_license_single\", project_name, metadata_directory=metadata_directory\n    )\n    helpers.assert_files(extraction_directory, expected_files)\n\n    # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n    # https://stackoverflow.com/q/9813243\n    with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n        zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n        assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n\ndef test_explicit_path(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    config = {\n        \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n        \"tool\": {\n            \"hatch\": {\n                \"version\": {\"path\": \"my_app/__about__.py\"},\n                \"build\": {\"targets\": {\"custom\": {\"path\": f\"foo/{DEFAULT_BUILD_SCRIPT}\"}}},\n            },\n        },\n    }\n\n    file_path = project_path / \"foo\" / DEFAULT_BUILD_SCRIPT\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            import os\n\n            from hatchling.builders.wheel import WheelBuilder\n\n            def get_builder():\n                return CustomWheelBuilder\n\n            class CustomWheelBuilder(WheelBuilder):\n                def build(self, **kwargs):\n                    for i, artifact in enumerate(super().build(**kwargs)):\n                        build_dir = os.path.dirname(artifact)\n                        new_path = os.path.join(build_dir, f'{self.PLUGIN_NAME}-{i}.whl')\n                        os.replace(artifact, new_path)\n                        yield new_path\n            \"\"\"\n        )\n    )\n    builder = CustomBuilder(str(project_path), config=config)\n\n    build_path = project_path / \"dist\"\n\n    with project_path.as_cwd():\n        artifacts = list(builder.build())\n\n    assert len(artifacts) == 1\n    expected_artifact = artifacts[0]\n\n    build_artifacts = list(build_path.iterdir())\n    assert len(build_artifacts) == 1\n    assert expected_artifact == str(build_artifacts[0])\n    assert expected_artifact == str(build_path / \"custom-0.whl\")\n\n    extraction_directory = temp_dir / \"_archive\"\n    extraction_directory.mkdir()\n\n    with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n        zip_archive.extractall(str(extraction_directory))\n\n    metadata_directory = f\"{builder.project_id}.dist-info\"\n    expected_files = helpers.get_template_files(\n        \"wheel.standard_default_license_single\", project_name, metadata_directory=metadata_directory\n    )\n    helpers.assert_files(extraction_directory, expected_files)\n\n    # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n    # https://stackoverflow.com/q/9813243\n    with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n        zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n        assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n\ndef test_no_subclass(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    config = {\n        \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n        \"tool\": {\n            \"hatch\": {\n                \"version\": {\"path\": \"my_app/__about__.py\"},\n                \"build\": {\"targets\": {\"custom\": {\"path\": f\"foo/{DEFAULT_BUILD_SCRIPT}\"}}},\n            },\n        },\n    }\n\n    file_path = project_path / \"foo\" / DEFAULT_BUILD_SCRIPT\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.builders.plugin.interface import BuilderInterface\n\n            foo = None\n            bar = 'baz'\n\n            class CustomBuilder:\n                pass\n            \"\"\"\n        )\n    )\n\n    with (\n        pytest.raises(\n            ValueError,\n            match=re.escape(\n                f\"Unable to find a subclass of `BuilderInterface` in `foo/{DEFAULT_BUILD_SCRIPT}`: {temp_dir}\"\n            ),\n        ),\n        project_path.as_cwd(),\n    ):\n        CustomBuilder(str(project_path), config=config)\n\n\ndef test_multiple_subclasses(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    config = {\n        \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n        \"tool\": {\n            \"hatch\": {\n                \"version\": {\"path\": \"my_app/__about__.py\"},\n                \"build\": {\"targets\": {\"custom\": {\"path\": f\"foo/{DEFAULT_BUILD_SCRIPT}\"}}},\n            },\n        },\n    }\n\n    file_path = project_path / \"foo\" / DEFAULT_BUILD_SCRIPT\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            import os\n\n            from hatchling.builders.wheel import WheelBuilder\n\n            class CustomWheelBuilder(WheelBuilder):\n                pass\n            \"\"\"\n        )\n    )\n\n    with (\n        pytest.raises(\n            ValueError,\n            match=re.escape(\n                f\"Multiple subclasses of `BuilderInterface` found in `foo/{DEFAULT_BUILD_SCRIPT}`, select \"\n                f\"one by defining a function named `get_builder`: {temp_dir}\"\n            ),\n        ),\n        project_path.as_cwd(),\n    ):\n        CustomBuilder(str(project_path), config=config)\n\n\ndef test_dynamic_dependencies(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    config = {\n        \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n        \"tool\": {\n            \"hatch\": {\n                \"version\": {\"path\": \"my_app/__about__.py\"},\n                \"build\": {\n                    \"targets\": {\"custom\": {\"dependencies\": [\"foo\"], \"hooks\": {\"custom\": {\"dependencies\": [\"bar\"]}}}}\n                },\n            },\n        },\n    }\n\n    file_path = project_path / DEFAULT_BUILD_SCRIPT\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n            from hatchling.builders.wheel import WheelBuilder\n\n            def get_builder():\n                return CustomWheelBuilder\n\n            class CustomWheelBuilder(WheelBuilder):\n                pass\n\n            class CustomHook(BuildHookInterface):\n                def dependencies(self):\n                    return ['baz']\n            \"\"\"\n        )\n    )\n    builder = CustomBuilder(str(project_path), config=config)\n\n    assert builder.config.dependencies == [\"foo\", \"bar\", \"baz\"]\n"
  },
  {
    "path": "tests/backend/builders/test_sdist.py",
    "content": "import os\nimport tarfile\n\nimport pytest\n\nfrom hatchling.builders.plugin.interface import BuilderInterface\nfrom hatchling.builders.sdist import SdistBuilder\nfrom hatchling.builders.utils import get_reproducible_timestamp\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION, get_core_metadata_constructors\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE\n\n\ndef test_class():\n    assert issubclass(SdistBuilder, BuilderInterface)\n\n\ndef test_default_versions(isolation):\n    builder = SdistBuilder(str(isolation))\n\n    assert builder.get_default_versions() == [\"standard\"]\n\n\nclass TestSupportLegacy:\n    def test_default(self, isolation):\n        builder = SdistBuilder(str(isolation))\n\n        assert builder.config.support_legacy is builder.config.support_legacy is False\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"sdist\": {\"support-legacy\": True}}}}}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.config.support_legacy is builder.config.support_legacy is True\n\n\nclass TestCoreMetadataConstructor:\n    def test_default(self, isolation):\n        builder = SdistBuilder(str(isolation))\n\n        assert builder.config.core_metadata_constructor is builder.config.core_metadata_constructor\n        assert builder.config.core_metadata_constructor is get_core_metadata_constructors()[DEFAULT_METADATA_VERSION]\n\n    def test_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"sdist\": {\"core-metadata-version\": 42}}}}}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError, match=\"Field `tool.hatch.build.targets.sdist.core-metadata-version` must be a string\"\n        ):\n            _ = builder.config.core_metadata_constructor\n\n    def test_unknown(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"sdist\": {\"core-metadata-version\": \"9000\"}}}}}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                f\"Unknown metadata version `9000` for field `tool.hatch.build.targets.sdist.core-metadata-version`. \"\n                f\"Available: {', '.join(sorted(get_core_metadata_constructors()))}\"\n            ),\n        ):\n            _ = builder.config.core_metadata_constructor\n\n\nclass TestStrictNaming:\n    def test_default(self, isolation):\n        builder = SdistBuilder(str(isolation))\n\n        assert builder.config.strict_naming is builder.config.strict_naming is True\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"sdist\": {\"strict-naming\": False}}}}}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.config.strict_naming is False\n\n    def test_target_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"sdist\": {\"strict-naming\": 9000}}}}}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.sdist.strict-naming` must be a boolean\"):\n            _ = builder.config.strict_naming\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"strict-naming\": False}}}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.config.strict_naming is False\n\n    def test_global_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"strict-naming\": 9000}}}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.strict-naming` must be a boolean\"):\n            _ = builder.config.strict_naming\n\n    def test_target_overrides_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"strict-naming\": False, \"targets\": {\"sdist\": {\"strict-naming\": True}}}}}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.config.strict_naming is True\n\n\nclass TestConstructSetupPyFile:\n    def test_default(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n            )\n            \"\"\"\n        )\n\n    def test_packages(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_description(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"description\": \"foo\"}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                description='foo',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_readme(self, helpers, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n            }\n        }\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                long_description='test content\\\\n',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_authors_name(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}]}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                author='foo',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_authors_email(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"foo@domain\"}]}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                author_email='foo@domain',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_authors_name_and_email(self, helpers, isolation):\n        config = {\n            \"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}]}\n        }\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                author_email='foo <bar@domain>',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_authors_multiple(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                author='foo, bar',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_maintainers_name(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}]}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                maintainer='foo',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_maintainers_email(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"email\": \"foo@domain\"}]}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                maintainer_email='foo@domain',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_maintainers_name_and_email(self, helpers, isolation):\n        config = {\n            \"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}]}\n        }\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                maintainer_email='foo <bar@domain>',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_maintainers_multiple(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                maintainer='foo, bar',\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_classifiers(self, helpers, isolation):\n        classifiers = [\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.9\",\n        ]\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"classifiers\": classifiers}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                classifiers=[\n                    'Programming Language :: Python :: 3.9',\n                    'Programming Language :: Python :: 3.11',\n                ],\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_dependencies(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                install_requires=[\n                    'bar==5',\n                    'foo==1',\n                ],\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_dependencies_extra(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")], [\"baz==3\"]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                install_requires=[\n                    'bar==5',\n                    'foo==1',\n                    'baz==3',\n                ],\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_optional_dependencies(self, helpers, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"optional-dependencies\": {\n                    \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                    \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                },\n            }\n        }\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                extras_require={\n                    'feature1': [\n                        'bar==5; python_version < \"3\"',\n                        'foo==1',\n                    ],\n                    'feature2': [\n                        'bar==5',\n                        'foo==1; python_version < \"3\"',\n                    ],\n                },\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_scripts(self, helpers, isolation):\n        config = {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"scripts\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"}}}\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                entry_points={\n                    'console_scripts': [\n                        'bar = pkg:foo',\n                        'foo = pkg:bar',\n                    ],\n                },\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_gui_scripts(self, helpers, isolation):\n        config = {\n            \"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"gui-scripts\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"}}\n        }\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                entry_points={\n                    'gui_scripts': [\n                        'bar = pkg:foo',\n                        'foo = pkg:bar',\n                    ],\n                },\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_entry_points(self, helpers, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"entry-points\": {\n                    \"foo\": {\"bar\": \"pkg:foo\", \"foo\": \"pkg:bar\"},\n                    \"bar\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"},\n                },\n            }\n        }\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                entry_points={\n                    'bar': [\n                        'bar = pkg:foo',\n                        'foo = pkg:bar',\n                    ],\n                    'foo': [\n                        'bar = pkg:foo',\n                        'foo = pkg:bar',\n                    ],\n                },\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n    def test_all(self, helpers, isolation):\n        config = {\n            \"project\": {\n                \"name\": \"My.App\",\n                \"version\": \"0.1.0\",\n                \"description\": \"foo\",\n                \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n                \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                \"classifiers\": [\n                    \"Programming Language :: Python :: 3.11\",\n                    \"Programming Language :: Python :: 3.9\",\n                ],\n                \"dependencies\": [\"foo==1\", \"bar==5\"],\n                \"optional-dependencies\": {\n                    \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                    \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                    \"feature3\": [],\n                },\n                \"scripts\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"},\n                \"gui-scripts\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"},\n                \"entry-points\": {\n                    \"foo\": {\"bar\": \"pkg:foo\", \"foo\": \"pkg:bar\"},\n                    \"bar\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"},\n                },\n            }\n        }\n        builder = SdistBuilder(str(isolation), config=config)\n\n        assert builder.construct_setup_py_file([\"my_app\", os.path.join(\"my_app\", \"pkg\")]) == helpers.dedent(\n            \"\"\"\n            from setuptools import setup\n\n            setup(\n                name='my-app',\n                version='0.1.0',\n                description='foo',\n                long_description='test content\\\\n',\n                author_email='foo <bar@domain>',\n                maintainer_email='foo <bar@domain>',\n                classifiers=[\n                    'Programming Language :: Python :: 3.9',\n                    'Programming Language :: Python :: 3.11',\n                ],\n                install_requires=[\n                    'bar==5',\n                    'foo==1',\n                ],\n                extras_require={\n                    'feature1': [\n                        'bar==5; python_version < \"3\"',\n                        'foo==1',\n                    ],\n                    'feature2': [\n                        'bar==5',\n                        'foo==1; python_version < \"3\"',\n                    ],\n                },\n                entry_points={\n                    'console_scripts': [\n                        'bar = pkg:foo',\n                        'foo = pkg:bar',\n                    ],\n                    'gui_scripts': [\n                        'bar = pkg:foo',\n                        'foo = pkg:bar',\n                    ],\n                    'bar': [\n                        'bar = pkg:foo',\n                        'foo = pkg:bar',\n                    ],\n                    'foo': [\n                        'bar = pkg:foo',\n                        'foo = pkg:bar',\n                    ],\n                },\n                packages=[\n                    'my_app',\n                    'my_app.pkg',\n                ],\n            )\n            \"\"\"\n        )\n\n\nclass TestBuildStandard:\n    def test_default(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"sdist\": {\"versions\": [\"standard\"]}}},\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_default\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.project_id / \"PKG-INFO\"))\n        assert stat.st_mtime == get_reproducible_timestamp()\n\n    def test_default_no_reproducible(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"sdist\": {\"versions\": [\"standard\"], \"reproducible\": False}}},\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_default\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.project_id / \"PKG-INFO\"))\n        assert stat.st_mtime != get_reproducible_timestamp()\n\n    def test_default_support_legacy(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"sdist\": {\"versions\": [\"standard\"], \"support-legacy\": True}}},\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_default_support_legacy\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_artifacts(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\\n\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        pathlib.Path('my_app', 'lib.so').touch()\n                        pathlib.Path('my_app', 'lib.h').touch()\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"sdist\": {\"versions\": [\"standard\"], \"exclude\": [DEFAULT_BUILD_SCRIPT, \".gitignore\"]}\n                        },\n                        \"artifacts\": [\"my_app/lib.so\"],\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_default_build_script_artifacts\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_extra_dependencies(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\\n\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        pathlib.Path('my_app', 'lib.so').touch()\n                        pathlib.Path('my_app', 'lib.h').touch()\n                        build_data['dependencies'].append('binary')\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"sdist\": {\"versions\": [\"standard\"], \"exclude\": [DEFAULT_BUILD_SCRIPT, \".gitignore\"]}\n                        },\n                        \"artifacts\": [\"my_app/lib.so\"],\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_default_build_script_extra_dependencies\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_include_project_file(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"], \"readme\": \"README.md\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"sdist\": {\"versions\": [\"standard\"], \"include\": [\"my_app/\", \"pyproject.toml\"]}}\n                    },\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_include\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.project_id / \"PKG-INFO\"))\n        assert stat.st_mtime == get_reproducible_timestamp()\n\n    def test_project_file_always_included(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"], \"readme\": \"README.md\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"sdist\": {\n                                \"versions\": [\"standard\"],\n                                \"only-include\": [\"my_app\"],\n                                \"exclude\": [\"pyproject.toml\"],\n                            },\n                        },\n                    },\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        # Ensure that only the root project file is forcibly included\n        (project_path / \"my_app\" / \"pyproject.toml\").touch()\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_include\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.project_id / \"PKG-INFO\"))\n        assert stat.st_mtime == get_reproducible_timestamp()\n\n    def test_config_file_always_included(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"], \"readme\": \"README.md\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"sdist\": {\n                                \"versions\": [\"standard\"],\n                                \"only-include\": [\"my_app\"],\n                                \"exclude\": [DEFAULT_CONFIG_FILE],\n                            },\n                        },\n                    },\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        (project_path / DEFAULT_CONFIG_FILE).touch()\n\n        # Ensure that only the root config file is forcibly included\n        (project_path / \"my_app\" / DEFAULT_CONFIG_FILE).touch()\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_include_config_file\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.project_id / \"PKG-INFO\"))\n        assert stat.st_mtime == get_reproducible_timestamp()\n\n    def test_include_readme(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"], \"readme\": \"README.md\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"sdist\": {\"versions\": [\"standard\"], \"include\": [\"my_app/\", \"README.md\"]}}},\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_include\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.project_id / \"PKG-INFO\"))\n        assert stat.st_mtime == get_reproducible_timestamp()\n\n    def test_readme_always_included(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"], \"readme\": \"README.md\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"sdist\": {\"versions\": [\"standard\"], \"only-include\": [\"my_app\"], \"exclude\": [\"README.md\"]},\n                        },\n                    },\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        # Ensure that only the desired readme is forcibly included\n        (project_path / \"my_app\" / \"README.md\").touch()\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_include\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.project_id / \"PKG-INFO\"))\n        assert stat.st_mtime == get_reproducible_timestamp()\n\n    def test_include_license_files(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"], \"readme\": \"README.md\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"sdist\": {\"versions\": [\"standard\"], \"include\": [\"my_app/\", \"LICENSE.txt\"]}}},\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_include\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.project_id / \"PKG-INFO\"))\n        assert stat.st_mtime == get_reproducible_timestamp()\n\n    def test_license_files_always_included(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"], \"readme\": \"README.md\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"sdist\": {\"versions\": [\"standard\"], \"only-include\": [\"my_app\"], \"exclude\": [\"LICENSE.txt\"]},\n                        },\n                    },\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        # Ensure that only the desired readme is forcibly included\n        (project_path / \"my_app\" / \"LICENSE.txt\").touch()\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_include\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.project_id / \"PKG-INFO\"))\n        assert stat.st_mtime == get_reproducible_timestamp()\n\n    def test_default_vcs_git_exclusion_files(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = temp_dir / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\\n\")\n\n        (project_path / \"my_app\" / \"lib.so\").touch()\n        (project_path / \"my_app\" / \"lib.h\").touch()\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"sdist\": {\"versions\": [\"standard\"], \"exclude\": [\".gitignore\"]}},\n                        \"artifacts\": [\"my_app/lib.so\"],\n                    },\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_default_vcs_git_exclusion_files\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_vcs_mercurial_exclusion_files(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = temp_dir / \".hgignore\"\n        vcs_ignore_file.write_text(\n            helpers.dedent(\n                \"\"\"\n                syntax: glob\n                *.pyc\n\n                syntax: foo\n                README.md\n\n                syntax: glob\n                *.so\n                *.h\n                \"\"\"\n            )\n        )\n\n        (project_path / \"my_app\" / \"lib.so\").touch()\n        (project_path / \"my_app\" / \"lib.h\").touch()\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"sdist\": {\"versions\": [\"standard\"], \"exclude\": [\".hgignore\"]}},\n                        \"artifacts\": [\"my_app/lib.so\"],\n                    },\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_default_vcs_mercurial_exclusion_files\", project_name, relative_root=builder.project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_no_strict_naming(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"sdist\": {\"versions\": [\"standard\"], \"strict-naming\": False}}},\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.artifact_project_id}.tar.gz\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with tarfile.open(str(expected_artifact), \"r:gz\") as tar_archive:\n            tar_archive.extractall(str(extraction_directory), **helpers.tarfile_extraction_compat_options())\n\n        expected_files = helpers.get_template_files(\n            \"sdist.standard_default\", project_name, relative_root=builder.artifact_project_id\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        stat = os.stat(str(extraction_directory / builder.artifact_project_id / \"PKG-INFO\"))\n        assert stat.st_mtime == get_reproducible_timestamp()\n\n    def test_file_permissions_normalized(self, hatch, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"sdist\": {\"versions\": [\"standard\"]}}},\n                },\n            },\n        }\n        builder = SdistBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.artifact_project_id}.tar.gz\")\n\n        file_stat = os.stat(expected_artifact)\n        # we assert that at minimum 644 is set, based on the platform (e.g.)\n        # windows it may be higher\n        assert file_stat.st_mode & 0o644\n"
  },
  {
    "path": "tests/backend/builders/test_wheel.py",
    "content": "from __future__ import annotations\n\nimport os\nimport platform\nimport sys\nimport zipfile\nfrom typing import TYPE_CHECKING\n\nimport packaging.tags\nimport pytest\n\nfrom hatchling.builders.plugin.interface import BuilderInterface\nfrom hatchling.builders.utils import get_known_python_major_versions\nfrom hatchling.builders.wheel import WheelBuilder\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION, get_core_metadata_constructors\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\nif TYPE_CHECKING:\n    from hatch.utils.fs import Path\n\n\ndef sys_tags():\n    return iter(\n        t for t in packaging.tags.sys_tags() if \"manylinux\" not in t.platform and \"muslllinux\" not in t.platform\n    )\n\n\n# https://github.com/python/cpython/pull/26184\nfixed_pathlib_resolution = pytest.mark.skipif(\n    sys.platform == \"win32\" and (sys.version_info < (3, 8) or sys.implementation.name == \"pypy\"),\n    reason=\"pathlib.Path.resolve has bug on Windows\",\n)\n\n\ndef get_python_versions_tag():\n    return \".\".join(f\"py{major_version}\" for major_version in get_known_python_major_versions())\n\n\ndef extract_zip(zip_path: Path, target: Path) -> None:\n    with zipfile.ZipFile(zip_path, \"r\") as z:\n        for name in z.namelist():\n            member = z.getinfo(name)\n            path = z.extract(member, target)\n            os.chmod(path, member.external_attr >> 16)\n\n\ndef test_class():\n    assert issubclass(WheelBuilder, BuilderInterface)\n\n\ndef test_default_versions(isolation):\n    builder = WheelBuilder(str(isolation))\n\n    assert builder.get_default_versions() == [\"standard\"]\n\n\nclass TestDefaultFileSelection:\n    def test_already_defined(self, temp_dir):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\n                                \"include\": [\"foo\"],\n                                \"exclude\": [\"bar\"],\n                                \"packages\": [\"foo\", \"bar\", \"baz\"],\n                                \"only-include\": [\"baz\"],\n                            }\n                        }\n                    }\n                }\n            },\n        }\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        assert builder.config.default_include() == [\"foo\"]\n        assert builder.config.default_exclude() == [\"bar\"]\n        assert builder.config.default_packages() == [\"foo\", \"bar\", \"baz\"]\n        assert builder.config.default_only_include() == [\"baz\"]\n\n    def test_flat_layout(self, temp_dir):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"exclude\": [\"foobarbaz\"]}}}}},\n        }\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        flat_root = temp_dir / \"my_app\" / \"__init__.py\"\n        flat_root.ensure_parent_dir_exists()\n        flat_root.touch()\n\n        src_root = temp_dir / \"src\" / \"my_app\" / \"__init__.py\"\n        src_root.ensure_parent_dir_exists()\n        src_root.touch()\n\n        single_module_root = temp_dir / \"my_app.py\"\n        single_module_root.touch()\n\n        namespace_root = temp_dir / \"ns\" / \"my_app\" / \"__init__.py\"\n        namespace_root.ensure_parent_dir_exists()\n        namespace_root.touch()\n\n        assert builder.config.default_include() == []\n        assert builder.config.default_exclude() == [\"foobarbaz\"]\n        assert builder.config.default_packages() == [\"my_app\"]\n        assert builder.config.default_only_include() == []\n\n    def test_src_layout(self, temp_dir):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"exclude\": [\"foobarbaz\"]}}}}},\n        }\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        src_root = temp_dir / \"src\" / \"my_app\" / \"__init__.py\"\n        src_root.ensure_parent_dir_exists()\n        src_root.touch()\n\n        single_module_root = temp_dir / \"my_app.py\"\n        single_module_root.touch()\n\n        namespace_root = temp_dir / \"ns\" / \"my_app\" / \"__init__.py\"\n        namespace_root.ensure_parent_dir_exists()\n        namespace_root.touch()\n\n        assert builder.config.default_include() == []\n        assert builder.config.default_exclude() == [\"foobarbaz\"]\n        assert builder.config.default_packages() == [\"src/my_app\"]\n        assert builder.config.default_only_include() == []\n\n    def test_single_module(self, temp_dir):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"exclude\": [\"foobarbaz\"]}}}}},\n        }\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        single_module_root = temp_dir / \"my_app.py\"\n        single_module_root.touch()\n\n        namespace_root = temp_dir / \"ns\" / \"my_app\" / \"__init__.py\"\n        namespace_root.ensure_parent_dir_exists()\n        namespace_root.touch()\n\n        assert builder.config.default_include() == []\n        assert builder.config.default_exclude() == [\"foobarbaz\"]\n        assert builder.config.default_packages() == []\n        assert builder.config.default_only_include() == [\"my_app.py\"]\n\n    def test_namespace(self, temp_dir):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"exclude\": [\"foobarbaz\"]}}}}},\n        }\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        namespace_root = temp_dir / \"ns\" / \"my_app\" / \"__init__.py\"\n        namespace_root.ensure_parent_dir_exists()\n        namespace_root.touch()\n\n        assert builder.config.default_include() == []\n        assert builder.config.default_exclude() == [\"foobarbaz\"]\n        assert builder.config.default_packages() == [\"ns\"]\n        assert builder.config.default_only_include() == []\n\n    def test_default_error(self, temp_dir):\n        config = {\n            \"project\": {\"name\": \"MyApp\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"exclude\": [\"foobarbaz\"]}}}}},\n        }\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        for method in (\n            builder.config.default_include,\n            builder.config.default_exclude,\n            builder.config.default_packages,\n            builder.config.default_only_include,\n        ):\n            with pytest.raises(\n                ValueError,\n                match=(\n                    \"Unable to determine which files to ship inside the wheel using the following heuristics: \"\n                    \"https://hatch.pypa.io/latest/plugins/builder/wheel/#default-file-selection\\n\\n\"\n                    \"The most likely cause of this is that there is no directory that matches the name of your \"\n                    \"project \\\\(MyApp or myapp\\\\).\\n\\n\"\n                    \"At least one file selection option must be defined in the `tool.hatch.build.targets.wheel` \"\n                    \"table, see: https://hatch.pypa.io/latest/config/build/\\n\\n\"\n                    \"As an example, if you intend to ship a directory named `foo` that resides within a `src` \"\n                    \"directory located at the root of your project, you can define the following:\\n\\n\"\n                    \"\\\\[tool.hatch.build.targets.wheel\\\\]\\n\"\n                    'packages = \\\\[\"src/foo\"\\\\]'\n                ),\n            ):\n                method()\n\n    def test_bypass_selection_option(self, temp_dir):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"bypass-selection\": True}}}}},\n        }\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        assert builder.config.default_include() == []\n        assert builder.config.default_exclude() == []\n        assert builder.config.default_packages() == []\n        assert builder.config.default_only_include() == []\n\n    def test_force_include_option_considered_selection(self, temp_dir):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"force-include\": {\"foo\": \"bar\"}}}}}},\n        }\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        assert builder.config.default_include() == []\n        assert builder.config.default_exclude() == []\n        assert builder.config.default_packages() == []\n        assert builder.config.default_only_include() == []\n\n    def test_force_include_build_data_considered_selection(self, temp_dir):\n        config = {\"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"}}\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        build_data = {\"artifacts\": [], \"force_include\": {\"foo\": \"bar\"}}\n        with builder.config.set_build_data(build_data):\n            assert builder.config.default_include() == []\n            assert builder.config.default_exclude() == []\n            assert builder.config.default_packages() == []\n            assert builder.config.default_only_include() == []\n\n    def test_artifacts_build_data_considered_selection(self, temp_dir):\n        config = {\"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"}}\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        build_data = {\"artifacts\": [\"foo\"], \"force_include\": {}}\n        with builder.config.set_build_data(build_data):\n            assert builder.config.default_include() == []\n            assert builder.config.default_exclude() == []\n            assert builder.config.default_packages() == []\n            assert builder.config.default_only_include() == []\n\n    def test_unnormalized_name_with_unnormalized_directory(self, temp_dir):\n        config = {\"project\": {\"name\": \"MyApp\", \"version\": \"0.0.1\"}}\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        src_root = temp_dir / \"src\" / \"MyApp\" / \"__init__.py\"\n        src_root.ensure_parent_dir_exists()\n        src_root.touch()\n\n        assert builder.config.default_packages() == [\"src/MyApp\"]\n\n    def test_unnormalized_name_with_normalized_directory(self, temp_dir):\n        config = {\"project\": {\"name\": \"MyApp\", \"version\": \"0.0.1\"}}\n        builder = WheelBuilder(str(temp_dir), config=config)\n\n        src_root = temp_dir / \"src\" / \"myapp\" / \"__init__.py\"\n        src_root.ensure_parent_dir_exists()\n        src_root.touch()\n\n        assert builder.config.default_packages() == [\"src/myapp\"]\n\n\nclass TestCoreMetadataConstructor:\n    def test_default(self, isolation):\n        builder = WheelBuilder(str(isolation))\n\n        assert builder.config.core_metadata_constructor is builder.config.core_metadata_constructor\n        assert builder.config.core_metadata_constructor is get_core_metadata_constructors()[DEFAULT_METADATA_VERSION]\n\n    def test_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"core-metadata-version\": 42}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError, match=\"Field `tool.hatch.build.targets.wheel.core-metadata-version` must be a string\"\n        ):\n            _ = builder.config.core_metadata_constructor\n\n    def test_unknown(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"core-metadata-version\": \"9000\"}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                f\"Unknown metadata version `9000` for field `tool.hatch.build.targets.wheel.core-metadata-version`. \"\n                f\"Available: {', '.join(sorted(get_core_metadata_constructors()))}\"\n            ),\n        ):\n            _ = builder.config.core_metadata_constructor\n\n\nclass TestSharedData:\n    def test_default(self, isolation):\n        builder = WheelBuilder(str(isolation))\n\n        assert builder.config.shared_data == builder.config.shared_data == {}\n\n    def test_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-data\": 42}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.wheel.shared-data` must be a mapping\"):\n            _ = builder.config.shared_data\n\n    def test_absolute(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-data\": {str(isolation / \"source\"): \"/target/\"}}}}}\n            }\n        }\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.config.shared_data == {str(isolation / \"source\"): \"target\"}\n\n    def test_relative(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-data\": {\"../source\": \"/target/\"}}}}}}}\n        builder = WheelBuilder(str(isolation / \"foo\"), config=config)\n\n        assert builder.config.shared_data == {str(isolation / \"source\"): \"target\"}\n\n    def test_source_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-data\": {\"\": \"/target/\"}}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=\"Source #1 in field `tool.hatch.build.targets.wheel.shared-data` cannot be an empty string\",\n        ):\n            _ = builder.config.shared_data\n\n    def test_relative_path_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-data\": {\"source\": 0}}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError,\n            match=\"Path for source `source` in field `tool.hatch.build.targets.wheel.shared-data` must be a string\",\n        ):\n            _ = builder.config.shared_data\n\n    def test_relative_path_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-data\": {\"source\": \"\"}}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Path for source `source` in field `tool.hatch.build.targets.wheel.shared-data` \"\n                \"cannot be an empty string\"\n            ),\n        ):\n            _ = builder.config.shared_data\n\n    def test_order(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\n                                \"shared-data\": {\n                                    \"../very-nested\": \"target1/embedded\",\n                                    \"../source1\": \"/target2/\",\n                                    \"../source2\": \"/target1/\",\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        builder = WheelBuilder(str(isolation / \"foo\"), config=config)\n\n        assert builder.config.shared_data == {\n            str(isolation / \"source2\"): \"target1\",\n            str(isolation / \"very-nested\"): f\"target1{os.sep}embedded\",\n            str(isolation / \"source1\"): \"target2\",\n        }\n\n\nclass TestSharedScripts:\n    def test_default(self, isolation):\n        builder = WheelBuilder(str(isolation))\n\n        assert builder.config.shared_scripts == builder.config.shared_scripts == {}\n\n    def test_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-scripts\": 42}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.wheel.shared-scripts` must be a mapping\"):\n            _ = builder.config.shared_scripts\n\n    def test_absolute(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-scripts\": {str(isolation / \"source\"): \"/target/\"}}}}}\n            }\n        }\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.config.shared_scripts == {str(isolation / \"source\"): \"target\"}\n\n    def test_relative(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-scripts\": {\"../source\": \"/target/\"}}}}}}}\n        builder = WheelBuilder(str(isolation / \"foo\"), config=config)\n\n        assert builder.config.shared_scripts == {str(isolation / \"source\"): \"target\"}\n\n    def test_source_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-scripts\": {\"\": \"/target/\"}}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=\"Source #1 in field `tool.hatch.build.targets.wheel.shared-scripts` cannot be an empty string\",\n        ):\n            _ = builder.config.shared_scripts\n\n    def test_relative_path_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-scripts\": {\"source\": 0}}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError,\n            match=\"Path for source `source` in field `tool.hatch.build.targets.wheel.shared-scripts` must be a string\",\n        ):\n            _ = builder.config.shared_scripts\n\n    def test_relative_path_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"shared-scripts\": {\"source\": \"\"}}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Path for source `source` in field `tool.hatch.build.targets.wheel.shared-scripts` \"\n                \"cannot be an empty string\"\n            ),\n        ):\n            _ = builder.config.shared_scripts\n\n    def test_order(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\n                                \"shared-scripts\": {\n                                    \"../very-nested\": \"target1/embedded\",\n                                    \"../source1\": \"/target2/\",\n                                    \"../source2\": \"/target1/\",\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        builder = WheelBuilder(str(isolation / \"foo\"), config=config)\n\n        assert builder.config.shared_scripts == {\n            str(isolation / \"source2\"): \"target1\",\n            str(isolation / \"very-nested\"): f\"target1{os.sep}embedded\",\n            str(isolation / \"source1\"): \"target2\",\n        }\n\n\nclass TestExtraMetadata:\n    def test_default(self, isolation):\n        builder = WheelBuilder(str(isolation))\n\n        assert builder.config.extra_metadata == builder.config.extra_metadata == {}\n\n    def test_invalid_type(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"extra-metadata\": 42}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.wheel.extra-metadata` must be a mapping\"):\n            _ = builder.config.extra_metadata\n\n    def test_absolute(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"extra-metadata\": {str(isolation / \"source\"): \"/target/\"}}}}}\n            }\n        }\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.config.extra_metadata == {str(isolation / \"source\"): \"target\"}\n\n    def test_relative(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"extra-metadata\": {\"../source\": \"/target/\"}}}}}}}\n        builder = WheelBuilder(str(isolation / \"foo\"), config=config)\n\n        assert builder.config.extra_metadata == {str(isolation / \"source\"): \"target\"}\n\n    def test_source_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"extra-metadata\": {\"\": \"/target/\"}}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=\"Source #1 in field `tool.hatch.build.targets.wheel.extra-metadata` cannot be an empty string\",\n        ):\n            _ = builder.config.extra_metadata\n\n    def test_relative_path_not_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"extra-metadata\": {\"source\": 0}}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError,\n            match=\"Path for source `source` in field `tool.hatch.build.targets.wheel.extra-metadata` must be a string\",\n        ):\n            _ = builder.config.extra_metadata\n\n    def test_relative_path_empty_string(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"extra-metadata\": {\"source\": \"\"}}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Path for source `source` in field `tool.hatch.build.targets.wheel.extra-metadata` \"\n                \"cannot be an empty string\"\n            ),\n        ):\n            _ = builder.config.extra_metadata\n\n    def test_order(self, isolation):\n        config = {\n            \"tool\": {\n                \"hatch\": {\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\n                                \"extra-metadata\": {\n                                    \"../very-nested\": \"target1/embedded\",\n                                    \"../source1\": \"/target2/\",\n                                    \"../source2\": \"/target1/\",\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        builder = WheelBuilder(str(isolation / \"foo\"), config=config)\n\n        assert builder.config.extra_metadata == {\n            str(isolation / \"source2\"): \"target1\",\n            str(isolation / \"very-nested\"): f\"target1{os.sep}embedded\",\n            str(isolation / \"source1\"): \"target2\",\n        }\n\n\nclass TestStrictNaming:\n    def test_default(self, isolation):\n        builder = WheelBuilder(str(isolation))\n\n        assert builder.config.strict_naming is builder.config.strict_naming is True\n\n    def test_target(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"strict-naming\": False}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.config.strict_naming is False\n\n    def test_target_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"strict-naming\": 9000}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.wheel.strict-naming` must be a boolean\"):\n            _ = builder.config.strict_naming\n\n    def test_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"strict-naming\": False}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.config.strict_naming is False\n\n    def test_global_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"strict-naming\": 9000}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.strict-naming` must be a boolean\"):\n            _ = builder.config.strict_naming\n\n    def test_target_overrides_global(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"strict-naming\": False, \"targets\": {\"wheel\": {\"strict-naming\": True}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.config.strict_naming is True\n\n\nclass TestMacOSMaxCompat:\n    def test_default(self, isolation):\n        builder = WheelBuilder(str(isolation))\n\n        assert builder.config.macos_max_compat is builder.config.macos_max_compat is False\n\n    def test_correct(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"macos-max-compat\": True}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.config.macos_max_compat is True\n\n    def test_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"macos-max-compat\": 9000}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError, match=\"Field `tool.hatch.build.targets.wheel.macos-max-compat` must be a boolean\"\n        ):\n            _ = builder.config.macos_max_compat\n\n\nclass TestBypassSelection:\n    def test_default(self, isolation):\n        builder = WheelBuilder(str(isolation))\n\n        assert builder.config.bypass_selection is False\n\n    def test_correct(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"bypass-selection\": True}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.config.bypass_selection is True\n\n    def test_not_boolean(self, isolation):\n        config = {\"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"bypass-selection\": 9000}}}}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError, match=\"Field `tool.hatch.build.targets.wheel.bypass-selection` must be a boolean\"\n        ):\n            _ = builder.config.bypass_selection\n\n\nclass TestConstructEntryPointsFile:\n    def test_default(self, isolation):\n        config = {\"project\": {}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.construct_entry_points_file() == \"\"\n\n    def test_scripts(self, isolation, helpers):\n        config = {\"project\": {\"scripts\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.construct_entry_points_file() == helpers.dedent(\n            \"\"\"\n            [console_scripts]\n            bar = pkg:foo\n            foo = pkg:bar\n            \"\"\"\n        )\n\n    def test_gui_scripts(self, isolation, helpers):\n        config = {\"project\": {\"gui-scripts\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"}}}\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.construct_entry_points_file() == helpers.dedent(\n            \"\"\"\n            [gui_scripts]\n            bar = pkg:foo\n            foo = pkg:bar\n            \"\"\"\n        )\n\n    def test_entry_points(self, isolation, helpers):\n        config = {\n            \"project\": {\n                \"entry-points\": {\n                    \"foo\": {\"bar\": \"pkg:foo\", \"foo\": \"pkg:bar\"},\n                    \"bar\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"},\n                }\n            }\n        }\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.construct_entry_points_file() == helpers.dedent(\n            \"\"\"\n            [bar]\n            bar = pkg:foo\n            foo = pkg:bar\n\n            [foo]\n            bar = pkg:foo\n            foo = pkg:bar\n            \"\"\"\n        )\n\n    def test_all(self, isolation, helpers):\n        config = {\n            \"project\": {\n                \"scripts\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"},\n                \"gui-scripts\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"},\n                \"entry-points\": {\n                    \"foo\": {\"bar\": \"pkg:foo\", \"foo\": \"pkg:bar\"},\n                    \"bar\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"},\n                },\n            }\n        }\n        builder = WheelBuilder(str(isolation), config=config)\n\n        assert builder.construct_entry_points_file() == helpers.dedent(\n            \"\"\"\n            [console_scripts]\n            bar = pkg:foo\n            foo = pkg:bar\n\n            [gui_scripts]\n            bar = pkg:foo\n            foo = pkg:bar\n\n            [bar]\n            bar = pkg:foo\n            foo = pkg:bar\n\n            [foo]\n            bar = pkg:foo\n            foo = pkg:bar\n            \"\"\"\n        )\n\n\nclass TestBuildStandard:\n    def test_default_auto_detection(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_license_single\", project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @pytest.mark.parametrize(\n        (\"epoch\", \"expected_date_time\"),\n        [\n            (\"0\", (1980, 1, 1, 0, 0, 0)),\n            (\"1580601700\", (2020, 2, 2, 0, 1, 40)),\n        ],\n    )\n    def test_default_reproducible_timestamp(self, hatch, helpers, temp_dir, config_file, epoch, expected_date_time):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd(env_vars={\"SOURCE_DATE_EPOCH\": epoch}):\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_license_single\", project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == expected_date_time\n\n    def test_default_no_reproducible(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"reproducible\": False}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd(env_vars={\"SOURCE_DATE_EPOCH\": \"1580601700\"}):\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_license_single\", project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    def test_default_multiple_licenses(self, hatch, helpers, config_file, temp_dir):\n        project_name = \"My.App\"\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.model.template.licenses.default = [\"MIT\", \"Apache-2.0\"]\n        config_file.save()\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        # Ensure that we trigger the non-file case for code coverage\n        (project_path / \"LICENSES\" / \"test\").mkdir()\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"], \"license-files\": [\"LICENSES/*\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_license_multiple\", project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_include(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"include\": [\"my_app\", \"tests\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_tests\", project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_only_packages(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        tests_path = project_path / \"tests\"\n        (tests_path / \"__init__.py\").replace(tests_path / \"foo.py\")\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\"versions\": [\"standard\"], \"include\": [\"my_app\", \"tests\"], \"only-packages\": True}\n                        },\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_license_single\", project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_only_packages_artifact_override(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        tests_path = project_path / \"tests\"\n        (tests_path / \"__init__.py\").replace(tests_path / \"foo.py\")\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"artifacts\": [\"foo.py\"],\n                        \"targets\": {\n                            \"wheel\": {\"versions\": [\"standard\"], \"include\": [\"my_app\", \"tests\"], \"only-packages\": True}\n                        },\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_only_packages_artifact_override\", project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    @pytest.mark.parametrize(\n        (\"python_constraint\", \"expected_template_file\"),\n        [\n            pytest.param(\">3\", \"wheel.standard_default_python_constraint\", id=\">3\"),\n            pytest.param(\"==3.11.4\", \"wheel.standard_default_python_constraint_three_components\", id=\"==3.11.4\"),\n        ],\n    )\n    def test_default_python_constraint(\n        self, hatch, helpers, temp_dir, config_file, python_constraint, expected_template_file\n    ):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": python_constraint, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-py3-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            expected_template_file, project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_default_tag(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    pass\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        tag = \"py3-none-any\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script\", project_name, metadata_directory=metadata_directory, tag=tag\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_set_tag(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['tag'] = 'foo-bar-baz'\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        tag = \"foo-bar-baz\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script\", project_name, metadata_directory=metadata_directory, tag=tag\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_known_artifacts(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n\n                        pathlib.Path('my_app', 'lib.so').touch()\n                        pathlib.Path('my_app', 'lib.h').touch()\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"artifacts\": [\"my_app/lib.so\"],\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        best_matching_tag = next(sys_tags())\n        tag = f\"{best_matching_tag.interpreter}-{best_matching_tag.abi}-{best_matching_tag.platform}\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script_artifacts\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_configured_build_hooks(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n\n                        pathlib.Path('my_app', 'lib.so').write_text(','.join(build_data['build_hooks']))\n                        pathlib.Path('my_app', 'lib.h').write_text(','.join(build_data['build_hooks']))\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"artifacts\": [\"my_app/lib.so\"],\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        best_matching_tag = next(sys_tags())\n        tag = f\"{best_matching_tag.interpreter}-{best_matching_tag.abi}-{best_matching_tag.platform}\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script_configured_build_hooks\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_extra_dependencies(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n                        build_data['dependencies'].append('binary')\n\n                        pathlib.Path('my_app', 'lib.so').touch()\n                        pathlib.Path('my_app', 'lib.h').touch()\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"artifacts\": [\"my_app/lib.so\"],\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        best_matching_tag = next(sys_tags())\n        tag = f\"{best_matching_tag.interpreter}-{best_matching_tag.abi}-{best_matching_tag.platform}\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script_extra_dependencies\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_dynamic_artifacts(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n                        build_data['artifacts'] = ['my_app/lib.so']\n\n                        pathlib.Path('my_app', 'lib.so').touch()\n                        pathlib.Path('my_app', 'lib.h').touch()\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        best_matching_tag = next(sys_tags())\n        tag = f\"{best_matching_tag.interpreter}-{best_matching_tag.abi}-{best_matching_tag.platform}\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script_artifacts\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_dynamic_force_include(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n                        build_data['artifacts'].extend(('lib.so', 'lib.h'))\n                        build_data['force_include']['../artifacts'] = 'my_app'\n\n                        artifact_path = pathlib.Path('..', 'artifacts')\n                        artifact_path.mkdir()\n                        (artifact_path / 'lib.so').touch()\n                        (artifact_path / 'lib.h').touch()\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        best_matching_tag = next(sys_tags())\n        tag = f\"{best_matching_tag.interpreter}-{best_matching_tag.abi}-{best_matching_tag.platform}\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script_force_include\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_dynamic_force_include_duplicate(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\")\n\n        target_file = project_path / \"my_app\" / \"z.py\"\n        target_file.write_text('print(\"hello world\")')\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n                        build_data['force_include']['../tmp/new_z.py'] = 'my_app/z.py'\n\n                        tmp_path = pathlib.Path('..', 'tmp')\n                        tmp_path.mkdir()\n                        (tmp_path / 'new_z.py').write_bytes(pathlib.Path('my_app/z.py').read_bytes())\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        best_matching_tag = next(sys_tags())\n        tag = f\"{best_matching_tag.interpreter}-{best_matching_tag.abi}-{best_matching_tag.platform}\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script_force_include_no_duplication\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_build_script_dynamic_artifacts_with_src_layout(self, hatch, helpers, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.pyd\\n*.h\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n                        build_data['artifacts'] = ['src/my_app/lib.so']\n                        build_data['force_include']['src/zlib.pyd'] = 'src/zlib.pyd'\n\n                        pathlib.Path('src', 'my_app', 'lib.so').touch()\n                        pathlib.Path('src', 'lib.h').touch()\n                        pathlib.Path('src', 'zlib.pyd').touch()\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        best_matching_tag = next(sys_tags())\n        tag = f\"{best_matching_tag.interpreter}-{best_matching_tag.abi}-{best_matching_tag.platform}\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script_artifacts_with_src_layout\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_shared_data(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        shared_data_path = temp_dir / \"data\"\n        shared_data_path.ensure_dir_exists()\n        (shared_data_path / \"foo.txt\").touch()\n        nested_data_path = shared_data_path / \"nested\"\n        nested_data_path.ensure_dir_exists()\n        (nested_data_path / \"bar.txt\").touch()\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"shared-data\": {\"../data\": \"/\"}}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        shared_data_directory = f\"{builder.project_id}.data\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_shared_data\",\n            project_name,\n            metadata_directory=metadata_directory,\n            shared_data_directory=shared_data_directory,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_shared_data_from_build_data(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        shared_data_path = temp_dir / \"data\"\n        shared_data_path.ensure_dir_exists()\n        (shared_data_path / \"foo.txt\").touch()\n        nested_data_path = shared_data_path / \"nested\"\n        nested_data_path.ensure_dir_exists()\n        (nested_data_path / \"bar.txt\").touch()\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['shared_data']['../data'] = '/'\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"hooks\": {\"custom\": {}}}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        shared_data_directory = f\"{builder.project_id}.data\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_shared_data\",\n            project_name,\n            metadata_directory=metadata_directory,\n            shared_data_directory=shared_data_directory,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_shared_scripts(self, hatch, platform, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        shared_data_path = temp_dir / \"data\"\n        shared_data_path.ensure_dir_exists()\n\n        binary_contents = os.urandom(1024)\n        binary_file = shared_data_path / \"binary\"\n        binary_file.write_bytes(binary_contents)\n        if not platform.windows:\n            expected_mode = 0o755\n            binary_file.chmod(expected_mode)\n\n        (shared_data_path / \"other_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!/bin/sh arg1 arg2\n                echo \"Hello, World!\"\n                \"\"\"\n            )\n        )\n        (shared_data_path / \"python_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!/usr/bin/env python3.11 arg1 arg2\n                print(\"Hello, World!\")\n                \"\"\"\n            )\n        )\n        (shared_data_path / \"pythonw_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!/usr/bin/pythonw3.11 arg1 arg2\n                print(\"Hello, World!\")\n                \"\"\"\n            )\n        )\n        (shared_data_path / \"pypy_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!/usr/bin/env pypy\n                print(\"Hello, World!\")\n                \"\"\"\n            )\n        )\n        (shared_data_path / \"pypyw_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!pypyw3.11 arg1 arg2\n                print(\"Hello, World!\")\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"shared-scripts\": {\"../data\": \"/\"}}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        extraction_directory = temp_dir / \"_archive\"\n        extract_zip(expected_artifact, extraction_directory)\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        shared_data_directory = f\"{builder.project_id}.data\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_shared_scripts\",\n            project_name,\n            metadata_directory=metadata_directory,\n            shared_data_directory=shared_data_directory,\n            binary_contents=binary_contents,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        if not platform.windows:\n            extracted_binary = extraction_directory / shared_data_directory / \"scripts\" / \"binary\"\n            assert extracted_binary.stat().st_mode & 0o777 == expected_mode\n\n    def test_default_shared_scripts_from_build_data(self, hatch, platform, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        shared_data_path = temp_dir / \"data\"\n        shared_data_path.ensure_dir_exists()\n\n        binary_contents = os.urandom(1024)\n        binary_file = shared_data_path / \"binary\"\n        binary_file.write_bytes(binary_contents)\n        if not platform.windows:\n            expected_mode = 0o755\n            binary_file.chmod(expected_mode)\n\n        (shared_data_path / \"other_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!/bin/sh arg1 arg2\n                echo \"Hello, World!\"\n                \"\"\"\n            )\n        )\n        (shared_data_path / \"python_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!/usr/bin/env python3.11 arg1 arg2\n                print(\"Hello, World!\")\n                \"\"\"\n            )\n        )\n        (shared_data_path / \"pythonw_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!/usr/bin/pythonw3.11 arg1 arg2\n                print(\"Hello, World!\")\n                \"\"\"\n            )\n        )\n        (shared_data_path / \"pypy_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!/usr/bin/env pypy\n                print(\"Hello, World!\")\n                \"\"\"\n            )\n        )\n        (shared_data_path / \"pypyw_script.sh\").write_text(\n            helpers.dedent(\n                \"\"\"\n\n                #!pypyw3.11 arg1 arg2\n                print(\"Hello, World!\")\n                \"\"\"\n            )\n        )\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['shared_scripts']['../data'] = '/'\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"hooks\": {\"custom\": {}}}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        extraction_directory = temp_dir / \"_archive\"\n        extract_zip(expected_artifact, extraction_directory)\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        shared_data_directory = f\"{builder.project_id}.data\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_shared_scripts\",\n            project_name,\n            metadata_directory=metadata_directory,\n            shared_data_directory=shared_data_directory,\n            binary_contents=binary_contents,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        if not platform.windows:\n            extracted_binary = extraction_directory / shared_data_directory / \"scripts\" / \"binary\"\n            assert extracted_binary.stat().st_mode & 0o777 == expected_mode\n\n    def test_default_extra_metadata(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        extra_metadata_path = temp_dir / \"data\"\n        extra_metadata_path.ensure_dir_exists()\n        (extra_metadata_path / \"foo.txt\").touch()\n        nested_data_path = extra_metadata_path / \"nested\"\n        nested_data_path.ensure_dir_exists()\n        (nested_data_path / \"bar.txt\").touch()\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"extra-metadata\": {\"../data\": \"/\"}}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_extra_metadata\",\n            project_name,\n            metadata_directory=metadata_directory,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_extra_metadata_build_data(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        extra_metadata_path = temp_dir / \"data\"\n        extra_metadata_path.ensure_dir_exists()\n        (extra_metadata_path / \"foo.txt\").touch()\n        nested_data_path = extra_metadata_path / \"nested\"\n        nested_data_path.ensure_dir_exists()\n        (nested_data_path / \"bar.txt\").touch()\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['extra_metadata']['../data'] = '/'\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"hooks\": {\"custom\": {}}}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_extra_metadata\",\n            project_name,\n            metadata_directory=metadata_directory,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    @pytest.mark.requires_unix\n    def test_default_symlink(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\")\n\n        (temp_dir / \"foo.so\").write_bytes(b\"data\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import os\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n\n                        pathlib.Path('my_app', 'lib.so').symlink_to(os.path.abspath(os.path.join('..', 'foo.so')))\n                        pathlib.Path('my_app', 'lib.h').touch()\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"artifacts\": [\"my_app/lib.so\"],\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        best_matching_tag = next(sys_tags())\n        tag = f\"{best_matching_tag.interpreter}-{best_matching_tag.abi}-{best_matching_tag.platform}\"\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_symlink\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    @fixed_pathlib_resolution\n    def test_editable_default(self, hatch, helpers, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"editable\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_pth\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_paths=[str(project_path / \"src\")],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @fixed_pathlib_resolution\n    def test_editable_default_extra_dependencies(self, hatch, helpers, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['dependencies'].append('binary')\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"editable\"], \"hooks\": {\"custom\": {}}}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_pth_extra_dependencies\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_paths=[str(project_path / \"src\")],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @fixed_pathlib_resolution\n    def test_editable_default_force_include(self, hatch, helpers, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        # Prefix z just to satisfy our ordering test assertion\n                        build_data['force_include_editable']['src/my_app/__about__.py'] = 'zfoo.py'\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"editable\"], \"hooks\": {\"custom\": {}}}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_pth_force_include\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_paths=[str(project_path / \"src\")],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @fixed_pathlib_resolution\n    def test_editable_default_force_include_option(self, hatch, helpers, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\n                                \"versions\": [\"editable\"],\n                                \"force-include\": {\"src/my_app/__about__.py\": \"zfoo.py\"},\n                            }\n                        }\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_pth_force_include\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_paths=[str(project_path / \"src\")],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @pytest.mark.requires_unix\n    def test_editable_default_symlink(self, hatch, helpers, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        symlink = project_path / \"_\" / \"my_app\"\n        symlink.parent.ensure_dir_exists()\n        symlink.symlink_to(project_path / \"src\" / \"my_app\")\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"editable\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_pth\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_paths=[str(project_path / \"src\")],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @fixed_pathlib_resolution\n    def test_editable_exact(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"editable\"], \"dev-mode-exact\": True}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_exact\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_root=str(project_path / \"my_app\" / \"__init__.py\"),\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @fixed_pathlib_resolution\n    def test_editable_exact_extra_dependencies(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['dependencies'].append('binary')\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\"versions\": [\"editable\"], \"dev-mode-exact\": True, \"hooks\": {\"custom\": {}}}\n                        }\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_exact_extra_dependencies\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_root=str(project_path / \"my_app\" / \"__init__.py\"),\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @fixed_pathlib_resolution\n    def test_editable_exact_force_include(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        # Prefix z just to satisfy our ordering test assertion\n                        build_data['force_include_editable']['my_app/__about__.py'] = 'zfoo.py'\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\"versions\": [\"editable\"], \"dev-mode-exact\": True, \"hooks\": {\"custom\": {}}}\n                        }\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_exact_force_include\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_root=str(project_path / \"my_app\" / \"__init__.py\"),\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @fixed_pathlib_resolution\n    def test_editable_exact_force_include_option(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\n                                \"versions\": [\"editable\"],\n                                \"dev-mode-exact\": True,\n                                \"force-include\": {\"my_app/__about__.py\": \"zfoo.py\"},\n                            }\n                        }\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_exact_force_include\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_root=str(project_path / \"my_app\" / \"__init__.py\"),\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @fixed_pathlib_resolution\n    def test_editable_exact_force_include_build_data_precedence(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        # Prefix z just to satisfy our ordering test assertion\n                        build_data['force_include_editable']['my_app/__about__.py'] = 'zfoo.py'\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\n                                \"versions\": [\"editable\"],\n                                \"dev-mode-exact\": True,\n                                \"force-include\": {\"my_app/__about__.py\": \"zbar.py\"},\n                                \"hooks\": {\"custom\": {}},\n                            }\n                        }\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_exact_force_include\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_root=str(project_path / \"my_app\" / \"__init__.py\"),\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    @fixed_pathlib_resolution\n    def test_editable_pth(self, hatch, helpers, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"editable\"], \"dev-mode-dirs\": [\".\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_editable_pth\",\n            project_name,\n            metadata_directory=metadata_directory,\n            package_paths=[str(project_path)],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n        # Inspect the archive rather than the extracted files because on Windows they lose their metadata\n        # https://stackoverflow.com/q/9813243\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_info = zip_archive.getinfo(f\"{metadata_directory}/WHEEL\")\n            assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)\n\n    def test_default_namespace_package(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        package_path = project_path / \"my_app\"\n        namespace_path = project_path / \"namespace\"\n        namespace_path.mkdir()\n        package_path.replace(namespace_path / \"my_app\")\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"namespace/my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_namespace_package\",\n            project_name,\n            metadata_directory=metadata_directory,\n            namespace=\"namespace\",\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_default_entry_points(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"], \"scripts\": {\"foo\": \"pkg:bar\", \"bar\": \"pkg:foo\"}},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"]}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{get_python_versions_tag()}-none-any.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_entry_points\", project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_explicit_selection_with_src_layout(self, hatch, helpers, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\n                                \"versions\": [\"standard\"],\n                                \"artifacts\": [\"README.md\"],\n                                \"only-include\": [\"src/my_app\"],\n                                \"sources\": [\"src\"],\n                            }\n                        },\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_license_single\",\n            project_name,\n            metadata_directory=metadata_directory,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_single_module(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        (project_path / \"my_app\").remove()\n        (project_path / \"my_app.py\").touch()\n\n        config = {\"project\": {\"name\": project_name, \"version\": \"0.0.1\"}}\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_single_module\",\n            project_name,\n            metadata_directory=metadata_directory,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_no_strict_naming(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"strict-naming\": False}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(\n            build_path / f\"{builder.artifact_project_id}-{get_python_versions_tag()}-none-any.whl\"\n        )\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.artifact_project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_no_strict_naming\", project_name, metadata_directory=metadata_directory\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_editable_sources_rewrite_error(self, hatch, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\n                            \"wheel\": {\n                                \"versions\": [\"editable\"],\n                                \"only-include\": [\"src/my_app\"],\n                                \"sources\": {\"src/my_app\": \"namespace/plugins/my_app\"},\n                            }\n                        },\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with (\n            project_path.as_cwd(),\n            pytest.raises(\n                ValueError,\n                match=(\n                    \"Dev mode installations are unsupported when any path rewrite in the `sources` option \"\n                    \"changes a prefix rather than removes it, see: \"\n                    \"https://github.com/pfmoore/editables/issues/20\"\n                ),\n            ),\n        ):\n            list(builder.build(directory=str(build_path)))\n\n    @pytest.mark.skipif(\n        sys.platform != \"darwin\" or sys.version_info < (3, 8),\n        reason=\"requires support for ARM on macOS\",\n    )\n    @pytest.mark.parametrize(\n        (\"archflags\", \"expected_arch\"),\n        [(\"-arch x86_64\", \"x86_64\"), (\"-arch arm64\", \"arm64\"), (\"-arch arm64 -arch x86_64\", \"universal2\")],\n    )\n    def test_macos_archflags(self, hatch, helpers, temp_dir, config_file, archflags, expected_arch):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n\n                        pathlib.Path('my_app', 'lib.so').touch()\n                        pathlib.Path('my_app', 'lib.h').touch()\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"]}},\n                        \"artifacts\": [\"my_app/lib.so\"],\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd({\"ARCHFLAGS\": archflags}):\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        tag = next(sys_tags())\n        tag_parts = [tag.interpreter, tag.abi, tag.platform]\n        tag_parts[2] = tag_parts[2].replace(platform.mac_ver()[2], expected_arch)\n        expected_tag = \"-\".join(tag_parts)\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{expected_tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script_artifacts\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=expected_tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    @pytest.mark.requires_macos\n    @pytest.mark.parametrize(\"macos_max_compat\", [True, False])\n    def test_macos_max_compat(self, hatch, helpers, temp_dir, config_file, macos_max_compat):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        vcs_ignore_file = project_path / \".gitignore\"\n        vcs_ignore_file.write_text(\"*.pyc\\n*.so\\n*.h\")\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data['pure_python'] = False\n                        build_data['infer_tag'] = True\n\n                        pathlib.Path('my_app', 'lib.so').touch()\n                        pathlib.Path('my_app', 'lib.h').touch()\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": project_name, \"requires-python\": \">3\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"macos-max-compat\": macos_max_compat}},\n                        \"artifacts\": [\"my_app/lib.so\"],\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n\n        tag = next(sys_tags())\n        tag_parts = [tag.interpreter, tag.abi, tag.platform]\n        if macos_max_compat:\n            sdk_version_major, sdk_version_minor = tag_parts[2].split(\"_\")[1:3]\n            if int(sdk_version_major) >= 11:\n                tag_parts[2] = tag_parts[2].replace(f\"{sdk_version_major}_{sdk_version_minor}\", \"10_16\", 1)\n\n        expected_tag = \"-\".join(tag_parts)\n        assert expected_artifact == str(build_path / f\"{builder.project_id}-{expected_tag}.whl\")\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(expected_artifact), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_build_script_artifacts\",\n            project_name,\n            metadata_directory=metadata_directory,\n            tag=expected_tag,\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_file_permissions_normalized(self, hatch, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n\n        config = {\n            \"project\": {\"name\": project_name, \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"], \"strict-naming\": False}}},\n                },\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build())\n\n        assert len(artifacts) == 1\n        expected_artifact = artifacts[0]\n\n        build_artifacts = list(build_path.iterdir())\n        assert len(build_artifacts) == 1\n        assert expected_artifact == str(build_artifacts[0])\n        assert expected_artifact == str(\n            build_path / f\"{builder.artifact_project_id}-{get_python_versions_tag()}-none-any.whl\"\n        )\n        file_stat = os.stat(expected_artifact)\n        # we assert that at minimum 644 is set, based on the platform (e.g.)\n        # windows it may be higher\n        assert file_stat.st_mode & 0o644\n\n\nclass TestSBOMFiles:\n    def test_single_sbom_file(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", \"My.App\")\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        sbom_file = project_path / \"my-sbom.spdx.json\"\n        sbom_file.write_text('{\"spdxVersion\": \"SPDX-2.3\"}')\n\n        config = {\n            \"project\": {\"name\": \"My.App\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"sbom-files\": [\"my-sbom.spdx.json\"]}}},\n                }\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(artifacts[0]), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_sbom\",\n            \"My.App\",\n            metadata_directory=metadata_directory,\n            sbom_files=[(\"my-sbom.spdx.json\", '{\"spdxVersion\": \"SPDX-2.3\"}')],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_multiple_sbom_files(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", \"My.App\")\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        (project_path / \"sbom1.spdx.json\").write_text('{\"spdxVersion\": \"SPDX-2.3\"}')\n        (project_path / \"sbom2.cyclonedx.json\").write_text('{\"bomFormat\": \"CycloneDX\"}')\n\n        config = {\n            \"project\": {\"name\": \"My.App\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"sbom-files\": [\"sbom1.spdx.json\", \"sbom2.cyclonedx.json\"]}}},\n                }\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(artifacts[0]), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_sbom\",\n            \"My.App\",\n            metadata_directory=metadata_directory,\n            sbom_files=[\n                (\"sbom1.spdx.json\", '{\"spdxVersion\": \"SPDX-2.3\"}'),\n                (\"sbom2.cyclonedx.json\", '{\"bomFormat\": \"CycloneDX\"}'),\n            ],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_nested_sbom_file(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", \"My.App\")\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        sbom_dir = project_path / \"sboms\"\n        sbom_dir.mkdir()\n        (sbom_dir / \"vendor.spdx.json\").write_text('{\"spdxVersion\": \"SPDX-2.3\"}')\n\n        config = {\n            \"project\": {\"name\": \"My.App\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\"targets\": {\"wheel\": {\"sbom-files\": [\"sboms/vendor.spdx.json\"]}}},\n                }\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(artifacts[0]), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_sbom\",\n            \"My.App\",\n            metadata_directory=metadata_directory,\n            sbom_files=[(\"vendor.spdx.json\", '{\"spdxVersion\": \"SPDX-2.3\"}')],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n\n    def test_sbom_files_invalid_type(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"sbom-files\": \"not-a-list\"}}}}},\n        }\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.wheel.sbom-files` must be an array\"):\n            _ = builder.config.sbom_files\n\n    def test_sbom_file_invalid_item(self, isolation):\n        config = {\n            \"project\": {\"name\": \"my-app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"build\": {\"targets\": {\"wheel\": {\"sbom-files\": [123]}}}}},\n        }\n        builder = WheelBuilder(str(isolation), config=config)\n\n        with pytest.raises(\n            TypeError, match=\"SBOM file #1 in field `tool.hatch.build.targets.wheel.sbom-files` must be a string\"\n        ):\n            _ = builder.config.sbom_files\n\n    def test_sbom_from_build_data(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", \"My.App\")\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        (project_path / \"sbom1.cyclonedx.json\").write_text('{\"bomFormat\": \"CycloneDX\"}')\n        (project_path / \"sbom2.spdx.json\").write_text('{\"spdxVersion\": \"SPDX-2.3\"}')\n\n        build_script = project_path / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                import pathlib\n\n                from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n                class CustomHook(BuildHookInterface):\n                    def initialize(self, version, build_data):\n                        build_data[\"sbom_files\"].append(\"sbom2.spdx.json\")\n                \"\"\"\n            )\n        )\n\n        config = {\n            \"project\": {\"name\": \"My.App\", \"dynamic\": [\"version\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"version\": {\"path\": \"src/my_app/__about__.py\"},\n                    \"build\": {\n                        \"targets\": {\"wheel\": {\"sbom-files\": [\"sbom1.cyclonedx.json\"]}},\n                        \"hooks\": {\"custom\": {\"path\": DEFAULT_BUILD_SCRIPT}},\n                    },\n                }\n            },\n        }\n        builder = WheelBuilder(str(project_path), config=config)\n\n        build_path = project_path / \"dist\"\n        build_path.mkdir()\n\n        with project_path.as_cwd():\n            artifacts = list(builder.build(directory=str(build_path)))\n\n        assert len(artifacts) == 1\n\n        extraction_directory = temp_dir / \"_archive\"\n        extraction_directory.mkdir()\n\n        with zipfile.ZipFile(str(artifacts[0]), \"r\") as zip_archive:\n            zip_archive.extractall(str(extraction_directory))\n\n        metadata_directory = f\"{builder.project_id}.dist-info\"\n        expected_files = helpers.get_template_files(\n            \"wheel.standard_default_sbom\",\n            \"My.App\",\n            metadata_directory=metadata_directory,\n            sbom_files=[\n                (\"sbom1.cyclonedx.json\", '{\"bomFormat\": \"CycloneDX\"}'),\n                (\"sbom2.spdx.json\", '{\"spdxVersion\": \"SPDX-2.3\"}'),\n            ],\n        )\n        helpers.assert_files(extraction_directory, expected_files)\n"
  },
  {
    "path": "tests/backend/builders/utils.py",
    "content": "from hatchling.builders.plugin.interface import BuilderInterface\n\n\nclass MockBuilder(BuilderInterface):  # no cov\n    def get_version_api(self):\n        return {}\n"
  },
  {
    "path": "tests/backend/metadata/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/metadata/test_build.py",
    "content": "import pytest\nfrom packaging.requirements import Requirement\n\nfrom hatchling.metadata.core import BuildMetadata\n\n\nclass TestRequires:\n    def test_default(self, isolation):\n        metadata = BuildMetadata(str(isolation), {})\n\n        assert metadata.requires == metadata.requires == []\n\n    def test_not_array(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"requires\": 10})\n\n        with pytest.raises(TypeError, match=\"Field `build-system.requires` must be an array\"):\n            _ = metadata.requires\n\n    def test_entry_not_string(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"requires\": [10]})\n\n        with pytest.raises(TypeError, match=\"Dependency #1 of field `build-system.requires` must be a string\"):\n            _ = metadata.requires\n\n    def test_invalid_specifier(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"requires\": [\"foo^1\"]})\n\n        with pytest.raises(ValueError, match=\"Dependency #1 of field `build-system.requires` is invalid: .+\"):\n            _ = metadata.requires\n\n    def test_correct(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"requires\": [\"foo\", \"bar\", \"Baz\"]})\n\n        assert metadata.requires == metadata.requires == [\"foo\", \"bar\", \"Baz\"]\n\n    def test_correct_complex_type(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"requires\": [\"foo\"]})\n\n        assert isinstance(metadata.requires_complex, list)\n        assert isinstance(metadata.requires_complex[0], Requirement)\n\n\nclass TestBuildBackend:\n    def test_default(self, isolation):\n        metadata = BuildMetadata(str(isolation), {})\n\n        assert metadata.build_backend == metadata.build_backend == \"\"\n\n    def test_not_string(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"build-backend\": 10})\n\n        with pytest.raises(TypeError, match=\"Field `build-system.build-backend` must be a string\"):\n            _ = metadata.build_backend\n\n    def test_correct(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"build-backend\": \"foo\"})\n\n        assert metadata.build_backend == metadata.build_backend == \"foo\"\n\n\nclass TestBackendPath:\n    def test_default(self, isolation):\n        metadata = BuildMetadata(str(isolation), {})\n\n        assert metadata.backend_path == metadata.backend_path == []\n\n    def test_not_array(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"backend-path\": 10})\n\n        with pytest.raises(TypeError, match=\"Field `build-system.backend-path` must be an array\"):\n            _ = metadata.backend_path\n\n    def test_entry_not_string(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"backend-path\": [10]})\n\n        with pytest.raises(TypeError, match=\"Entry #1 of field `build-system.backend-path` must be a string\"):\n            _ = metadata.backend_path\n\n    def test_correct(self, isolation):\n        metadata = BuildMetadata(str(isolation), {\"backend-path\": [\"foo\", \"bar\", \"Baz\"]})\n\n        assert metadata.backend_path == metadata.backend_path == [\"foo\", \"bar\", \"Baz\"]\n"
  },
  {
    "path": "tests/backend/metadata/test_core.py",
    "content": "import pytest\n\nfrom hatchling.metadata.core import BuildMetadata, CoreMetadata, HatchMetadata, ProjectMetadata\nfrom hatchling.metadata.spec import (\n    LATEST_METADATA_VERSION,\n    get_core_metadata_constructors,\n    project_metadata_from_core_metadata,\n)\nfrom hatchling.plugin.manager import PluginManager\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\nfrom hatchling.version.source.regex import RegexSource\n\n\n@pytest.fixture(scope=\"module\")\ndef latest_spec():\n    return get_core_metadata_constructors()[LATEST_METADATA_VERSION]\n\n\nclass TestConfig:\n    def test_default(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None)\n\n        assert metadata.config == metadata.config == {}\n\n    def test_reuse(self, isolation):\n        config = {}\n        metadata = ProjectMetadata(str(isolation), None, config)\n\n        assert metadata.config is metadata.config is config\n\n    def test_read(self, temp_dir):\n        project_file = temp_dir / \"pyproject.toml\"\n        project_file.write_text(\"foo = 5\")\n\n        with temp_dir.as_cwd():\n            metadata = ProjectMetadata(str(temp_dir), None)\n\n            assert metadata.config == metadata.config == {\"foo\": 5}\n\n\nclass TestInterface:\n    def test_types(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        assert isinstance(metadata.core, CoreMetadata)\n        assert isinstance(metadata.hatch, HatchMetadata)\n        assert isinstance(metadata.build, BuildMetadata)\n\n    def test_missing_core_metadata(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {})\n\n        with pytest.raises(ValueError, match=\"Missing `project` metadata table in configuration\"):\n            _ = metadata.core\n\n    def test_core_metadata_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": \"foo\"})\n\n        with pytest.raises(TypeError, match=\"The `project` configuration must be a table\"):\n            _ = metadata.core\n\n    def test_tool_metadata_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"tool\": \"foo\"})\n\n        with pytest.raises(TypeError, match=\"The `tool` configuration must be a table\"):\n            _ = metadata.hatch\n\n    def test_hatch_metadata_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"tool\": {\"hatch\": \"foo\"}})\n\n        with pytest.raises(TypeError, match=\"The `tool.hatch` configuration must be a table\"):\n            _ = metadata.hatch\n\n    def test_build_metadata_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"build-system\": \"foo\"})\n\n        with pytest.raises(TypeError, match=\"The `build-system` configuration must be a table\"):\n            _ = metadata.build\n\n\nclass TestDynamic:\n    def test_not_array(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"dynamic\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.dynamic` must be an array\"):\n            _ = metadata.core.dynamic\n\n    def test_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"dynamic\": [10]}})\n\n        with pytest.raises(TypeError, match=\"Field #1 of field `project.dynamic` must be a string\"):\n            _ = metadata.core.dynamic\n\n    def test_correct(self, isolation):\n        dynamic = [\"version\"]\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"dynamic\": dynamic}})\n\n        assert metadata.core.dynamic == [\"version\"]\n\n    def test_cache_not_array(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"dynamic\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.dynamic` must be an array\"):\n            _ = metadata.dynamic\n\n    def test_cache_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"dynamic\": [10]}})\n\n        with pytest.raises(TypeError, match=\"Field #1 of field `project.dynamic` must be a string\"):\n            _ = metadata.dynamic\n\n    def test_cache_correct(self, temp_dir, helpers):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"dynamic\": [\"version\", \"description\"]},\n                \"tool\": {\"hatch\": {\"version\": {\"path\": \"a/b\"}, \"metadata\": {\"hooks\": {\"custom\": {}}}}},\n            },\n        )\n\n        file_path = temp_dir / \"a\" / \"b\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text('__version__ = \"0.0.1\"')\n\n        file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n                class CustomHook(MetadataHookInterface):\n                    def update(self, metadata):\n                        metadata['description'] = metadata['name'] + 'bar'\n                \"\"\"\n            )\n        )\n\n        # Trigger hooks with `metadata.core` first\n        assert metadata.core.dynamic == []\n        assert metadata.dynamic == [\"version\", \"description\"]\n\n\nclass TestRawName:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": 9000, \"dynamic\": [\"name\"]}})\n\n        with pytest.raises(\n            ValueError, match=\"Static metadata field `name` cannot be present in field `project.dynamic`\"\n        ):\n            _ = metadata.core.raw_name\n\n    def test_missing(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        with pytest.raises(ValueError, match=\"Missing required field `project.name`\"):\n            _ = metadata.core.raw_name\n\n    def test_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": 9000}})\n\n        with pytest.raises(TypeError, match=\"Field `project.name` must be a string\"):\n            _ = metadata.core.raw_name\n\n    def test_invalid(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": \"my app\"}})\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Required field `project.name` must only contain ASCII letters/digits, underscores, \"\n                \"hyphens, and periods, and must begin and end with ASCII letters/digits.\"\n            ),\n        ):\n            _ = metadata.core.raw_name\n\n    def test_correct(self, isolation):\n        name = \"My.App\"\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": name}})\n\n        assert metadata.core.raw_name is metadata.core.raw_name is name\n\n\nclass TestName:\n    @pytest.mark.parametrize(\"name\", [\"My--App\", \"My__App\", \"My..App\"])\n    def test_normalization(self, isolation, name):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": name}})\n\n        assert metadata.core.name == metadata.core.name == \"my-app\"\n\n\nclass TestVersion:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"version\": 9000, \"dynamic\": [\"version\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=\"Metadata field `version` cannot be both statically defined and listed in field `project.dynamic`\",\n        ):\n            _ = metadata.core.version\n\n    def test_static_missing(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        with pytest.raises(\n            ValueError,\n            match=\"Field `project.version` can only be resolved dynamically if `version` is in field `project.dynamic`\",\n        ):\n            _ = metadata.version\n\n    def test_static_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"version\": 9000}})\n\n        with pytest.raises(TypeError, match=\"Field `project.version` must be a string\"):\n            _ = metadata.version\n\n    def test_static_invalid(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"version\": \"0..0\"}})\n\n        with pytest.raises(\n            ValueError,\n            match=\"Invalid version `0..0` from field `project.version`, see https://peps.python.org/pep-0440/\",\n        ):\n            _ = metadata.version\n\n    def test_static_normalization(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"version\": \"0.1.0.0-rc.1\"}})\n\n        assert metadata.version == metadata.version == \"0.1.0.0rc1\"\n        assert metadata.core.version == metadata.core.version == \"0.1.0.0-rc.1\"\n\n    def test_dynamic_missing(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"dynamic\": [\"version\"]}, \"tool\": {\"hatch\": {}}})\n\n        with pytest.raises(ValueError, match=\"Missing `tool.hatch.version` configuration\"):\n            _ = metadata.version\n\n    def test_dynamic_not_table(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"dynamic\": [\"version\"]}, \"tool\": {\"hatch\": {\"version\": \"1.0\"}}}\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.version` must be a table\"):\n            _ = metadata.version\n\n    def test_dynamic_source_empty(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"dynamic\": [\"version\"]}, \"tool\": {\"hatch\": {\"version\": {\"source\": \"\"}}}}\n        )\n\n        with pytest.raises(\n            ValueError, match=\"The `source` option under the `tool.hatch.version` table must not be empty if defined\"\n        ):\n            _ = metadata.version.cached\n\n    def test_dynamic_source_not_string(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"dynamic\": [\"version\"]}, \"tool\": {\"hatch\": {\"version\": {\"source\": 42}}}}\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.version.source` must be a string\"):\n            _ = metadata.version.cached\n\n    def test_dynamic_unknown_source(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            PluginManager(),\n            {\"project\": {\"dynamic\": [\"version\"]}, \"tool\": {\"hatch\": {\"version\": {\"source\": \"foo\"}}}},\n        )\n\n        with pytest.raises(ValueError, match=\"Unknown version source: foo\"):\n            _ = metadata.version.cached\n\n    def test_dynamic_source_regex(self, temp_dir):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\"project\": {\"dynamic\": [\"version\"]}, \"tool\": {\"hatch\": {\"version\": {\"source\": \"regex\", \"path\": \"a/b\"}}}},\n        )\n\n        file_path = temp_dir / \"a\" / \"b\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text('__version__ = \"0.0.1\"')\n\n        assert metadata.hatch.version.source is metadata.hatch.version.source\n        assert isinstance(metadata.hatch.version.source, RegexSource)\n        assert metadata.hatch.version.cached == metadata.hatch.version.cached == \"0.0.1\"\n\n    def test_dynamic_source_regex_invalid(self, temp_dir):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\"project\": {\"dynamic\": [\"version\"]}, \"tool\": {\"hatch\": {\"version\": {\"source\": \"regex\", \"path\": \"a/b\"}}}},\n        )\n\n        file_path = temp_dir / \"a\" / \"b\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text('__version__ = \"0..0\"')\n\n        with pytest.raises(\n            ValueError, match=\"Invalid version `0..0` from source `regex`, see https://peps.python.org/pep-0440/\"\n        ):\n            _ = metadata.version\n\n    def test_dynamic_error(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            PluginManager(),\n            {\"project\": {\"dynamic\": [\"version\"]}, \"tool\": {\"hatch\": {\"version\": {\"source\": \"regex\"}}}},\n        )\n\n        with pytest.raises(\n            ValueError, match=\"Error getting the version from source `regex`: option `path` must be specified\"\n        ):\n            _ = metadata.version.cached\n\n\nclass TestDescription:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"description\": 9000, \"dynamic\": [\"description\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Metadata field `description` cannot be both statically defined and listed in field `project.dynamic`\"\n            ),\n        ):\n            _ = metadata.core.description\n\n    def test_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"description\": 9000}})\n\n        with pytest.raises(TypeError, match=\"Field `project.description` must be a string\"):\n            _ = metadata.core.description\n\n    def test_default(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        assert metadata.core.description == metadata.core.description == \"\"\n\n    def test_custom(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"description\": \"foo\"}})\n\n        assert metadata.core.description == metadata.core.description == \"foo\"\n\n    def test_normaliza(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"description\": \"\\nfirst line.\\r\\nsecond line\"}})\n\n        assert metadata.core.description == metadata.core.description == \" first line. second line\"\n\n\nclass TestReadme:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"readme\": 9000, \"dynamic\": [\"readme\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=\"Metadata field `readme` cannot be both statically defined and listed in field `project.dynamic`\",\n        ):\n            _ = metadata.core.readme\n\n    @pytest.mark.parametrize(\"attribute\", [\"readme\", \"readme_content_type\"])\n    def test_unknown_type(self, isolation, attribute):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"readme\": 9000}})\n\n        with pytest.raises(TypeError, match=\"Field `project.readme` must be a string or a table\"):\n            _ = getattr(metadata.core, attribute)\n\n    def test_default(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        assert metadata.core.readme == metadata.core.readme == \"\"\n        assert metadata.core.readme_content_type == metadata.core.readme_content_type == \"text/markdown\"\n        assert metadata.core.readme_path == metadata.core.readme_path == \"\"\n\n    def test_string_path_unknown_content_type(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"readme\": \"foo\"}})\n\n        with pytest.raises(\n            TypeError, match=\"Unable to determine the content-type based on the extension of readme file: foo\"\n        ):\n            _ = metadata.core.readme\n\n    def test_string_path_nonexistent(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"readme\": \"foo/bar.md\"}})\n\n        with pytest.raises(OSError, match=\"Readme file does not exist: foo/bar\\\\.md\"):\n            _ = metadata.core.readme\n\n    @pytest.mark.parametrize(\n        (\"extension\", \"content_type\"), [(\".md\", \"text/markdown\"), (\".rst\", \"text/x-rst\"), (\".txt\", \"text/plain\")]\n    )\n    def test_string_correct(self, extension, content_type, temp_dir):\n        metadata = ProjectMetadata(str(temp_dir), None, {\"project\": {\"readme\": f\"foo/bar{extension}\"}})\n\n        file_path = temp_dir / \"foo\" / f\"bar{extension}\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text(\"test content\")\n\n        assert metadata.core.readme == metadata.core.readme == \"test content\"\n        assert metadata.core.readme_content_type == metadata.core.readme_content_type == content_type\n        assert metadata.core.readme_path == metadata.core.readme_path == f\"foo/bar{extension}\"\n\n    def test_table_content_type_missing(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"readme\": {}}})\n\n        with pytest.raises(ValueError, match=\"Field `content-type` is required in the `project.readme` table\"):\n            _ = metadata.core.readme\n\n    def test_table_content_type_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"readme\": {\"content-type\": 5}}})\n\n        with pytest.raises(TypeError, match=\"Field `content-type` in the `project.readme` table must be a string\"):\n            _ = metadata.core.readme\n\n    def test_table_content_type_not_unknown(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"readme\": {\"content-type\": \"foo\"}}})\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Field `content-type` in the `project.readme` table must be one of the following: \"\n                \"text/markdown, text/x-rst, text/plain\"\n            ),\n        ):\n            _ = metadata.core.readme\n\n    def test_table_multiple_options(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"readme\": {\"content-type\": \"text/markdown\", \"file\": \"\", \"text\": \"\"}}}\n        )\n\n        with pytest.raises(ValueError, match=\"Cannot specify both `file` and `text` in the `project.readme` table\"):\n            _ = metadata.core.readme\n\n    def test_table_no_option(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"readme\": {\"content-type\": \"text/markdown\"}}})\n\n        with pytest.raises(ValueError, match=\"Must specify either `file` or `text` in the `project.readme` table\"):\n            _ = metadata.core.readme\n\n    def test_table_file_not_string(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"readme\": {\"content-type\": \"text/markdown\", \"file\": 4}}}\n        )\n\n        with pytest.raises(TypeError, match=\"Field `file` in the `project.readme` table must be a string\"):\n            _ = metadata.core.readme\n\n    def test_table_file_nonexistent(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"readme\": {\"content-type\": \"text/markdown\", \"file\": \"foo/bar.md\"}}}\n        )\n\n        with pytest.raises(OSError, match=\"Readme file does not exist: foo/bar\\\\.md\"):\n            _ = metadata.core.readme\n\n    def test_table_file_correct(self, temp_dir):\n        metadata = ProjectMetadata(\n            str(temp_dir), None, {\"project\": {\"readme\": {\"content-type\": \"text/markdown\", \"file\": \"foo/bar.markdown\"}}}\n        )\n\n        file_path = temp_dir / \"foo\" / \"bar.markdown\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text(\"test content\")\n\n        assert metadata.core.readme == metadata.core.readme == \"test content\"\n        assert metadata.core.readme_content_type == metadata.core.readme_content_type == \"text/markdown\"\n        assert metadata.core.readme_path == metadata.core.readme_path == \"foo/bar.markdown\"\n\n    def test_table_text_not_string(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"readme\": {\"content-type\": \"text/markdown\", \"text\": 4}}}\n        )\n\n        with pytest.raises(TypeError, match=\"Field `text` in the `project.readme` table must be a string\"):\n            _ = metadata.core.readme\n\n    def test_table_text_correct(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\"}}}\n        )\n\n        assert metadata.core.readme == metadata.core.readme == \"test content\"\n        assert metadata.core.readme_content_type == metadata.core.readme_content_type == \"text/markdown\"\n        assert metadata.core.readme_path == metadata.core.readme_path == \"\"\n\n\nclass TestRequiresPython:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"requires-python\": 9000, \"dynamic\": [\"requires-python\"]}}\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Metadata field `requires-python` cannot be both statically defined and \"\n                \"listed in field `project.dynamic`\"\n            ),\n        ):\n            _ = metadata.core.requires_python\n\n    @pytest.mark.parametrize(\"attribute\", [\"requires_python\", \"python_constraint\"])\n    def test_not_string(self, isolation, attribute):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"requires-python\": 9000}})\n\n        with pytest.raises(TypeError, match=\"Field `project.requires-python` must be a string\"):\n            _ = getattr(metadata.core, attribute)\n\n    def test_invalid(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"requires-python\": \"^1\"}})\n\n        with pytest.raises(ValueError, match=\"Field `project.requires-python` is invalid: .+\"):\n            _ = metadata.core.requires_python\n\n    def test_default(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        assert metadata.core.requires_python == metadata.core.requires_python == \"\"\n        for major_version in map(str, range(10)):\n            assert metadata.core.python_constraint.contains(major_version)\n\n    def test_custom(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"requires-python\": \">2\"}})\n\n        assert metadata.core.requires_python == metadata.core.requires_python == \">2\"\n        assert not metadata.core.python_constraint.contains(\"2\")\n        assert metadata.core.python_constraint.contains(\"3\")\n\n\nclass TestLicense:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": 9000, \"dynamic\": [\"license\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=\"Metadata field `license` cannot be both statically defined and listed in field `project.dynamic`\",\n        ):\n            _ = metadata.core.license\n\n    def test_invalid_type(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": 9000}})\n\n        with pytest.raises(TypeError, match=\"Field `project.license` must be a string or a table\"):\n            _ = metadata.core.license\n\n    def test_default(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        assert metadata.core.license == metadata.core.license == \"\"\n        assert metadata.core.license_expression == metadata.core.license_expression == \"\"\n\n    def test_normalization(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": \"mit or apache-2.0\"}})\n\n        assert metadata.core.license_expression == \"MIT OR Apache-2.0\"\n\n    def test_invalid_expression(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": \"mit or foo\"}})\n\n        with pytest.raises(ValueError, match=\"Error parsing field `project.license` - Unknown license: 'foo'\"):\n            _ = metadata.core.license_expression\n\n    def test_multiple_options(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": {\"file\": \"\", \"text\": \"\"}}})\n\n        with pytest.raises(ValueError, match=\"Cannot specify both `file` and `text` in the `project.license` table\"):\n            _ = metadata.core.license\n\n    def test_no_option(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": {}}})\n\n        with pytest.raises(ValueError, match=\"Must specify either `file` or `text` in the `project.license` table\"):\n            _ = metadata.core.license\n\n    def test_file_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": {\"file\": 4}}})\n\n        with pytest.raises(TypeError, match=\"Field `file` in the `project.license` table must be a string\"):\n            _ = metadata.core.license\n\n    def test_file_nonexistent(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": {\"file\": \"foo/bar.md\"}}})\n\n        with pytest.raises(OSError, match=\"License file does not exist: foo/bar\\\\.md\"):\n            _ = metadata.core.license\n\n    def test_file_correct(self, temp_dir):\n        metadata = ProjectMetadata(str(temp_dir), None, {\"project\": {\"license\": {\"file\": \"foo/bar.md\"}}})\n\n        file_path = temp_dir / \"foo\" / \"bar.md\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text(\"test content\")\n\n        assert metadata.core.license == \"test content\"\n\n    def test_text_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": {\"text\": 4}}})\n\n        with pytest.raises(TypeError, match=\"Field `text` in the `project.license` table must be a string\"):\n            _ = metadata.core.license\n\n    def test_text_correct(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license\": {\"text\": \"test content\"}}})\n\n        assert metadata.core.license == \"test content\"\n\n\nclass TestLicenseFiles:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"license-files\": 9000, \"dynamic\": [\"license-files\"]}}\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Metadata field `license-files` cannot be both statically defined and listed in field `project.dynamic`\"\n            ),\n        ):\n            _ = metadata.core.license_files\n\n    def test_not_array(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license-files\": 9000}})\n\n        with pytest.raises(TypeError, match=\"Field `project.license-files` must be an array\"):\n            _ = metadata.core.license_files\n\n    def test_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"license-files\": [9000]}})\n\n        with pytest.raises(TypeError, match=\"Entry #1 of field `project.license-files` must be a string\"):\n            _ = metadata.core.license_files\n\n    def test_default_globs_no_licenses(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        assert metadata.core.license_files == metadata.core.license_files == []\n\n    def test_default_globs_with_licenses(self, temp_dir):\n        metadata = ProjectMetadata(str(temp_dir), None, {\"project\": {}})\n\n        expected = []\n        (temp_dir / \"foo\").touch()\n\n        for name in (\"LICENSE\", \"LICENCE\", \"COPYING\", \"NOTICE\", \"AUTHORS\"):\n            (temp_dir / name).touch()\n            expected.append(name)\n\n            name_with_extension = f\"{name}.txt\"\n            (temp_dir / f\"{name}.txt\").touch()\n            expected.append(name_with_extension)\n\n        assert metadata.core.license_files == sorted(expected)\n\n    def test_globs_with_licenses(self, temp_dir):\n        metadata = ProjectMetadata(str(temp_dir), None, {\"project\": {\"license-files\": [\"LICENSES/*\"]}})\n\n        licenses_dir = temp_dir / \"LICENSES\"\n        licenses_dir.mkdir()\n        (licenses_dir / \"MIT.txt\").touch()\n        (licenses_dir / \"Apache-2.0.txt\").touch()\n\n        for name in (\"LICENSE\", \"LICENCE\", \"COPYING\", \"NOTICE\", \"AUTHORS\"):\n            (temp_dir / name).touch()\n\n        assert metadata.core.license_files == [\"LICENSES/Apache-2.0.txt\", \"LICENSES/MIT.txt\"]\n\n    def test_paths_with_licenses(self, temp_dir):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            None,\n            {\"project\": {\"license-files\": [\"LICENSES/Apache-2.0.txt\", \"LICENSES/MIT.txt\", \"COPYING\"]}},\n        )\n\n        licenses_dir = temp_dir / \"LICENSES\"\n        licenses_dir.mkdir()\n        (licenses_dir / \"MIT.txt\").touch()\n        (licenses_dir / \"Apache-2.0.txt\").touch()\n\n        for name in (\"LICENSE\", \"LICENCE\", \"COPYING\", \"NOTICE\", \"AUTHORS\"):\n            (temp_dir / name).touch()\n\n        assert metadata.core.license_files == [\"COPYING\", \"LICENSES/Apache-2.0.txt\", \"LICENSES/MIT.txt\"]\n\n\nclass TestAuthors:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"authors\": 9000, \"dynamic\": [\"authors\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=\"Metadata field `authors` cannot be both statically defined and listed in field `project.dynamic`\",\n        ):\n            _ = metadata.core.authors\n\n    def test_not_array(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"authors\": \"foo\"}})\n\n        with pytest.raises(TypeError, match=\"Field `project.authors` must be an array\"):\n            _ = metadata.core.authors\n\n    def test_default(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        assert metadata.core.authors == metadata.core.authors == []\n\n    def test_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"authors\": [\"foo\"]}})\n\n        with pytest.raises(TypeError, match=\"Author #1 of field `project.authors` must be an inline table\"):\n            _ = metadata.core.authors\n\n    def test_no_data(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"authors\": [{}]}})\n\n        with pytest.raises(\n            ValueError, match=\"Author #1 of field `project.authors` must specify either `name` or `email`\"\n        ):\n            _ = metadata.core.authors\n\n    def test_name_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"authors\": [{\"name\": 9}]}})\n\n        with pytest.raises(TypeError, match=\"Name of author #1 of field `project.authors` must be a string\"):\n            _ = metadata.core.authors\n\n    def test_name_only(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"authors\": [{\"name\": \"foo\"}]}})\n\n        assert len(metadata.core.authors) == 1\n        assert metadata.core.authors[0] == {\"name\": \"foo\"}\n        assert metadata.core.authors_data == metadata.core.authors_data == {\"name\": [\"foo\"], \"email\": []}\n\n    def test_email_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"authors\": [{\"email\": 9}]}})\n\n        with pytest.raises(TypeError, match=\"Email of author #1 of field `project.authors` must be a string\"):\n            _ = metadata.core.authors\n\n    def test_email_only(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"authors\": [{\"email\": \"foo@bar.baz\"}]}})\n\n        assert len(metadata.core.authors) == 1\n        assert metadata.core.authors[0] == {\"email\": \"foo@bar.baz\"}\n        assert metadata.core.authors_data == {\"name\": [], \"email\": [\"foo@bar.baz\"]}\n\n    def test_name_and_email(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"authors\": [{\"name\": \"foo2\", \"email\": \"foo2@bar.baz\"}, {\"name\": \"foo1\", \"email\": \"foo1@bar.baz\"}]\n                }\n            },\n        )\n\n        assert len(metadata.core.authors) == 2\n        assert metadata.core.authors[0] == {\"name\": \"foo2\", \"email\": \"foo2@bar.baz\"}\n        assert metadata.core.authors[1] == {\"name\": \"foo1\", \"email\": \"foo1@bar.baz\"}\n        assert metadata.core.authors_data == {\"name\": [], \"email\": [\"foo2 <foo2@bar.baz>\", \"foo1 <foo1@bar.baz>\"]}\n\n\nclass TestMaintainers:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"maintainers\": 9000, \"dynamic\": [\"maintainers\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Metadata field `maintainers` cannot be both statically defined and listed in field `project.dynamic`\"\n            ),\n        ):\n            _ = metadata.core.maintainers\n\n    def test_not_array(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"maintainers\": \"foo\"}})\n\n        with pytest.raises(TypeError, match=\"Field `project.maintainers` must be an array\"):\n            _ = metadata.core.maintainers\n\n    def test_default(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        assert metadata.core.maintainers == metadata.core.maintainers == []\n\n    def test_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"maintainers\": [\"foo\"]}})\n\n        with pytest.raises(TypeError, match=\"Maintainer #1 of field `project.maintainers` must be an inline table\"):\n            _ = metadata.core.maintainers\n\n    def test_no_data(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"maintainers\": [{}]}})\n\n        with pytest.raises(\n            ValueError, match=\"Maintainer #1 of field `project.maintainers` must specify either `name` or `email`\"\n        ):\n            _ = metadata.core.maintainers\n\n    def test_name_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"maintainers\": [{\"name\": 9}]}})\n\n        with pytest.raises(TypeError, match=\"Name of maintainer #1 of field `project.maintainers` must be a string\"):\n            _ = metadata.core.maintainers\n\n    def test_name_only(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"maintainers\": [{\"name\": \"foo\"}]}})\n\n        assert len(metadata.core.maintainers) == 1\n        assert metadata.core.maintainers[0] == {\"name\": \"foo\"}\n        assert metadata.core.maintainers_data == metadata.core.maintainers_data == {\"name\": [\"foo\"], \"email\": []}\n\n    def test_email_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"maintainers\": [{\"email\": 9}]}})\n\n        with pytest.raises(TypeError, match=\"Email of maintainer #1 of field `project.maintainers` must be a string\"):\n            _ = metadata.core.maintainers\n\n    def test_email_only(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"maintainers\": [{\"email\": \"foo@bar.baz\"}]}})\n\n        assert len(metadata.core.maintainers) == 1\n        assert metadata.core.maintainers[0] == {\"email\": \"foo@bar.baz\"}\n        assert metadata.core.maintainers_data == {\"name\": [], \"email\": [\"foo@bar.baz\"]}\n\n    def test_name_and_email(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"maintainers\": [\n                        {\"name\": \"foo2\", \"email\": \"foo2@bar.baz\"},\n                        {\"name\": \"foo1\", \"email\": \"foo1@bar.baz\"},\n                    ]\n                }\n            },\n        )\n\n        assert len(metadata.core.maintainers) == 2\n        assert metadata.core.maintainers[0] == {\"name\": \"foo2\", \"email\": \"foo2@bar.baz\"}\n        assert metadata.core.maintainers[1] == {\"name\": \"foo1\", \"email\": \"foo1@bar.baz\"}\n        assert metadata.core.maintainers_data == {\"name\": [], \"email\": [\"foo2 <foo2@bar.baz>\", \"foo1 <foo1@bar.baz>\"]}\n\n\nclass TestKeywords:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"keywords\": 9000, \"dynamic\": [\"keywords\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=\"Metadata field `keywords` cannot be both statically defined and listed in field `project.dynamic`\",\n        ):\n            _ = metadata.core.keywords\n\n    def test_not_array(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"keywords\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.keywords` must be an array\"):\n            _ = metadata.core.keywords\n\n    def test_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"keywords\": [10]}})\n\n        with pytest.raises(TypeError, match=\"Keyword #1 of field `project.keywords` must be a string\"):\n            _ = metadata.core.keywords\n\n    def test_correct(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"keywords\": [\"foo\", \"foo\", \"bar\"]}})\n\n        assert metadata.core.keywords == metadata.core.keywords == [\"bar\", \"foo\"]\n\n\nclass TestClassifiers:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"classifiers\": 9000, \"dynamic\": [\"classifiers\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Metadata field `classifiers` cannot be both statically defined and listed in field `project.dynamic`\"\n            ),\n        ):\n            _ = metadata.core.classifiers\n\n    def test_not_array(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"classifiers\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.classifiers` must be an array\"):\n            _ = metadata.core.classifiers\n\n    def test_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"classifiers\": [10]}})\n\n        with pytest.raises(TypeError, match=\"Classifier #1 of field `project.classifiers` must be a string\"):\n            _ = metadata.core.classifiers\n\n    def test_entry_unknown(self, isolation, monkeypatch):\n        monkeypatch.delenv(\"HATCH_METADATA_CLASSIFIERS_NO_VERIFY\", False)\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"classifiers\": [\"foo\"]}})\n\n        with pytest.raises(ValueError, match=\"Unknown classifier in field `project.classifiers`: foo\"):\n            _ = metadata.core.classifiers\n\n    def test_entry_unknown_no_verify(self, isolation, monkeypatch):\n        monkeypatch.setenv(\"HATCH_METADATA_CLASSIFIERS_NO_VERIFY\", \"1\")\n        classifiers = [\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.9\",\n            \"Development Status :: 4 - Beta\",\n            \"Private :: Do Not Upload\",\n            \"Foo\",\n        ]\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"classifiers\": classifiers}})\n\n        assert (\n            metadata.core.classifiers\n            == metadata.core.classifiers\n            == [\n                \"Private :: Do Not Upload\",\n                \"Development Status :: 4 - Beta\",\n                \"Foo\",\n                \"Programming Language :: Python :: 3.9\",\n                \"Programming Language :: Python :: 3.11\",\n            ]\n        )\n\n    def test_correct(self, isolation):\n        classifiers = [\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.9\",\n            \"Development Status :: 4 - Beta\",\n            \"Private :: Do Not Upload\",\n        ]\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"classifiers\": classifiers}})\n\n        assert (\n            metadata.core.classifiers\n            == metadata.core.classifiers\n            == [\n                \"Private :: Do Not Upload\",\n                \"Development Status :: 4 - Beta\",\n                \"Programming Language :: Python :: 3.9\",\n                \"Programming Language :: Python :: 3.11\",\n            ]\n        )\n\n\nclass TestURLs:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"urls\": 9000, \"dynamic\": [\"urls\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=\"Metadata field `urls` cannot be both statically defined and listed in field `project.dynamic`\",\n        ):\n            _ = metadata.core.urls\n\n    def test_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"urls\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.urls` must be a table\"):\n            _ = metadata.core.urls\n\n    def test_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"urls\": {\"foo\": 7}}})\n\n        with pytest.raises(TypeError, match=\"URL `foo` of field `project.urls` must be a string\"):\n            _ = metadata.core.urls\n\n    def test_correct(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"}}})\n\n        assert metadata.core.urls == metadata.core.urls == {\"bar\": \"baz\", \"foo\": \"bar\"}\n\n\nclass TestScripts:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"scripts\": 9000, \"dynamic\": [\"scripts\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=\"Metadata field `scripts` cannot be both statically defined and listed in field `project.dynamic`\",\n        ):\n            _ = metadata.core.scripts\n\n    def test_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"scripts\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.scripts` must be a table\"):\n            _ = metadata.core.scripts\n\n    def test_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"scripts\": {\"foo\": 7}}})\n\n        with pytest.raises(TypeError, match=\"Object reference `foo` of field `project.scripts` must be a string\"):\n            _ = metadata.core.scripts\n\n    def test_correct(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"scripts\": {\"foo\": \"bar\", \"bar\": \"baz\"}}})\n\n        assert metadata.core.scripts == metadata.core.scripts == {\"bar\": \"baz\", \"foo\": \"bar\"}\n\n\nclass TestGUIScripts:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"gui-scripts\": 9000, \"dynamic\": [\"gui-scripts\"]}})\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Metadata field `gui-scripts` cannot be both statically defined and listed in field `project.dynamic`\"\n            ),\n        ):\n            _ = metadata.core.gui_scripts\n\n    def test_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"gui-scripts\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.gui-scripts` must be a table\"):\n            _ = metadata.core.gui_scripts\n\n    def test_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"gui-scripts\": {\"foo\": 7}}})\n\n        with pytest.raises(TypeError, match=\"Object reference `foo` of field `project.gui-scripts` must be a string\"):\n            _ = metadata.core.gui_scripts\n\n    def test_correct(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"gui-scripts\": {\"foo\": \"bar\", \"bar\": \"baz\"}}})\n\n        assert metadata.core.gui_scripts == metadata.core.gui_scripts == {\"bar\": \"baz\", \"foo\": \"bar\"}\n\n\nclass TestEntryPoints:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"entry-points\": 9000, \"dynamic\": [\"entry-points\"]}}\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Metadata field `entry-points` cannot be both statically defined and listed in field `project.dynamic`\"\n            ),\n        ):\n            _ = metadata.core.entry_points\n\n    def test_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"entry-points\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.entry-points` must be a table\"):\n            _ = metadata.core.entry_points\n\n    @pytest.mark.parametrize((\"field\", \"expected\"), [(\"console_scripts\", \"scripts\"), (\"gui-scripts\", \"gui-scripts\")])\n    def test_forbidden_fields(self, isolation, field, expected):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"entry-points\": {field: \"foo\"}}})\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                f\"Field `{field}` must be defined as `project.{expected}` instead of \"\n                f\"in the `project.entry-points` table\"\n            ),\n        ):\n            _ = metadata.core.entry_points\n\n    def test_data_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"entry-points\": {\"foo\": 7}}})\n\n        with pytest.raises(TypeError, match=\"Field `project.entry-points.foo` must be a table\"):\n            _ = metadata.core.entry_points\n\n    def test_data_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"entry-points\": {\"foo\": {\"bar\": 4}}}})\n\n        with pytest.raises(\n            TypeError, match=\"Object reference `bar` of field `project.entry-points.foo` must be a string\"\n        ):\n            _ = metadata.core.entry_points\n\n    def test_data_empty(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"entry-points\": {\"foo\": {}}}})\n\n        assert metadata.core.entry_points == metadata.core.entry_points == {}\n\n    def test_default(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {}})\n\n        assert metadata.core.entry_points == metadata.core.entry_points == {}\n\n    def test_correct(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"entry-points\": {\"foo\": {\"bar\": \"baz\", \"foo\": \"baz\"}, \"bar\": {\"foo\": \"baz\", \"bar\": \"baz\"}}}},\n        )\n\n        assert (\n            metadata.core.entry_points\n            == metadata.core.entry_points\n            == {\"bar\": {\"bar\": \"baz\", \"foo\": \"baz\"}, \"foo\": {\"bar\": \"baz\", \"foo\": \"baz\"}}\n        )\n\n\nclass TestDependencies:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"dependencies\": 9000, \"dynamic\": [\"dependencies\"]}}\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Metadata field `dependencies` cannot be both statically defined and listed in field `project.dynamic`\"\n            ),\n        ):\n            _ = metadata.core.dependencies\n\n    def test_not_array(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"dependencies\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.dependencies` must be an array\"):\n            _ = metadata.core.dependencies\n\n    def test_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"dependencies\": [10]}})\n\n        with pytest.raises(TypeError, match=\"Dependency #1 of field `project.dependencies` must be a string\"):\n            _ = metadata.core.dependencies\n\n    def test_invalid(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"dependencies\": [\"foo^1\"]}})\n\n        with pytest.raises(ValueError, match=\"Dependency #1 of field `project.dependencies` is invalid: .+\"):\n            _ = metadata.core.dependencies\n\n    def test_direct_reference(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"dependencies\": [\"proj @ git+https://github.com/org/proj.git@v1\"]}}\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Dependency #1 of field `project.dependencies` cannot be a direct reference unless \"\n                \"field `tool.hatch.metadata.allow-direct-references` is set to `true`\"\n            ),\n        ):\n            _ = metadata.core.dependencies\n\n    def test_direct_reference_allowed(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\"dependencies\": [\"proj @ git+https://github.com/org/proj.git@v1\"]},\n                \"tool\": {\"hatch\": {\"metadata\": {\"allow-direct-references\": True}}},\n            },\n        )\n\n        assert metadata.core.dependencies == [\"proj @ git+https://github.com/org/proj.git@v1\"]\n\n    def test_context_formatting(self, isolation, uri_slash_prefix):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\"dependencies\": [\"proj @ {root:uri}\"]},\n                \"tool\": {\"hatch\": {\"metadata\": {\"allow-direct-references\": True}}},\n            },\n        )\n\n        normalized_path = str(isolation).replace(\"\\\\\", \"/\")\n        assert metadata.core.dependencies == [f\"proj @ file:{uri_slash_prefix}{normalized_path}\"]\n\n    def test_correct(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"dependencies\": [\n                        'python___dateutil;platform_python_implementation==\"CPython\"',\n                        \"bAr.Baz[TLS, Zu.Bat, EdDSA, Zu_Bat]   >=1.2RC5 , <9000B1\",\n                        'Foo;python_version<\"3.8\"',\n                        'fOO;     python_version<    \"3.8\"',\n                    ],\n                },\n            },\n        )\n\n        assert (\n            metadata.core.dependencies\n            == metadata.core.dependencies\n            == [\n                \"bar-baz[eddsa,tls,zu-bat]<9000b1,>=1.2rc5\",\n                \"foo; python_version < '3.8'\",\n                \"python-dateutil; platform_python_implementation == 'CPython'\",\n            ]\n        )\n        assert metadata.core.dependencies_complex is metadata.core.dependencies_complex\n\n\nclass TestOptionalDependencies:\n    def test_dynamic(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"optional-dependencies\": 9000, \"dynamic\": [\"optional-dependencies\"]}}\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Metadata field `optional-dependencies` cannot be both statically defined and \"\n                \"listed in field `project.dynamic`\"\n            ),\n        ):\n            _ = metadata.core.optional_dependencies\n\n    def test_not_table(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"optional-dependencies\": 10}})\n\n        with pytest.raises(TypeError, match=\"Field `project.optional-dependencies` must be a table\"):\n            _ = metadata.core.optional_dependencies\n\n    def test_invalid_name(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"optional-dependencies\": {\"foo/bar\": []}}})\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Optional dependency group `foo/bar` of field `project.optional-dependencies` must only contain \"\n                \"ASCII letters/digits, underscores, hyphens, and periods, and must begin and end with \"\n                \"ASCII letters/digits.\"\n            ),\n        ):\n            _ = metadata.core.optional_dependencies\n\n    def test_definitions_not_array(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"optional-dependencies\": {\"foo\": 5}}})\n\n        with pytest.raises(\n            TypeError, match=\"Dependencies for option `foo` of field `project.optional-dependencies` must be an array\"\n        ):\n            _ = metadata.core.optional_dependencies\n\n    def test_entry_not_string(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"optional-dependencies\": {\"foo\": [5]}}})\n\n        with pytest.raises(\n            TypeError, match=\"Dependency #1 of option `foo` of field `project.optional-dependencies` must be a string\"\n        ):\n            _ = metadata.core.optional_dependencies\n\n    def test_invalid(self, isolation):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"optional-dependencies\": {\"foo\": [\"bar^1\"]}}})\n\n        with pytest.raises(\n            ValueError, match=\"Dependency #1 of option `foo` of field `project.optional-dependencies` is invalid: .+\"\n        ):\n            _ = metadata.core.optional_dependencies\n\n    def test_conflict(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"optional-dependencies\": {\"foo_bar\": [], \"foo.bar\": []}}}\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Optional dependency groups `foo_bar` and `foo.bar` of field `project.optional-dependencies` both \"\n                \"evaluate to `foo-bar`.\"\n            ),\n        ):\n            _ = metadata.core.optional_dependencies\n\n    def test_recursive_circular(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"my-app\", \"optional-dependencies\": {\"foo\": [\"my-app[bar]\"], \"bar\": [\"my-app[foo]\"]}}},\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=\"Field `project.optional-dependencies` defines a circular dependency group: foo\",\n        ):\n            _ = metadata.core.optional_dependencies\n\n    def test_recursive_unknown(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"my-app\", \"optional-dependencies\": {\"foo\": [\"my-app[bar]\"]}}},\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=\"Unknown recursive dependency group in field `project.optional-dependencies`: bar\",\n        ):\n            _ = metadata.core.optional_dependencies\n\n    def test_allow_ambiguity(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\"optional-dependencies\": {\"foo_bar\": [], \"foo.bar\": []}},\n                \"tool\": {\"hatch\": {\"metadata\": {\"allow-ambiguous-features\": True}}},\n            },\n        )\n\n        assert metadata.core.optional_dependencies == {\"foo_bar\": [], \"foo.bar\": []}\n\n    def test_direct_reference(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"optional-dependencies\": {\"foo\": [\"proj @ git+https://github.com/org/proj.git@v1\"]}}},\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Dependency #1 of option `foo` of field `project.optional-dependencies` cannot be a direct reference \"\n                \"unless field `tool.hatch.metadata.allow-direct-references` is set to `true`\"\n            ),\n        ):\n            _ = metadata.core.optional_dependencies\n\n    def test_context_formatting(self, isolation, uri_slash_prefix):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\"name\": \"my-app\", \"optional-dependencies\": {\"foo\": [\"proj @ {root:uri}\"]}},\n                \"tool\": {\"hatch\": {\"metadata\": {\"allow-direct-references\": True}}},\n            },\n        )\n\n        normalized_path = str(isolation).replace(\"\\\\\", \"/\")\n        assert metadata.core.optional_dependencies == {\"foo\": [f\"proj @ file:{uri_slash_prefix}{normalized_path}\"]}\n\n    def test_direct_reference_allowed(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"my-app\",\n                    \"optional-dependencies\": {\"foo\": [\"proj @ git+https://github.com/org/proj.git@v1\"]},\n                },\n                \"tool\": {\"hatch\": {\"metadata\": {\"allow-direct-references\": True}}},\n            },\n        )\n\n        assert metadata.core.optional_dependencies == {\"foo\": [\"proj @ git+https://github.com/org/proj.git@v1\"]}\n\n    def test_correct(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"my-app\",\n                    \"optional-dependencies\": {\n                        \"foo\": [\n                            'python___dateutil;platform_python_implementation==\"CPython\"',\n                            \"bAr.Baz[TLS, Zu.Bat, EdDSA, Zu_Bat]   >=1.2RC5 , <9000B1\",\n                            'Foo;python_version<\"3.8\"',\n                            'fOO;     python_version<    \"3.8\"',\n                            \"MY-APP[zZz]\",\n                        ],\n                        \"bar\": [\"foo\", \"bar\", \"Baz\"],\n                        \"baz\": [\"my___app[XYZ]\"],\n                        \"xyz\": [\"my...app[Bar]\"],\n                        \"zzz\": [\"aaa\"],\n                    },\n                },\n            },\n        )\n\n        assert (\n            metadata.core.optional_dependencies\n            == metadata.core.optional_dependencies\n            == {\n                \"bar\": [\"bar\", \"baz\", \"foo\"],\n                \"baz\": [\"bar\", \"baz\", \"foo\"],\n                \"foo\": [\n                    \"aaa\",\n                    \"bar-baz[eddsa,tls,zu-bat]<9000b1,>=1.2rc5\",\n                    \"foo; python_version < '3.8'\",\n                    \"python-dateutil; platform_python_implementation == 'CPython'\",\n                ],\n                \"xyz\": [\"bar\", \"baz\", \"foo\"],\n                \"zzz\": [\"aaa\"],\n            }\n        )\n\n\nclass TestHook:\n    def test_unknown(self, isolation):\n        metadata = ProjectMetadata(\n            str(isolation),\n            PluginManager(),\n            {\"project\": {\"name\": \"foo\"}, \"tool\": {\"hatch\": {\"metadata\": {\"hooks\": {\"foo\": {}}}}}},\n        )\n\n        with pytest.raises(ValueError, match=\"Unknown metadata hook: foo\"):\n            _ = metadata.core\n\n    def test_custom(self, temp_dir, helpers):\n        classifiers = [\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.9\",\n            \"Framework :: Foo\",\n            \"Development Status :: 4 - Beta\",\n            \"Private :: Do Not Upload\",\n        ]\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"classifiers\": classifiers, \"dynamic\": [\"version\", \"description\"]},\n                \"tool\": {\"hatch\": {\"version\": {\"path\": \"a/b\"}, \"metadata\": {\"hooks\": {\"custom\": {}}}}},\n            },\n        )\n\n        file_path = temp_dir / \"a\" / \"b\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text('__version__ = \"0.0.1\"')\n\n        file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n                class CustomHook(MetadataHookInterface):\n                    def update(self, metadata):\n                        metadata['description'] = metadata['name'] + 'bar'\n                        metadata['version'] = metadata['version'] + 'rc0'\n\n                    def get_known_classifiers(self):\n                        return ['Framework :: Foo']\n                \"\"\"\n            )\n        )\n\n        assert \"custom\" in metadata.hatch.metadata.hooks\n        assert metadata.core.name == \"foo\"\n        assert metadata.core.description == \"foobar\"\n        assert metadata.core.version == \"0.0.1rc0\"\n        assert metadata.core.classifiers == [\n            \"Private :: Do Not Upload\",\n            \"Development Status :: 4 - Beta\",\n            \"Framework :: Foo\",\n            \"Programming Language :: Python :: 3.9\",\n            \"Programming Language :: Python :: 3.11\",\n        ]\n\n    def test_custom_missing_dynamic(self, temp_dir, helpers):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"dynamic\": [\"version\"]},\n                \"tool\": {\"hatch\": {\"version\": {\"path\": \"a/b\"}, \"metadata\": {\"hooks\": {\"custom\": {}}}}},\n            },\n        )\n\n        file_path = temp_dir / \"a\" / \"b\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text('__version__ = \"0.0.1\"')\n\n        file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n                class CustomHook(MetadataHookInterface):\n                    def update(self, metadata):\n                        metadata['description'] = metadata['name'] + 'bar'\n                \"\"\"\n            )\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=\"The field `description` was set dynamically and therefore must be listed in `project.dynamic`\",\n        ):\n            _ = metadata.core\n\n\nclass TestHatchPersonalProjectConfigFile:\n    def test_correct(self, temp_dir, helpers):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"dynamic\": [\"version\"]},\n                \"tool\": {\"hatch\": {\"build\": {\"reproducible\": False}}},\n            },\n        )\n\n        file_path = temp_dir / \"a\" / \"b\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text('__version__ = \"0.0.1\"')\n\n        (temp_dir / \"pyproject.toml\").touch()\n        file_path = temp_dir / \"hatch.toml\"\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                [version]\n                path = 'a/b'\n                \"\"\"\n            )\n        )\n\n        assert metadata.version == \"0.0.1\"\n        assert metadata.hatch.build_config[\"reproducible\"] is False\n\n    def test_precedence(self, temp_dir, helpers):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\"name\": \"foo\", \"dynamic\": [\"version\"]},\n                \"tool\": {\"hatch\": {\"version\": {\"path\": \"a/b\"}, \"build\": {\"reproducible\": False}}},\n            },\n        )\n\n        file_path = temp_dir / \"a\" / \"b\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text('__version__ = \"0.0.1\"')\n\n        file_path = temp_dir / \"c\" / \"d\"\n        file_path.ensure_parent_dir_exists()\n        file_path.write_text('__version__ = \"0.0.2\"')\n\n        (temp_dir / \"pyproject.toml\").touch()\n        file_path = temp_dir / \"hatch.toml\"\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                [version]\n                path = 'c/d'\n                \"\"\"\n            )\n        )\n\n        assert metadata.version == \"0.0.2\"\n        assert metadata.hatch.build_config[\"reproducible\"] is False\n\n\nclass TestMetadataConversion:\n    def test_required_only(self, isolation, latest_spec):\n        raw_metadata = {\"name\": \"My.App\", \"version\": \"0.0.1\"}\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_dynamic(self, isolation, latest_spec):\n        raw_metadata = {\"name\": \"My.App\", \"version\": \"0.0.1\", \"dynamic\": [\"authors\", \"classifiers\"]}\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_description(self, isolation, latest_spec):\n        raw_metadata = {\"name\": \"My.App\", \"version\": \"0.0.1\", \"description\": \"foo bar\"}\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_urls(self, isolation, latest_spec):\n        raw_metadata = {\"name\": \"My.App\", \"version\": \"0.0.1\", \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"}}\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_authors(self, isolation, latest_spec):\n        raw_metadata = {\n            \"name\": \"My.App\",\n            \"version\": \"0.0.1\",\n            \"authors\": [{\"name\": \"foobar\"}, {\"email\": \"bar@domain\", \"name\": \"foo\"}, {\"email\": \"baz@domain\"}],\n        }\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_maintainers(self, isolation, latest_spec):\n        raw_metadata = {\n            \"name\": \"My.App\",\n            \"version\": \"0.0.1\",\n            \"maintainers\": [{\"name\": \"foobar\"}, {\"email\": \"bar@domain\", \"name\": \"foo\"}, {\"email\": \"baz@domain\"}],\n        }\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_keywords(self, isolation, latest_spec):\n        raw_metadata = {\"name\": \"My.App\", \"version\": \"0.0.1\", \"keywords\": [\"bar\", \"foo\"]}\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_classifiers(self, isolation, latest_spec):\n        raw_metadata = {\n            \"name\": \"My.App\",\n            \"version\": \"0.0.1\",\n            \"classifiers\": [\"Programming Language :: Python :: 3.9\", \"Programming Language :: Python :: 3.11\"],\n        }\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_license_files(self, temp_dir, latest_spec):\n        raw_metadata = {\n            \"name\": \"My.App\",\n            \"version\": \"0.0.1\",\n            \"license-files\": [\"LICENSES/Apache-2.0.txt\", \"LICENSES/MIT.txt\"],\n        }\n        metadata = ProjectMetadata(str(temp_dir), None, {\"project\": raw_metadata})\n\n        licenses_path = temp_dir / \"LICENSES\"\n        licenses_path.mkdir()\n        licenses_path.joinpath(\"Apache-2.0.txt\").touch()\n        licenses_path.joinpath(\"MIT.txt\").touch()\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_license_expression(self, isolation, latest_spec):\n        raw_metadata = {\"name\": \"My.App\", \"version\": \"0.0.1\", \"license\": \"MIT\"}\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_license_legacy(self, isolation, latest_spec):\n        raw_metadata = {\"name\": \"My.App\", \"version\": \"0.0.1\", \"license\": {\"text\": \"foo\"}}\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_readme(self, isolation, latest_spec):\n        raw_metadata = {\n            \"name\": \"My.App\",\n            \"version\": \"0.0.1\",\n            \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n        }\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_requires_python(self, isolation, latest_spec):\n        raw_metadata = {\"name\": \"My.App\", \"version\": \"0.0.1\", \"requires-python\": \"<2,>=1\"}\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n    def test_dependencies(self, isolation, latest_spec):\n        raw_metadata = {\n            \"name\": \"My.App\",\n            \"version\": \"0.0.1\",\n            \"dependencies\": [\"bar==5\", \"foo==1\"],\n            \"optional-dependencies\": {\n                \"feature1\": ['bar==5; python_version < \"3\"', \"foo==1\"],\n                \"feature2\": [\"bar==5\", 'foo==1; python_version < \"3\"'],\n            },\n        }\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": raw_metadata})\n\n        core_metadata = latest_spec(metadata)\n        assert project_metadata_from_core_metadata(core_metadata) == raw_metadata\n\n\nclass TestSourceDistributionMetadata:\n    def test_basic_persistence(self, temp_dir, helpers):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"dynamic\": [\"version\", \"keywords\"],\n                    \"dependencies\": [\"foo==1\"],\n                    \"scripts\": {\"foo\": \"bar\"},\n                },\n            },\n        )\n\n        pkg_info = temp_dir / \"PKG-INFO\"\n        pkg_info.write_text(\n            helpers.dedent(\n                f\"\"\"\n                Metadata-Version: {LATEST_METADATA_VERSION}\n                Name: My.App\n                Version: 0.0.1\n                Keywords: foo,bar\n                Requires-Dist: bar==5\n                \"\"\"\n            )\n        )\n        with temp_dir.as_cwd():\n            assert metadata.core.config == {\n                \"name\": \"My.App\",\n                \"version\": \"0.0.1\",\n                \"dependencies\": [\"foo==1\"],\n                \"keywords\": [\"foo\", \"bar\"],\n                \"scripts\": {\"foo\": \"bar\"},\n                \"dynamic\": [],\n            }\n\n    def test_metadata_hooks(self, temp_dir, helpers):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            PluginManager(),\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"dynamic\": [\"version\", \"keywords\", \"description\", \"scripts\"],\n                    \"dependencies\": [\"foo==1\"],\n                },\n                \"tool\": {\"hatch\": {\"metadata\": {\"hooks\": {\"custom\": {}}}}},\n            },\n        )\n\n        build_script = temp_dir / DEFAULT_BUILD_SCRIPT\n        build_script.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n                class CustomHook(MetadataHookInterface):\n                    def update(self, metadata):\n                        metadata['description'] = metadata['name'] + ' bar'\n                        metadata['scripts'] = {'foo': 'bar'}\n                \"\"\"\n            )\n        )\n\n        pkg_info = temp_dir / \"PKG-INFO\"\n        pkg_info.write_text(\n            helpers.dedent(\n                f\"\"\"\n                Metadata-Version: {LATEST_METADATA_VERSION}\n                Name: My.App\n                Version: 0.0.1\n                Summary: My.App bar\n                Keywords: foo,bar\n                Requires-Dist: bar==5\n                \"\"\"\n            )\n        )\n        with temp_dir.as_cwd():\n            assert metadata.core.config == {\n                \"name\": \"My.App\",\n                \"version\": \"0.0.1\",\n                \"description\": \"My.App bar\",\n                \"dependencies\": [\"foo==1\"],\n                \"keywords\": [\"foo\", \"bar\"],\n                \"scripts\": {\"foo\": \"bar\"},\n                \"dynamic\": [\"scripts\"],\n            }\n"
  },
  {
    "path": "tests/backend/metadata/test_custom_hook.py",
    "content": "import re\n\nimport pytest\n\nfrom hatchling.metadata.custom import CustomMetadataHook\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\n\ndef test_no_path(isolation):\n    config = {\"path\": \"\"}\n\n    with pytest.raises(ValueError, match=\"Option `path` for metadata hook `custom` must not be empty if defined\"):\n        CustomMetadataHook(str(isolation), config)\n\n\ndef test_path_not_string(isolation):\n    config = {\"path\": 3}\n\n    with pytest.raises(TypeError, match=\"Option `path` for metadata hook `custom` must be a string\"):\n        CustomMetadataHook(str(isolation), config)\n\n\ndef test_nonexistent(isolation):\n    config = {\"path\": \"test.py\"}\n\n    with pytest.raises(OSError, match=\"Build script does not exist: test.py\"):\n        CustomMetadataHook(str(isolation), config)\n\n\ndef test_default(temp_dir, helpers):\n    config = {}\n\n    file_path = temp_dir / DEFAULT_BUILD_SCRIPT\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n            class CustomHook(MetadataHookInterface):\n                def update(self, metadata):\n                    pass\n\n                def foo(self):\n                    return self.PLUGIN_NAME, self.root\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        hook = CustomMetadataHook(str(temp_dir), config)\n\n    assert hook.foo() == (\"custom\", str(temp_dir))\n\n\ndef test_explicit_path(temp_dir, helpers):\n    config = {\"path\": f\"foo/{DEFAULT_BUILD_SCRIPT}\"}\n\n    file_path = temp_dir / \"foo\" / DEFAULT_BUILD_SCRIPT\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n            class CustomHook(MetadataHookInterface):\n                def update(self, metadata):\n                    pass\n\n                def foo(self):\n                    return self.PLUGIN_NAME, self.root\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        hook = CustomMetadataHook(str(temp_dir), config)\n\n    assert hook.foo() == (\"custom\", str(temp_dir))\n\n\ndef test_no_subclass(temp_dir, helpers):\n    config = {\"path\": f\"foo/{DEFAULT_BUILD_SCRIPT}\"}\n\n    file_path = temp_dir / \"foo\" / DEFAULT_BUILD_SCRIPT\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n            foo = None\n            bar = 'baz'\n\n            class CustomHook:\n                pass\n            \"\"\"\n        )\n    )\n\n    with (\n        pytest.raises(\n            ValueError,\n            match=re.escape(\n                f\"Unable to find a subclass of `MetadataHookInterface` in `foo/{DEFAULT_BUILD_SCRIPT}`: {temp_dir}\"\n            ),\n        ),\n        temp_dir.as_cwd(),\n    ):\n        CustomMetadataHook(str(temp_dir), config)\n"
  },
  {
    "path": "tests/backend/metadata/test_hatch.py",
    "content": "import pytest\n\nfrom hatchling.metadata.core import HatchMetadata\nfrom hatchling.plugin.manager import PluginManager\nfrom hatchling.version.scheme.standard import StandardScheme\nfrom hatchling.version.source.regex import RegexSource\n\n\nclass TestBuildConfig:\n    def test_default(self, isolation):\n        config = {}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.build_config == metadata.build_config == {}\n\n    def test_not_table(self, isolation):\n        config = {\"build\": 0}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build` must be a table\"):\n            _ = metadata.build_config\n\n    def test_correct(self, isolation):\n        config = {\"build\": {\"reproducible\": True}}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.build_config == metadata.build_config == {\"reproducible\": True}\n\n\nclass TestBuildTargets:\n    def test_default(self, isolation):\n        config = {}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.build_targets == metadata.build_targets == {}\n\n    def test_not_table(self, isolation):\n        config = {\"build\": {\"targets\": 0}}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets` must be a table\"):\n            _ = metadata.build_targets\n\n    def test_correct(self, isolation):\n        config = {\"build\": {\"targets\": {\"wheel\": {\"versions\": [\"standard\"]}}}}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.build_targets == metadata.build_targets == {\"wheel\": {\"versions\": [\"standard\"]}}\n\n\nclass TestVersionSourceName:\n    def test_empty(self, isolation):\n        with pytest.raises(\n            ValueError, match=\"The `source` option under the `tool.hatch.version` table must not be empty if defined\"\n        ):\n            _ = HatchMetadata(isolation, {\"version\": {\"source\": \"\"}}, None).version.source_name\n\n    def test_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.version.source` must be a string\"):\n            _ = HatchMetadata(isolation, {\"version\": {\"source\": 9000}}, None).version.source_name\n\n    def test_correct(self, isolation):\n        metadata = HatchMetadata(isolation, {\"version\": {\"source\": \"foo\"}}, None)\n\n        assert metadata.version.source_name == metadata.version.source_name == \"foo\"\n\n    def test_default(self, isolation):\n        metadata = HatchMetadata(isolation, {\"version\": {}}, None)\n\n        assert metadata.version.source_name == metadata.version.source_name == \"regex\"\n\n\nclass TestVersionSchemeName:\n    def test_missing(self, isolation):\n        with pytest.raises(\n            ValueError, match=\"The `scheme` option under the `tool.hatch.version` table must not be empty if defined\"\n        ):\n            _ = HatchMetadata(isolation, {\"version\": {\"scheme\": \"\"}}, None).version.scheme_name\n\n    def test_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.version.scheme` must be a string\"):\n            _ = HatchMetadata(isolation, {\"version\": {\"scheme\": 9000}}, None).version.scheme_name\n\n    def test_correct(self, isolation):\n        metadata = HatchMetadata(isolation, {\"version\": {\"scheme\": \"foo\"}}, None)\n\n        assert metadata.version.scheme_name == metadata.version.scheme_name == \"foo\"\n\n    def test_default(self, isolation):\n        metadata = HatchMetadata(isolation, {\"version\": {}}, None)\n\n        assert metadata.version.scheme_name == metadata.version.scheme_name == \"standard\"\n\n\nclass TestVersionSource:\n    def test_unknown(self, isolation):\n        with pytest.raises(ValueError, match=\"Unknown version source: foo\"):\n            _ = HatchMetadata(isolation, {\"version\": {\"source\": \"foo\"}}, PluginManager()).version.source\n\n    def test_cached(self, isolation):\n        metadata = HatchMetadata(isolation, {\"version\": {}}, PluginManager())\n\n        assert metadata.version.source is metadata.version.source\n        assert isinstance(metadata.version.source, RegexSource)\n\n\nclass TestVersionScheme:\n    def test_unknown(self, isolation):\n        with pytest.raises(ValueError, match=\"Unknown version scheme: foo\"):\n            _ = HatchMetadata(isolation, {\"version\": {\"scheme\": \"foo\"}}, PluginManager()).version.scheme\n\n    def test_cached(self, isolation):\n        metadata = HatchMetadata(isolation, {\"version\": {}}, PluginManager())\n\n        assert metadata.version.scheme is metadata.version.scheme\n        assert isinstance(metadata.version.scheme, StandardScheme)\n\n\nclass TestMetadata:\n    def test_default(self, isolation):\n        config = {}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.metadata.config == metadata.metadata.config == {}\n\n    def test_not_table(self, isolation):\n        config = {\"metadata\": 0}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.metadata` must be a table\"):\n            _ = metadata.metadata.config\n\n    def test_correct(self, isolation):\n        config = {\"metadata\": {\"option\": True}}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.metadata.config == metadata.metadata.config == {\"option\": True}\n\n\nclass TestMetadataAllowDirectReferences:\n    def test_default(self, isolation):\n        config = {}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.metadata.allow_direct_references is metadata.metadata.allow_direct_references is False\n\n    def test_not_boolean(self, isolation):\n        config = {\"metadata\": {\"allow-direct-references\": 9000}}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.metadata.allow-direct-references` must be a boolean\"):\n            _ = metadata.metadata.allow_direct_references\n\n    def test_correct(self, isolation):\n        config = {\"metadata\": {\"allow-direct-references\": True}}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.metadata.allow_direct_references is True\n\n\nclass TestMetadataAllowAmbiguousFeatures:\n    def test_default(self, isolation):\n        config = {}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.metadata.allow_ambiguous_features is metadata.metadata.allow_ambiguous_features is False\n\n    def test_not_boolean(self, isolation):\n        config = {\"metadata\": {\"allow-ambiguous-features\": 9000}}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.metadata.allow-ambiguous-features` must be a boolean\"):\n            _ = metadata.metadata.allow_ambiguous_features\n\n    def test_correct(self, isolation):\n        config = {\"metadata\": {\"allow-ambiguous-features\": True}}\n        metadata = HatchMetadata(str(isolation), config, None)\n\n        assert metadata.metadata.allow_ambiguous_features is True\n"
  },
  {
    "path": "tests/backend/metadata/test_spec.py",
    "content": "import pytest\n\nfrom hatchling.metadata.core import ProjectMetadata\nfrom hatchling.metadata.spec import (\n    LATEST_METADATA_VERSION,\n    get_core_metadata_constructors,\n    project_metadata_from_core_metadata,\n)\n\n\nclass TestProjectMetadataFromCoreMetadata:\n    def test_missing_name(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\n\"\"\"\n        with pytest.raises(ValueError, match=\"^Missing required core metadata: Name$\"):\n            project_metadata_from_core_metadata(core_metadata)\n\n    def test_missing_version(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\n\"\"\"\n        with pytest.raises(ValueError, match=\"^Missing required core metadata: Version$\"):\n            project_metadata_from_core_metadata(core_metadata)\n\n    def test_dynamic(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nDynamic: Classifier\nDynamic: Provides-Extra\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"dynamic\": [\"classifiers\", \"dependencies\", \"optional-dependencies\"],\n        }\n\n    def test_description(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nSummary: foo\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"description\": \"foo\",\n        }\n\n    def test_urls(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nProject-URL: foo, bar\nProject-URL: bar, baz\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"},\n        }\n\n    def test_authors(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nAuthor: foobar\nAuthor-email: foo <bar@domain>, <baz@domain>\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"authors\": [{\"name\": \"foobar\"}, {\"email\": \"bar@domain\", \"name\": \"foo\"}, {\"email\": \"baz@domain\"}],\n        }\n\n    def test_maintainers(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nMaintainer: foobar\nMaintainer-email: foo <bar@domain>, <baz@domain>\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"maintainers\": [{\"name\": \"foobar\"}, {\"email\": \"bar@domain\", \"name\": \"foo\"}, {\"email\": \"baz@domain\"}],\n        }\n\n    def test_keywords(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nKeywords: bar,foo\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"keywords\": [\"bar\", \"foo\"],\n        }\n\n    def test_classifiers(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nClassifier: Programming Language :: Python :: 3.9\nClassifier: Programming Language :: Python :: 3.11\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"classifiers\": [\"Programming Language :: Python :: 3.9\", \"Programming Language :: Python :: 3.11\"],\n        }\n\n    def test_license_files(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nLicense-File: LICENSES/Apache-2.0.txt\nLicense-File: LICENSES/MIT.txt\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"license-files\": [\"LICENSES/Apache-2.0.txt\", \"LICENSES/MIT.txt\"],\n        }\n\n    def test_license_expression(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nLicense-Expression: MIT\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"license\": \"MIT\",\n        }\n\n    def test_license_legacy(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nLicense: foo\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"license\": {\"text\": \"foo\"},\n        }\n\n    def test_readme(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nDescription-Content-Type: text/markdown\n\ntest content\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n        }\n\n    def test_readme_default_content_type(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\n\ntest content\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"readme\": {\"content-type\": \"text/plain\", \"text\": \"test content\\n\"},\n        }\n\n    def test_requires_python(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nRequires-Python: <2,>=1\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"requires-python\": \"<2,>=1\",\n        }\n\n    def test_dependencies(self):\n        core_metadata = f\"\"\"\\\nMetadata-Version: {LATEST_METADATA_VERSION}\nName: My.App\nVersion: 0.1.0\nRequires-Dist: bar==5\nRequires-Dist: foo==1\nProvides-Extra: feature1\nRequires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\nRequires-Dist: foo==1; extra == 'feature1'\nProvides-Extra: feature2\nRequires-Dist: bar==5; extra == 'feature2'\nRequires-Dist: foo==1; (python_version < '3') and extra == 'feature2'\nProvides-Extra: feature3\nRequires-Dist: baz @ file:///path/to/project ; extra == 'feature3'\n\"\"\"\n        assert project_metadata_from_core_metadata(core_metadata) == {\n            \"name\": \"My.App\",\n            \"version\": \"0.1.0\",\n            \"dependencies\": [\"bar==5\", \"foo==1\"],\n            \"optional-dependencies\": {\n                \"feature1\": ['bar==5; python_version < \"3\"', \"foo==1\"],\n                \"feature2\": [\"bar==5\", 'foo==1; python_version < \"3\"'],\n                \"feature3\": [\"baz @ file:///path/to/project\"],\n            },\n        }\n\n\n@pytest.mark.parametrize(\"constructor\", [get_core_metadata_constructors()[\"1.2\"]])\nclass TestCoreMetadataV12:\n    def test_default(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}})\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            \"\"\"\n        )\n\n    def test_description(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"description\": \"foo\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            \"\"\"\n        )\n\n    def test_urls(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"}}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            \"\"\"\n        )\n\n    def test_authors_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Author: foo\n            \"\"\"\n        )\n\n    def test_authors_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo@domain\n            \"\"\"\n        )\n\n    def test_authors_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_authors_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Author: foo, bar\n            \"\"\"\n        )\n\n    def test_maintainers_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo\n            \"\"\"\n        )\n\n    def test_maintainers_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo@domain\n            \"\"\"\n        )\n\n    def test_maintainers_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_maintainers_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo, bar\n            \"\"\"\n        )\n\n    def test_license(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": {\"text\": \"foo\\nbar\"}}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            License: foo\n                    bar\n            \"\"\"\n        )\n\n    def test_license_expression(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": \"mit\"}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            License: MIT\n            \"\"\"\n        )\n\n    def test_keywords_single(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Keywords: foo\n            \"\"\"\n        )\n\n    def test_keywords_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\", \"bar\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Keywords: bar,foo\n            \"\"\"\n        )\n\n    def test_classifiers(self, constructor, isolation, helpers):\n        classifiers = [\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.9\",\n        ]\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"classifiers\": classifiers}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            \"\"\"\n        )\n\n    def test_requires_python(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"requires-python\": \">=1,<2\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Requires-Python: <2,>=1\n            \"\"\"\n        )\n\n    def test_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            \"\"\"\n        )\n\n    def test_extra_runtime_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata, extra_dependencies=[\"baz==9\"]) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            Requires-Dist: baz==9\n            \"\"\"\n        )\n\n    def test_all(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"description\": \"foo\",\n                    \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"},\n                    \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"license\": {\"text\": \"foo\\nbar\"},\n                    \"keywords\": [\"foo\", \"bar\"],\n                    \"classifiers\": [\n                        \"Programming Language :: Python :: 3.11\",\n                        \"Programming Language :: Python :: 3.9\",\n                    ],\n                    \"requires-python\": \">=1,<2\",\n                    \"dependencies\": [\"foo==1\", \"bar==5\"],\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 1.2\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            Author-email: foo <bar@domain>\n            Maintainer-email: foo <bar@domain>\n            License: foo\n                    bar\n            Keywords: bar,foo\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            Requires-Python: <2,>=1\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            \"\"\"\n        )\n\n\n@pytest.mark.parametrize(\"constructor\", [get_core_metadata_constructors()[\"2.1\"]])\nclass TestCoreMetadataV21:\n    def test_default(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}})\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            \"\"\"\n        )\n\n    def test_description(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"description\": \"foo\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            \"\"\"\n        )\n\n    def test_urls(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"}}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            \"\"\"\n        )\n\n    def test_authors_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Author: foo\n            \"\"\"\n        )\n\n    def test_authors_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo@domain\n            \"\"\"\n        )\n\n    def test_authors_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_authors_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Author: foo, bar\n            \"\"\"\n        )\n\n    def test_maintainers_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo\n            \"\"\"\n        )\n\n    def test_maintainers_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo@domain\n            \"\"\"\n        )\n\n    def test_maintainers_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_maintainers_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo, bar\n            \"\"\"\n        )\n\n    def test_license(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": {\"text\": \"foo\\nbar\"}}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            License: foo\n                    bar\n            \"\"\"\n        )\n\n    def test_license_expression(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": \"mit\"}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            License: MIT\n            \"\"\"\n        )\n\n    def test_keywords_single(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Keywords: foo\n            \"\"\"\n        )\n\n    def test_keywords_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\", \"bar\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Keywords: bar,foo\n            \"\"\"\n        )\n\n    def test_classifiers(self, constructor, isolation, helpers):\n        classifiers = [\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.9\",\n        ]\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"classifiers\": classifiers}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            \"\"\"\n        )\n\n    def test_requires_python(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"requires-python\": \">=1,<2\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Requires-Python: <2,>=1\n            \"\"\"\n        )\n\n    def test_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            \"\"\"\n        )\n\n    def test_optional_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"optional-dependencies\": {\n                        \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                        \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                    },\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Provides-Extra: feature1\n            Requires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\n            Requires-Dist: foo==1; extra == 'feature1'\n            Provides-Extra: feature2\n            Requires-Dist: bar==5; extra == 'feature2'\n            Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'\n            \"\"\"\n        )\n\n    def test_extra_runtime_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata, extra_dependencies=[\"baz==9\"]) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            Requires-Dist: baz==9\n            \"\"\"\n        )\n\n    def test_readme(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Description-Content-Type: text/markdown\n\n            test content\n            \"\"\"\n        )\n\n    def test_all(self, constructor, helpers, temp_dir):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"description\": \"foo\",\n                    \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"},\n                    \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"license\": {\"text\": \"foo\\nbar\"},\n                    \"keywords\": [\"foo\", \"bar\"],\n                    \"classifiers\": [\n                        \"Programming Language :: Python :: 3.11\",\n                        \"Programming Language :: Python :: 3.9\",\n                    ],\n                    \"requires-python\": \">=1,<2\",\n                    \"dependencies\": [\"foo==1\", \"bar==5\"],\n                    \"optional-dependencies\": {\n                        \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                        \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                        \"feature3\": [\"baz @ file:///path/to/project\"],\n                    },\n                    \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n                },\n                \"tool\": {\"hatch\": {\"metadata\": {\"allow-direct-references\": True}}},\n            },\n        )\n\n        (temp_dir / \"LICENSE.txt\").touch()\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.1\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            Author-email: foo <bar@domain>\n            Maintainer-email: foo <bar@domain>\n            License: foo\n                    bar\n            Keywords: bar,foo\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            Requires-Python: <2,>=1\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            Provides-Extra: feature1\n            Requires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\n            Requires-Dist: foo==1; extra == 'feature1'\n            Provides-Extra: feature2\n            Requires-Dist: bar==5; extra == 'feature2'\n            Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'\n            Provides-Extra: feature3\n            Requires-Dist: baz @ file:///path/to/project ; extra == 'feature3'\n            Description-Content-Type: text/markdown\n\n            test content\n            \"\"\"\n        )\n\n\n@pytest.mark.parametrize(\"constructor\", [get_core_metadata_constructors()[\"2.2\"]])\nclass TestCoreMetadataV22:\n    def test_default(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}})\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            \"\"\"\n        )\n\n    def test_dynamic(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dynamic\": [\"authors\", \"classifiers\"]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Dynamic: Author\n            Dynamic: Author-email\n            Dynamic: Classifier\n            \"\"\"\n        )\n\n    def test_description(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"description\": \"foo\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            \"\"\"\n        )\n\n    def test_urls(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"}}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            \"\"\"\n        )\n\n    def test_authors_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Author: foo\n            \"\"\"\n        )\n\n    def test_authors_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo@domain\n            \"\"\"\n        )\n\n    def test_authors_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_authors_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Author: foo, bar\n            \"\"\"\n        )\n\n    def test_maintainers_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo\n            \"\"\"\n        )\n\n    def test_maintainers_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo@domain\n            \"\"\"\n        )\n\n    def test_maintainers_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_maintainers_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo, bar\n            \"\"\"\n        )\n\n    def test_license(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": {\"text\": \"foo\\nbar\"}}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            License: foo\n                    bar\n            \"\"\"\n        )\n\n    def test_license_expression(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": \"mit\"}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            License: MIT\n            \"\"\"\n        )\n\n    def test_keywords_single(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Keywords: foo\n            \"\"\"\n        )\n\n    def test_keywords_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\", \"bar\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Keywords: bar,foo\n            \"\"\"\n        )\n\n    def test_classifiers(self, constructor, isolation, helpers):\n        classifiers = [\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.9\",\n        ]\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"classifiers\": classifiers}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            \"\"\"\n        )\n\n    def test_requires_python(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"requires-python\": \">=1,<2\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Requires-Python: <2,>=1\n            \"\"\"\n        )\n\n    def test_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            \"\"\"\n        )\n\n    def test_optional_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"optional-dependencies\": {\n                        \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                        \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                    },\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Provides-Extra: feature1\n            Requires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\n            Requires-Dist: foo==1; extra == 'feature1'\n            Provides-Extra: feature2\n            Requires-Dist: bar==5; extra == 'feature2'\n            Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'\n            \"\"\"\n        )\n\n    def test_optional_complex_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"optional-dependencies\": {\n                        \"feature2\": ['foo==1; sys_platform == \"win32\" or python_version < \"3\"', \"bar==5\"],\n                        \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                    },\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Provides-Extra: feature1\n            Requires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\n            Requires-Dist: foo==1; extra == 'feature1'\n            Provides-Extra: feature2\n            Requires-Dist: bar==5; extra == 'feature2'\n            Requires-Dist: foo==1; (sys_platform == 'win32' or python_version < '3') and extra == 'feature2'\n            \"\"\"\n        )\n\n    def test_extra_runtime_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata, extra_dependencies=[\"baz==9\"]) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            Requires-Dist: baz==9\n            \"\"\"\n        )\n\n    def test_readme(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Description-Content-Type: text/markdown\n\n            test content\n            \"\"\"\n        )\n\n    def test_all(self, constructor, helpers, temp_dir):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"description\": \"foo\",\n                    \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"},\n                    \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"license\": {\"text\": \"foo\\nbar\"},\n                    \"keywords\": [\"foo\", \"bar\"],\n                    \"classifiers\": [\n                        \"Programming Language :: Python :: 3.11\",\n                        \"Programming Language :: Python :: 3.9\",\n                    ],\n                    \"requires-python\": \">=1,<2\",\n                    \"dependencies\": [\"foo==1\", \"bar==5\"],\n                    \"optional-dependencies\": {\n                        \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                        \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                        \"feature3\": [\"baz @ file:///path/to/project\"],\n                    },\n                    \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n                },\n                \"tool\": {\"hatch\": {\"metadata\": {\"allow-direct-references\": True}}},\n            },\n        )\n\n        (temp_dir / \"LICENSE.txt\").touch()\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.2\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            Author-email: foo <bar@domain>\n            Maintainer-email: foo <bar@domain>\n            License: foo\n                    bar\n            Keywords: bar,foo\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            Requires-Python: <2,>=1\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            Provides-Extra: feature1\n            Requires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\n            Requires-Dist: foo==1; extra == 'feature1'\n            Provides-Extra: feature2\n            Requires-Dist: bar==5; extra == 'feature2'\n            Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'\n            Provides-Extra: feature3\n            Requires-Dist: baz @ file:///path/to/project ; extra == 'feature3'\n            Description-Content-Type: text/markdown\n\n            test content\n            \"\"\"\n        )\n\n\n@pytest.mark.parametrize(\"constructor\", [get_core_metadata_constructors()[\"2.3\"]])\nclass TestCoreMetadataV23:\n    def test_default(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}})\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            \"\"\"\n        )\n\n    def test_description(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"description\": \"foo\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            \"\"\"\n        )\n\n    def test_dynamic(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dynamic\": [\"authors\", \"classifiers\"]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Dynamic: Author\n            Dynamic: Author-email\n            Dynamic: Classifier\n            \"\"\"\n        )\n\n    def test_urls(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"}}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            \"\"\"\n        )\n\n    def test_authors_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Author: foo\n            \"\"\"\n        )\n\n    def test_authors_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo@domain\n            \"\"\"\n        )\n\n    def test_authors_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_authors_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Author: foo, bar\n            \"\"\"\n        )\n\n    def test_maintainers_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo\n            \"\"\"\n        )\n\n    def test_maintainers_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo@domain\n            \"\"\"\n        )\n\n    def test_maintainers_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_maintainers_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo, bar\n            \"\"\"\n        )\n\n    def test_license(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": {\"text\": \"foo\\nbar\"}}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            License: foo\n                    bar\n            \"\"\"\n        )\n\n    def test_license_expression(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": \"mit\"}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            License: MIT\n            \"\"\"\n        )\n\n    def test_keywords_single(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Keywords: foo\n            \"\"\"\n        )\n\n    def test_keywords_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\", \"bar\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Keywords: bar,foo\n            \"\"\"\n        )\n\n    def test_classifiers(self, constructor, isolation, helpers):\n        classifiers = [\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.9\",\n        ]\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"classifiers\": classifiers}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            \"\"\"\n        )\n\n    def test_requires_python(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"requires-python\": \">=1,<2\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Requires-Python: <2,>=1\n            \"\"\"\n        )\n\n    def test_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            \"\"\"\n        )\n\n    def test_optional_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"optional-dependencies\": {\n                        \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                        \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                    },\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Provides-Extra: feature1\n            Requires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\n            Requires-Dist: foo==1; extra == 'feature1'\n            Provides-Extra: feature2\n            Requires-Dist: bar==5; extra == 'feature2'\n            Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'\n            \"\"\"\n        )\n\n    def test_extra_runtime_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata, extra_dependencies=[\"baz==9\"]) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            Requires-Dist: baz==9\n            \"\"\"\n        )\n\n    def test_readme(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Description-Content-Type: text/markdown\n\n            test content\n            \"\"\"\n        )\n\n    def test_all(self, constructor, temp_dir, helpers):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"description\": \"foo\",\n                    \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"},\n                    \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"keywords\": [\"foo\", \"bar\"],\n                    \"classifiers\": [\n                        \"Programming Language :: Python :: 3.11\",\n                        \"Programming Language :: Python :: 3.9\",\n                    ],\n                    \"requires-python\": \">=1,<2\",\n                    \"dependencies\": [\"foo==1\", \"bar==5\"],\n                    \"optional-dependencies\": {\n                        \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                        \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                        \"feature3\": [\"baz @ file:///path/to/project\"],\n                    },\n                    \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n                },\n                \"tool\": {\"hatch\": {\"metadata\": {\"allow-direct-references\": True}}},\n            },\n        )\n\n        licenses_dir = temp_dir / \"LICENSES\"\n        licenses_dir.mkdir()\n        (licenses_dir / \"MIT.txt\").touch()\n        (licenses_dir / \"Apache-2.0.txt\").touch()\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.3\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            Author-email: foo <bar@domain>\n            Maintainer-email: foo <bar@domain>\n            Keywords: bar,foo\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            Requires-Python: <2,>=1\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            Provides-Extra: feature1\n            Requires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\n            Requires-Dist: foo==1; extra == 'feature1'\n            Provides-Extra: feature2\n            Requires-Dist: bar==5; extra == 'feature2'\n            Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'\n            Provides-Extra: feature3\n            Requires-Dist: baz @ file:///path/to/project ; extra == 'feature3'\n            Description-Content-Type: text/markdown\n\n            test content\n            \"\"\"\n        )\n\n\n@pytest.mark.parametrize(\"constructor\", [get_core_metadata_constructors()[\"2.4\"]])\nclass TestCoreMetadataV24:\n    def test_default(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\"}})\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            \"\"\"\n        )\n\n    def test_description(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"description\": \"foo\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            \"\"\"\n        )\n\n    def test_dynamic(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dynamic\": [\"authors\", \"classifiers\"]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Dynamic: Author\n            Dynamic: Author-email\n            Dynamic: Classifier\n            \"\"\"\n        )\n\n    def test_urls(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"}}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            \"\"\"\n        )\n\n    def test_authors_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Author: foo\n            \"\"\"\n        )\n\n    def test_authors_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo@domain\n            \"\"\"\n        )\n\n    def test_authors_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Author-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_authors_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"authors\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Author: foo, bar\n            \"\"\"\n        )\n\n    def test_maintainers_name(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo\n            \"\"\"\n        )\n\n    def test_maintainers_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"email\": \"foo@domain\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo@domain\n            \"\"\"\n        )\n\n    def test_maintainers_name_and_email(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Maintainer-email: foo <bar@domain>\n            \"\"\"\n        )\n\n    def test_maintainers_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"maintainers\": [{\"name\": \"foo\"}, {\"name\": \"bar\"}]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Maintainer: foo, bar\n            \"\"\"\n        )\n\n    def test_license(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": {\"text\": \"foo\\nbar\"}}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            License: foo\n                    bar\n            \"\"\"\n        )\n\n    def test_license_expression(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license\": \"mit or apache-2.0\"}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            License-Expression: MIT OR Apache-2.0\n            \"\"\"\n        )\n\n    def test_license_files(self, constructor, temp_dir, helpers):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"license-files\": [\"LICENSES/*\"]}},\n        )\n\n        licenses_dir = temp_dir / \"LICENSES\"\n        licenses_dir.mkdir()\n        (licenses_dir / \"MIT.txt\").touch()\n        (licenses_dir / \"Apache-2.0.txt\").touch()\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            License-File: LICENSES/Apache-2.0.txt\n            License-File: LICENSES/MIT.txt\n            \"\"\"\n        )\n\n    def test_keywords_single(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Keywords: foo\n            \"\"\"\n        )\n\n    def test_keywords_multiple(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"keywords\": [\"foo\", \"bar\"]}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Keywords: bar,foo\n            \"\"\"\n        )\n\n    def test_classifiers(self, constructor, isolation, helpers):\n        classifiers = [\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.9\",\n        ]\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"classifiers\": classifiers}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            \"\"\"\n        )\n\n    def test_requires_python(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation), None, {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"requires-python\": \">=1,<2\"}}\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Requires-Python: <2,>=1\n            \"\"\"\n        )\n\n    def test_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            \"\"\"\n        )\n\n    def test_optional_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"optional-dependencies\": {\n                        \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                        \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                    },\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Provides-Extra: feature1\n            Requires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\n            Requires-Dist: foo==1; extra == 'feature1'\n            Provides-Extra: feature2\n            Requires-Dist: bar==5; extra == 'feature2'\n            Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'\n            \"\"\"\n        )\n\n    def test_extra_runtime_dependencies(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\"project\": {\"name\": \"My.App\", \"version\": \"0.1.0\", \"dependencies\": [\"foo==1\", \"bar==5\"]}},\n        )\n\n        assert constructor(metadata, extra_dependencies=[\"baz==9\"]) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            Requires-Dist: baz==9\n            \"\"\"\n        )\n\n    def test_readme(self, constructor, isolation, helpers):\n        metadata = ProjectMetadata(\n            str(isolation),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n                }\n            },\n        )\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Description-Content-Type: text/markdown\n\n            test content\n            \"\"\"\n        )\n\n    def test_all(self, constructor, temp_dir, helpers):\n        metadata = ProjectMetadata(\n            str(temp_dir),\n            None,\n            {\n                \"project\": {\n                    \"name\": \"My.App\",\n                    \"version\": \"0.1.0\",\n                    \"description\": \"foo\",\n                    \"urls\": {\"foo\": \"bar\", \"bar\": \"baz\"},\n                    \"authors\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"maintainers\": [{\"email\": \"bar@domain\", \"name\": \"foo\"}],\n                    \"license\": \"mit or apache-2.0\",\n                    \"license-files\": [\"LICENSES/*\"],\n                    \"keywords\": [\"foo\", \"bar\"],\n                    \"classifiers\": [\n                        \"Programming Language :: Python :: 3.11\",\n                        \"Programming Language :: Python :: 3.9\",\n                    ],\n                    \"requires-python\": \">=1,<2\",\n                    \"dependencies\": [\"foo==1\", \"bar==5\"],\n                    \"optional-dependencies\": {\n                        \"feature2\": ['foo==1; python_version < \"3\"', \"bar==5\"],\n                        \"feature1\": [\"foo==1\", 'bar==5; python_version < \"3\"'],\n                        \"feature3\": [\"baz @ file:///path/to/project\"],\n                    },\n                    \"readme\": {\"content-type\": \"text/markdown\", \"text\": \"test content\\n\"},\n                },\n                \"tool\": {\"hatch\": {\"metadata\": {\"allow-direct-references\": True}}},\n            },\n        )\n\n        licenses_dir = temp_dir / \"LICENSES\"\n        licenses_dir.mkdir()\n        (licenses_dir / \"MIT.txt\").touch()\n        (licenses_dir / \"Apache-2.0.txt\").touch()\n\n        assert constructor(metadata) == helpers.dedent(\n            \"\"\"\n            Metadata-Version: 2.4\n            Name: My.App\n            Version: 0.1.0\n            Summary: foo\n            Project-URL: foo, bar\n            Project-URL: bar, baz\n            Author-email: foo <bar@domain>\n            Maintainer-email: foo <bar@domain>\n            License-Expression: MIT OR Apache-2.0\n            License-File: LICENSES/Apache-2.0.txt\n            License-File: LICENSES/MIT.txt\n            Keywords: bar,foo\n            Classifier: Programming Language :: Python :: 3.9\n            Classifier: Programming Language :: Python :: 3.11\n            Requires-Python: <2,>=1\n            Requires-Dist: bar==5\n            Requires-Dist: foo==1\n            Provides-Extra: feature1\n            Requires-Dist: bar==5; (python_version < '3') and extra == 'feature1'\n            Requires-Dist: foo==1; extra == 'feature1'\n            Provides-Extra: feature2\n            Requires-Dist: bar==5; extra == 'feature2'\n            Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'\n            Provides-Extra: feature3\n            Requires-Dist: baz @ file:///path/to/project ; extra == 'feature3'\n            Description-Content-Type: text/markdown\n\n            test content\n            \"\"\"\n        )\n"
  },
  {
    "path": "tests/backend/test_build.py",
    "content": "from hatchling.build import build_editable, build_sdist, build_wheel\n\n\ndef test_sdist(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    project_config = project_path / \"pyproject.toml\"\n    project_config.write_text(\n        helpers.dedent(\n            \"\"\"\n            [project]\n            name = 'my__app'\n            dynamic = [ 'version' ]\n\n            [tool.hatch.version]\n            path = 'my_app/__about__.py'\n\n            [tool.hatch.build.targets.sdist]\n            versions = '9000'\n            \"\"\"\n        )\n    )\n\n    build_path = project_path / \"dist\"\n    build_path.mkdir()\n\n    with project_path.as_cwd():\n        expected_artifact = build_sdist(str(build_path))\n\n    build_artifacts = list(build_path.iterdir())\n    assert len(build_artifacts) == 1\n    assert expected_artifact == str(build_artifacts[0].name)\n    assert expected_artifact.endswith(\".tar.gz\")\n\n\ndef test_wheel(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    project_config = project_path / \"pyproject.toml\"\n    project_config.write_text(\n        helpers.dedent(\n            \"\"\"\n            [project]\n            name = 'my__app'\n            dynamic = [ 'version' ]\n\n            [tool.hatch.version]\n            path = 'my_app/__about__.py'\n\n            [tool.hatch.build.targets.wheel]\n            versions = '9000'\n            \"\"\"\n        )\n    )\n\n    build_path = project_path / \"dist\"\n    build_path.mkdir()\n\n    with project_path.as_cwd():\n        expected_artifact = build_wheel(str(build_path))\n\n    build_artifacts = list(build_path.iterdir())\n    assert len(build_artifacts) == 1\n    assert expected_artifact == str(build_artifacts[0].name)\n    assert expected_artifact.endswith(\".whl\")\n\n\ndef test_editable(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    project_config = project_path / \"pyproject.toml\"\n    project_config.write_text(\n        helpers.dedent(\n            \"\"\"\n            [project]\n            name = 'my__app'\n            dynamic = [ 'version' ]\n\n            [tool.hatch.version]\n            path = 'my_app/__about__.py'\n\n            [tool.hatch.build.targets.wheel]\n            versions = '9000'\n            \"\"\"\n        )\n    )\n\n    build_path = project_path / \"dist\"\n    build_path.mkdir()\n\n    with project_path.as_cwd():\n        expected_artifact = build_editable(str(build_path), None)\n\n    build_artifacts = list(build_path.iterdir())\n    assert len(build_artifacts) == 1\n    assert expected_artifact == str(build_artifacts[0].name)\n    assert expected_artifact.endswith(\".whl\")\n"
  },
  {
    "path": "tests/backend/utils/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/utils/test_context.py",
    "content": "import os\n\nimport pytest\n\nfrom hatch.utils.structures import EnvVars\nfrom hatchling.utils.context import Context\n\n\ndef test_normal(isolation):\n    context = Context(isolation)\n    assert context.format(\"foo {0} {key}\", \"arg\", key=\"value\") == \"foo arg value\"\n\n\nclass TestStatic:\n    def test_directory_separator(self, isolation):\n        context = Context(isolation)\n        assert context.format(\"foo {/}\") == f\"foo {os.sep}\"\n\n    def test_path_separator(self, isolation):\n        context = Context(isolation)\n        assert context.format(\"foo {;}\") == f\"foo {os.pathsep}\"\n\n\nclass TestRoot:\n    def test_default(self, isolation):\n        context = Context(isolation)\n        assert context.format(\"foo {root}\") == f\"foo {isolation}\"\n\n    def test_parent(self, isolation):\n        context = Context(isolation)\n        path = os.path.dirname(str(isolation))\n        assert context.format(\"foo {root:parent}\") == f\"foo {path}\"\n\n    def test_parent_parent(self, isolation):\n        context = Context(isolation)\n        path = os.path.dirname(os.path.dirname(str(isolation)))\n        assert context.format(\"foo {root:parent:parent}\") == f\"foo {path}\"\n\n    def test_uri(self, isolation, uri_slash_prefix):\n        context = Context(isolation)\n        normalized_path = str(isolation).replace(os.sep, \"/\")\n        assert context.format(\"foo {root:uri}\") == f\"foo file:{uri_slash_prefix}{normalized_path}\"\n\n    def test_uri_parent(self, isolation, uri_slash_prefix):\n        context = Context(isolation)\n        normalized_path = os.path.dirname(str(isolation)).replace(os.sep, \"/\")\n        assert context.format(\"foo {root:parent:uri}\") == f\"foo file:{uri_slash_prefix}{normalized_path}\"\n\n    def test_uri_parent_parent(self, isolation, uri_slash_prefix):\n        context = Context(isolation)\n        normalized_path = os.path.dirname(os.path.dirname(str(isolation))).replace(os.sep, \"/\")\n        assert context.format(\"foo {root:parent:parent:uri}\") == f\"foo file:{uri_slash_prefix}{normalized_path}\"\n\n    def test_real(self, isolation):\n        context = Context(isolation)\n        real_path = os.path.realpath(isolation)\n        assert context.format(\"foo {root:real}\") == f\"foo {real_path}\"\n\n    def test_real_parent(self, isolation):\n        context = Context(isolation)\n        real_path = os.path.dirname(os.path.realpath(isolation))\n        assert context.format(\"foo {root:parent:real}\") == f\"foo {real_path}\"\n\n    def test_real_parent_parent(self, isolation):\n        context = Context(isolation)\n        real_path = os.path.dirname(os.path.dirname(os.path.realpath(isolation)))\n        assert context.format(\"foo {root:parent:parent:real}\") == f\"foo {real_path}\"\n\n    def test_unknown_modifier(self, isolation):\n        context = Context(isolation)\n\n        with pytest.raises(ValueError, match=\"Unknown path modifier: bar\"):\n            context.format(\"foo {root:bar}\")\n\n    def test_too_many_modifiers_after_parent(self, isolation):\n        context = Context(isolation)\n\n        with pytest.raises(ValueError, match=\"Expected a single path modifier and instead got: foo, bar, baz\"):\n            context.format(\"foo {root:parent:foo:bar:baz}\")\n\n\nclass TestHome:\n    def test_default(self, isolation):\n        context = Context(isolation)\n        assert context.format(\"foo {home}\") == f\"foo {os.path.expanduser('~')}\"\n\n    def test_uri(self, isolation, uri_slash_prefix):\n        context = Context(isolation)\n        normalized_path = os.path.expanduser(\"~\").replace(os.sep, \"/\")\n        assert context.format(\"foo {home:uri}\") == f\"foo file:{uri_slash_prefix}{normalized_path}\"\n\n    def test_real(self, isolation):\n        context = Context(isolation)\n        assert context.format(\"foo {home:real}\") == f\"foo {os.path.realpath(os.path.expanduser('~'))}\"\n\n    def test_unknown_modifier(self, isolation):\n        context = Context(isolation)\n\n        with pytest.raises(ValueError, match=\"Unknown path modifier: bar\"):\n            context.format(\"foo {home:bar}\")\n\n\nclass TestEnvVars:\n    def test_set(self, isolation):\n        context = Context(isolation)\n\n        with EnvVars({\"BAR\": \"foobarbaz\"}):\n            assert context.format(\"foo {env:BAR}\") == \"foo foobarbaz\"\n\n    def test_default(self, isolation):\n        context = Context(isolation)\n\n        assert context.format(\"foo {env:BAR:foobarbaz}\") == \"foo foobarbaz\"\n\n    def test_default_empty_string(self, isolation):\n        context = Context(isolation)\n\n        assert context.format(\"foo {env:BAR:}\") == \"foo \"\n\n    def test_default_nested_set(self, isolation):\n        context = Context(isolation)\n\n        with EnvVars({\"BAZ\": \"foobarbaz\"}):\n            assert context.format(\"foo {env:BAR:{env:BAZ}}\") == \"foo foobarbaz\"\n\n    def test_default_nested_default(self, isolation):\n        context = Context(isolation)\n\n        assert context.format(\"foo {env:BAR:{env:BAZ:{home}}}\") == f\"foo {os.path.expanduser('~')}\"\n\n    def test_no_selection(self, isolation):\n        context = Context(isolation)\n\n        with pytest.raises(ValueError, match=\"The `env` context formatting field requires a modifier\"):\n            context.format(\"foo {env}\")\n\n    def test_unset_without_default(self, isolation):\n        context = Context(isolation)\n\n        with pytest.raises(ValueError, match=\"Nonexistent environment variable must set a default: BAR\"):\n            context.format(\"foo {env:BAR}\")\n"
  },
  {
    "path": "tests/backend/utils/test_fs.py",
    "content": "import os\n\nfrom hatchling.utils.fs import path_to_uri\n\n\nclass TestPathToURI:\n    def test_unix(self, isolation, uri_slash_prefix):\n        bad_path = f\"{isolation}{os.sep}\"\n        normalized_path = str(isolation).replace(os.sep, \"/\")\n        assert path_to_uri(bad_path) == f\"file:{uri_slash_prefix}{normalized_path}\"\n\n    def test_character_escaping(self, temp_dir, uri_slash_prefix):\n        path = temp_dir / \"foo bar\"\n        normalized_path = str(path).replace(os.sep, \"/\").replace(\" \", \"%20\")\n        assert path_to_uri(path) == f\"file:{uri_slash_prefix}{normalized_path}\"\n"
  },
  {
    "path": "tests/backend/utils/test_macos.py",
    "content": "from __future__ import annotations\n\nimport platform\n\nimport pytest\n\nfrom hatchling.builders.macos import normalize_macos_version, process_macos_plat_tag\n\n\n@pytest.mark.parametrize(\n    (\"plat\", \"arch\", \"compat\", \"archflags\", \"deptarget\", \"expected\"),\n    [\n        (\"macosx_10_9_x86_64\", \"x86_64\", False, \"\", \"\", \"macosx_10_9_x86_64\"),\n        (\"macosx_11_9_x86_64\", \"x86_64\", False, \"\", \"\", \"macosx_11_0_x86_64\"),\n        (\"macosx_12_0_x86_64\", \"x86_64\", True, \"\", \"\", \"macosx_10_16_x86_64\"),\n        (\"macosx_10_9_arm64\", \"arm64\", False, \"\", \"\", \"macosx_11_0_arm64\"),\n        (\"macosx_10_9_arm64\", \"arm64\", False, \"-arch x86_64 -arch arm64\", \"\", \"macosx_10_9_universal2\"),\n        (\"macosx_10_9_x86_64\", \"x86_64\", False, \"-arch x86_64 -arch arm64\", \"\", \"macosx_10_9_universal2\"),\n        (\"macosx_10_9_x86_64\", \"x86_64\", False, \"-arch x86_64 -arch arm64\", \"12\", \"macosx_12_0_universal2\"),\n        (\"macosx_10_9_x86_64\", \"x86_64\", False, \"-arch arm64\", \"12.4\", \"macosx_12_0_arm64\"),\n        (\"macosx_10_9_x86_64\", \"x86_64\", False, \"-arch arm64\", \"10.12\", \"macosx_11_0_arm64\"),\n        (\"macosx_10_9_x86_64\", \"x86_64\", True, \"-arch arm64\", \"10.12\", \"macosx_10_16_arm64\"),\n    ],\n)\ndef test_process_macos_plat_tag(\n    monkeypatch: pytest.MonkeyPatch,\n    *,\n    plat: str,\n    arch: str,\n    compat: bool,\n    archflags: str,\n    deptarget: str,\n    expected: str,\n) -> None:\n    monkeypatch.setenv(\"ARCHFLAGS\", archflags)\n    monkeypatch.setenv(\"MACOSX_DEPLOYMENT_TARGET\", deptarget)\n    monkeypatch.setattr(platform, \"machine\", lambda: arch)\n\n    assert process_macos_plat_tag(plat, compat=compat) == expected\n\n\n@pytest.mark.parametrize(\n    (\"version\", \"arm\", \"compat\", \"expected\"),\n    [\n        (\"10_9\", False, False, \"10_9\"),\n        (\"10_9\", False, True, \"10_9\"),\n        (\"10_9\", True, False, \"11_0\"),\n        (\"10_9\", True, True, \"10_9\"),\n        (\"11_3\", False, False, \"11_0\"),\n        (\"12_3\", True, False, \"12_0\"),\n        (\"12_3\", False, True, \"10_16\"),\n        (\"12_3\", True, True, \"10_16\"),\n    ],\n)\ndef check_normalization(*, version: str, arm: bool, compat: bool, expected: str) -> None:\n    assert normalize_macos_version(version, arm=arm, compat=compat) == expected\n"
  },
  {
    "path": "tests/backend/version/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/version/scheme/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/version/scheme/test_standard.py",
    "content": "import pytest\nfrom packaging.version import _parse_letter_version  # noqa: PLC2701\n\nfrom hatch.utils.structures import EnvVars\nfrom hatchling.utils.constants import VersionEnvVars\nfrom hatchling.version.scheme.standard import StandardScheme\n\n\ndef test_not_higher(isolation):\n    scheme = StandardScheme(str(isolation), {})\n\n    with pytest.raises(ValueError, match=\"Version `1.0.0` is not higher than the original version `1.0`\"):\n        scheme.update(\"1.0.0\", \"1.0\", {})\n\n\ndef test_specific(isolation):\n    scheme = StandardScheme(str(isolation), {})\n\n    assert scheme.update(\"9000.0.0-rc.1\", \"1.0\", {}) == \"9000.0.0rc1\"\n\n\ndef test_specific_not_higher_allowed_config(isolation):\n    scheme = StandardScheme(str(isolation), {\"validate-bump\": False})\n\n    assert scheme.update(\"0.24.4\", \"1.0.0.dev0\", {}) == \"0.24.4\"\n\n\ndef test_specific_not_higher_allowed_env_var(isolation):\n    scheme = StandardScheme(str(isolation), {})\n\n    with EnvVars({VersionEnvVars.VALIDATE_BUMP: \"false\"}):\n        assert scheme.update(\"0.24.4\", \"1.0.0.dev0\", {}) == \"0.24.4\"\n\n\ndef test_release(isolation):\n    scheme = StandardScheme(str(isolation), {})\n\n    assert scheme.update(\"release\", \"9000.0.0-rc.1.post7.dev5\", {}) == \"9000.0.0\"\n\n\ndef test_major(isolation):\n    scheme = StandardScheme(str(isolation), {})\n\n    assert scheme.update(\"major\", \"9000.0.0-rc.1\", {}) == \"9001.0.0\"\n\n\ndef test_minor(isolation):\n    scheme = StandardScheme(str(isolation), {})\n\n    assert scheme.update(\"minor\", \"9000.0.0-rc.1\", {}) == \"9000.1.0\"\n\n\n@pytest.mark.parametrize(\"keyword\", [\"micro\", \"patch\", \"fix\"])\ndef test_micro(isolation, keyword):\n    scheme = StandardScheme(str(isolation), {})\n\n    assert scheme.update(keyword, \"9000.0.0-rc.1\", {}) == \"9000.0.1\"\n\n\nclass TestPre:\n    @pytest.mark.parametrize(\"phase\", [\"a\", \"b\", \"c\", \"rc\", \"alpha\", \"beta\", \"pre\", \"preview\"])\n    def test_begin(self, isolation, phase):\n        scheme = StandardScheme(str(isolation), {})\n\n        normalized_phase, _ = _parse_letter_version(phase, 0)\n        assert scheme.update(phase, \"9000.0.0.post7.dev5\", {}) == f\"9000.0.0{normalized_phase}0\"\n\n    @pytest.mark.parametrize(\"phase\", [\"a\", \"b\", \"c\", \"rc\", \"alpha\", \"beta\", \"pre\", \"preview\"])\n    def test_continue(self, isolation, phase):\n        scheme = StandardScheme(str(isolation), {})\n\n        normalized_phase, _ = _parse_letter_version(phase, 0)\n        assert scheme.update(phase, f\"9000.0.0{phase}0.post7.dev5\", {}) == f\"9000.0.0{normalized_phase}1\"\n\n    @pytest.mark.parametrize(\"phase\", [\"a\", \"b\", \"c\", \"rc\", \"alpha\", \"beta\", \"pre\", \"preview\"])\n    def test_restart(self, isolation, phase):\n        scheme = StandardScheme(str(isolation), {})\n\n        normalized_phase, _ = _parse_letter_version(phase, 0)\n        other_phase = \"b\" if normalized_phase == \"a\" else \"a\"\n        assert scheme.update(phase, f\"9000.0.0-{other_phase}5.post7.dev5\", {}) == f\"9000.0.0{normalized_phase}0\"\n\n\nclass TestPost:\n    @pytest.mark.parametrize(\"key\", [\"post\", \"rev\", \"r\"])\n    def test_begin(self, isolation, key):\n        scheme = StandardScheme(str(isolation), {})\n\n        assert scheme.update(key, \"9000.0.0-rc.3.dev5\", {}) == \"9000.0.0rc3.post0\"\n\n    @pytest.mark.parametrize(\"key\", [\"post\", \"rev\", \"r\"])\n    def test_continue(self, isolation, key):\n        scheme = StandardScheme(str(isolation), {})\n\n        assert scheme.update(key, f\"9000.0.0-rc.3-{key}7.dev5\", {}) == \"9000.0.0rc3.post8\"\n\n\nclass TestDev:\n    def test_begin(self, isolation):\n        scheme = StandardScheme(str(isolation), {})\n\n        assert scheme.update(\"dev\", \"9000.0.0-rc.3-7\", {}) == \"9000.0.0rc3.post7.dev0\"\n\n    def test_continue(self, isolation):\n        scheme = StandardScheme(str(isolation), {})\n\n        assert scheme.update(\"dev\", \"9000.0.0-rc.3-7.dev5\", {}) == \"9000.0.0rc3.post7.dev6\"\n\n\nclass TestMultiple:\n    def test_explicit_error(self, isolation):\n        scheme = StandardScheme(str(isolation), {})\n\n        with pytest.raises(ValueError, match=\"Cannot specify multiple update operations with an explicit version\"):\n            scheme.update(\"5,rc\", \"3\", {})\n\n    @pytest.mark.parametrize(\n        (\"operations\", \"expected\"),\n        [\n            (\"fix,rc\", \"0.0.2rc0\"),\n            (\"minor,dev\", \"0.1.0.dev0\"),\n            (\"minor,preview\", \"0.1.0rc0\"),\n            (\"major,beta\", \"1.0.0b0\"),\n            (\"major,major,major\", \"3.0.0\"),\n        ],\n    )\n    def test_correct(self, isolation, operations, expected):\n        scheme = StandardScheme(str(isolation), {})\n\n        assert scheme.update(operations, \"0.0.1\", {}) == expected\n\n\nclass TestWithEpoch:\n    @pytest.mark.parametrize(\n        (\"operations\", \"expected\"),\n        [\n            (\"patch,dev,release\", \"1!0.0.2\"),\n            (\"fix,rc\", \"1!0.0.2rc0\"),\n            (\"minor,dev\", \"1!0.1.0.dev0\"),\n            (\"minor,preview\", \"1!0.1.0rc0\"),\n            (\"major,beta\", \"1!1.0.0b0\"),\n            (\"major,major,major\", \"1!3.0.0\"),\n        ],\n    )\n    def test_correct(self, isolation, operations, expected):\n        scheme = StandardScheme(str(isolation), {})\n\n        assert scheme.update(operations, \"1!0.0.1\", {}) == expected\n"
  },
  {
    "path": "tests/backend/version/source/__init__.py",
    "content": ""
  },
  {
    "path": "tests/backend/version/source/test_code.py",
    "content": "import pytest\n\nfrom hatchling.version.source.code import CodeSource\n\n\ndef test_no_path(isolation):\n    source = CodeSource(str(isolation), {})\n\n    with pytest.raises(ValueError, match=\"option `path` must be specified\"):\n        source.get_version_data()\n\n\ndef test_path_not_string(isolation):\n    source = CodeSource(str(isolation), {\"path\": 1})\n\n    with pytest.raises(TypeError, match=\"option `path` must be a string\"):\n        source.get_version_data()\n\n\ndef test_path_nonexistent(isolation):\n    source = CodeSource(str(isolation), {\"path\": \"a/b.py\"})\n\n    with pytest.raises(OSError, match=\"file does not exist: a/b.py\"):\n        source.get_version_data()\n\n\ndef test_expression_not_string(temp_dir):\n    source = CodeSource(str(temp_dir), {\"path\": \"a/b.py\", \"expression\": 23})\n\n    file_path = temp_dir / \"a\" / \"b.py\"\n    file_path.ensure_parent_dir_exists()\n    file_path.touch()\n\n    with pytest.raises(TypeError, match=\"option `expression` must be a string\"):\n        source.get_version_data()\n\n\ndef test_search_paths_not_array(temp_dir):\n    source = CodeSource(str(temp_dir), {\"path\": \"a/b.py\", \"search-paths\": 23})\n\n    file_path = temp_dir / \"a\" / \"b.py\"\n    file_path.ensure_parent_dir_exists()\n    file_path.touch()\n\n    with pytest.raises(TypeError, match=\"option `search-paths` must be an array\"):\n        source.get_version_data()\n\n\ndef test_search_paths_entry_not_string(temp_dir):\n    source = CodeSource(str(temp_dir), {\"path\": \"a/b.py\", \"search-paths\": [23]})\n\n    file_path = temp_dir / \"a\" / \"b.py\"\n    file_path.ensure_parent_dir_exists()\n    file_path.touch()\n\n    with pytest.raises(TypeError, match=\"entry #1 of option `search-paths` must be a string\"):\n        source.get_version_data()\n\n\ndef test_match_default_expression(temp_dir):\n    source = CodeSource(str(temp_dir), {\"path\": \"a/b.py\"})\n\n    file_path = temp_dir / \"a\" / \"b.py\"\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text('__version__ = \"0.0.1\"')\n\n    with temp_dir.as_cwd():\n        assert source.get_version_data()[\"version\"] == \"0.0.1\"\n\n\ndef test_match_custom_expression_basic(temp_dir):\n    source = CodeSource(str(temp_dir), {\"path\": \"a/b.py\", \"expression\": \"VER\"})\n\n    file_path = temp_dir / \"a\" / \"b.py\"\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text('VER = \"0.0.1\"')\n\n    with temp_dir.as_cwd():\n        assert source.get_version_data()[\"version\"] == \"0.0.1\"\n\n\ndef test_match_custom_expression_complex(temp_dir, helpers):\n    source = CodeSource(str(temp_dir), {\"path\": \"a/b.py\", \"expression\": \"foo()\"})\n\n    file_path = temp_dir / \"a\" / \"b.py\"\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            __version_info__ = (1, 0, 0, 1, 'dev0')\n\n            def foo():\n                return '.'.join(str(part) for part in __version_info__)\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        assert source.get_version_data()[\"version\"] == \"1.0.0.1.dev0\"\n\n\ndef test_search_paths(temp_dir, helpers):\n    source = CodeSource(str(temp_dir), {\"path\": \"a/b.py\", \"search-paths\": [\".\"]})\n\n    parent_dir = temp_dir / \"a\"\n    parent_dir.mkdir()\n    (parent_dir / \"__init__.py\").touch()\n    (parent_dir / \"b.py\").write_text(\n        helpers.dedent(\n            \"\"\"\n            from a.c import foo\n\n            __version__ = foo((1, 0, 0, 1, 'dev0'))\n            \"\"\"\n        )\n    )\n    (parent_dir / \"c.py\").write_text(\n        helpers.dedent(\n            \"\"\"\n            def foo(version_info):\n                return '.'.join(str(part) for part in version_info)\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        assert source.get_version_data()[\"version\"] == \"1.0.0.1.dev0\"\n"
  },
  {
    "path": "tests/backend/version/source/test_env.py",
    "content": "import pytest\n\nfrom hatch.utils.structures import EnvVars\nfrom hatchling.version.source.env import EnvSource\n\n\ndef test_no_variable(isolation):\n    source = EnvSource(str(isolation), {})\n\n    with pytest.raises(ValueError, match=\"option `variable` must be specified\"):\n        source.get_version_data()\n\n\ndef test_variable_not_string(isolation):\n    source = EnvSource(str(isolation), {\"variable\": 1})\n\n    with pytest.raises(TypeError, match=\"option `variable` must be a string\"):\n        source.get_version_data()\n\n\ndef test_variable_not_available(isolation):\n    source = EnvSource(str(isolation), {\"variable\": \"ENV_VERSION\"})\n\n    with (\n        EnvVars(exclude=[\"ENV_VERSION\"]),\n        pytest.raises(RuntimeError, match=\"environment variable `ENV_VERSION` is not set\"),\n    ):\n        source.get_version_data()\n\n\ndef test_variable_contains_version(isolation):\n    source = EnvSource(str(isolation), {\"variable\": \"ENV_VERSION\"})\n\n    with EnvVars({\"ENV_VERSION\": \"0.0.1\"}):\n        assert source.get_version_data()[\"version\"] == \"0.0.1\"\n"
  },
  {
    "path": "tests/backend/version/source/test_regex.py",
    "content": "from itertools import product\n\nimport pytest\n\nfrom hatchling.version.source.regex import RegexSource\n\nDEFAULT_PATTERN_PRODUCTS = list(product((\"__version__\", \"VERSION\", \"version\"), ('\"', \"'\"), (\"\", \"v\")))\n\n\ndef test_no_path(isolation):\n    source = RegexSource(str(isolation), {})\n\n    with pytest.raises(ValueError, match=\"option `path` must be specified\"):\n        source.get_version_data()\n\n\ndef test_path_not_string(isolation):\n    source = RegexSource(str(isolation), {\"path\": 1})\n\n    with pytest.raises(TypeError, match=\"option `path` must be a string\"):\n        source.get_version_data()\n\n\ndef test_path_nonexistent(isolation):\n    source = RegexSource(str(isolation), {\"path\": \"a/b\"})\n\n    with pytest.raises(OSError, match=\"file does not exist: a/b\"):\n        source.get_version_data()\n\n\ndef test_pattern_not_string(temp_dir):\n    source = RegexSource(str(temp_dir), {\"path\": \"a/b\", \"pattern\": 23})\n\n    file_path = temp_dir / \"a\" / \"b\"\n    file_path.ensure_parent_dir_exists()\n    file_path.touch()\n\n    with pytest.raises(TypeError, match=\"option `pattern` must be a string\"):\n        source.get_version_data()\n\n\ndef test_no_version(temp_dir):\n    source = RegexSource(str(temp_dir), {\"path\": \"a/b\"})\n\n    file_path = temp_dir / \"a\" / \"b\"\n    file_path.ensure_parent_dir_exists()\n    file_path.touch()\n\n    with temp_dir.as_cwd(), pytest.raises(ValueError, match=\"unable to parse the version from the file: a/b\"):\n        source.get_version_data()\n\n\ndef test_pattern_no_version_group(temp_dir):\n    source = RegexSource(str(temp_dir), {\"path\": \"a/b\", \"pattern\": \".+\"})\n\n    file_path = temp_dir / \"a\" / \"b\"\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\"foo\")\n\n    with temp_dir.as_cwd(), pytest.raises(ValueError, match=\"no group named `version` was defined in the pattern\"):\n        source.get_version_data()\n\n\ndef test_match_custom_pattern(temp_dir):\n    source = RegexSource(str(temp_dir), {\"path\": \"a/b\", \"pattern\": 'VER = \"(?P<version>.+)\"'})\n\n    file_path = temp_dir / \"a\" / \"b\"\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text('VER = \"0.0.1\"')\n\n    with temp_dir.as_cwd():\n        assert source.get_version_data()[\"version\"] == \"0.0.1\"\n\n\n@pytest.mark.parametrize((\"variable\", \"quote\", \"prefix\"), DEFAULT_PATTERN_PRODUCTS)\ndef test_match_default_pattern(temp_dir, helpers, variable, quote, prefix):\n    source = RegexSource(str(temp_dir), {\"path\": \"a/b\"})\n\n    file_path = temp_dir / \"a\" / \"b\"\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            f\"\"\"\n            __all__ = [{quote}{variable}{quote}, {quote}foo{quote}]\n\n            {variable} = {quote}{prefix}0.0.1{quote}\n\n            def foo():\n                return {quote}bar{quote}\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        assert source.get_version_data()[\"version\"] == \"0.0.1\"\n\n\n@pytest.mark.parametrize((\"variable\", \"quote\", \"prefix\"), DEFAULT_PATTERN_PRODUCTS)\ndef test_set_default_pattern(temp_dir, helpers, variable, quote, prefix):\n    source = RegexSource(str(temp_dir), {\"path\": \"a/b\"})\n\n    file_path = temp_dir / \"a\" / \"b\"\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            f\"\"\"\n            __all__ = [{quote}{variable}{quote}, {quote}foo{quote}]\n\n            {variable} = {quote}{prefix}0.0.1{quote}\n\n            def foo():\n                return {quote}bar{quote}\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        source.set_version(\"foo\", source.get_version_data())\n        assert source.get_version_data()[\"version\"] == \"foo\"\n"
  },
  {
    "path": "tests/cli/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/build/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/build/test_build.py",
    "content": "import os\nimport re\n\nimport pytest\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.project.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE, BuildEnvVars\nfrom hatch.project.core import Project\n\npytestmark = [pytest.mark.usefixtures(\"mock_backend_process\")]\n\n\n@pytest.mark.requires_internet\nclass TestOtherBackend:\n    def test_standard(self, hatch, temp_dir, helpers):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n            assert result.exit_code == 0, result.output\n\n        path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(path)\n        config = dict(project.raw_config)\n        config[\"build-system\"][\"requires\"] = [\"flit-core\"]\n        config[\"build-system\"][\"build-backend\"] = \"flit_core.buildapi\"\n        config[\"project\"][\"version\"] = \"0.0.1\"\n        config[\"project\"][\"dynamic\"] = []\n        del config[\"project\"][\"license\"]\n        project.save_config(config)\n\n        build_directory = path / \"dist\"\n        assert not build_directory.is_dir()\n\n        with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"build\")\n            assert result.exit_code == 0, result.output\n\n        assert build_directory.is_dir()\n\n        artifacts = list(build_directory.iterdir())\n        assert len(artifacts) == 2\n\n        wheel_path = build_directory / \"my_app-0.0.1-py3-none-any.whl\"\n        assert wheel_path.is_file()\n\n        sdist_path = build_directory / \"my_app-0.0.1.tar.gz\"\n        assert sdist_path.is_file()\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Creating environment: hatch-build\n            Checking dependencies\n            Syncing dependencies\n            Inspecting build dependencies\n            ──────────────────────────────────── sdist ─────────────────────────────────────\n            {sdist_path.relative_to(path)}\n            ──────────────────────────────────── wheel ─────────────────────────────────────\n            {wheel_path.relative_to(path)}\n            \"\"\"\n        )\n\n        build_directory.remove()\n        with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"build\", \"-t\", \"wheel\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Inspecting build dependencies\n            ──────────────────────────────────── wheel ─────────────────────────────────────\n            {wheel_path.relative_to(path)}\n            \"\"\"\n        )\n\n        assert build_directory.is_dir()\n        assert wheel_path.is_file()\n        assert not sdist_path.is_file()\n\n        build_directory.remove()\n        with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"build\", \"-t\", \"sdist\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Inspecting build dependencies\n            ──────────────────────────────────── sdist ─────────────────────────────────────\n            {sdist_path.relative_to(path)}\n            \"\"\"\n        )\n\n        assert build_directory.is_dir()\n        assert not wheel_path.is_file()\n        assert sdist_path.is_file()\n\n    def test_legacy(self, hatch, temp_dir, helpers):\n        path = temp_dir / \"tmp\"\n        path.mkdir()\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        (path / \"pyproject.toml\").write_text(\n            \"\"\"\\\n[build-system]\nrequires = [\"setuptools\"]\nbuild-backend = \"setuptools.build_meta\"\n\"\"\"\n        )\n        (path / \"setup.py\").write_text(\n            \"\"\"\\\nimport setuptools\nsetuptools.setup(name=\"tmp\", version=\"0.0.1\")\n\"\"\"\n        )\n        (path / \"tmp.py\").write_text(\n            \"\"\"\\\nprint(\"Hello World!\")\n\"\"\"\n        )\n        (path / \"README.md\").touch()\n\n        build_directory = path / \"dist\"\n        assert not build_directory.is_dir()\n\n        with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"build\")\n            assert result.exit_code == 0, result.output\n\n        assert build_directory.is_dir()\n\n        artifacts = list(build_directory.iterdir())\n        assert len(artifacts) == 2\n\n        wheel_path = build_directory / \"tmp-0.0.1-py3-none-any.whl\"\n        assert wheel_path.is_file()\n\n        sdist_path = build_directory / \"tmp-0.0.1.tar.gz\"\n        assert sdist_path.is_file()\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Creating environment: hatch-build\n            Checking dependencies\n            Syncing dependencies\n            Inspecting build dependencies\n            ──────────────────────────────────── sdist ─────────────────────────────────────\n            {sdist_path.relative_to(path)}\n            ──────────────────────────────────── wheel ─────────────────────────────────────\n            {wheel_path.relative_to(path)}\n            \"\"\"\n        )\n\n\n@pytest.mark.allow_backend_process\ndef test_incompatible_environment(hatch, temp_dir, helpers, build_env_config):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    project = Project(path)\n    helpers.update_project_environment(project, \"hatch-build\", {\"python\": \"9000\", **build_env_config})\n\n    with path.as_cwd():\n        result = hatch(\"build\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `hatch-build` is incompatible: cannot locate Python: 9000\n        \"\"\"\n    )\n\n\n@pytest.mark.allow_backend_process\n@pytest.mark.requires_internet\ndef test_no_compatibility_check_if_exists(hatch, temp_dir, helpers, mocker):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = project_path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        \"\"\"\n    )\n\n    build_directory.remove()\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment.check_compatibility\", side_effect=Exception(\"incompatible\"))\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        \"\"\"\n    )\n\n\ndef test_unknown_targets(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"build\", \"-t\", \"foo\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ───────────────────────────────────── foo ──────────────────────────────────────\n        Unknown build targets: foo\n        \"\"\"\n    )\n\n\ndef test_mutually_exclusive_hook_options(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"build\", \"--hooks-only\", \"--no-hooks\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        Cannot use both --hooks-only and --no-hooks together\n        \"\"\"\n    )\n\n\ndef test_default(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".tar.gz\"))\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        {sdist_path.relative_to(path)}\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path.relative_to(path)}\n        \"\"\"\n    )\n\n\ndef test_explicit_targets(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"build\", \"-t\", \"wheel\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 1\n\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path.relative_to(path)}\n        \"\"\"\n    )\n\n\ndef test_explicit_directory(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    build_directory = temp_dir / \"dist\"\n\n    with path.as_cwd():\n        result = hatch(\"build\", str(build_directory))\n\n    assert result.exit_code == 0, result.output\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".tar.gz\"))\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        {sdist_path}\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path}\n        \"\"\"\n    )\n\n\ndef test_explicit_directory_env_var(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    build_directory = temp_dir / \"dist\"\n\n    with path.as_cwd({BuildEnvVars.LOCATION: str(build_directory)}):\n        result = hatch(\"build\")\n\n    assert result.exit_code == 0, result.output\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".tar.gz\"))\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        {sdist_path}\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path}\n        \"\"\"\n    )\n\n\ndef test_clean(hatch, temp_dir, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def clean(self, versions):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').unlink()\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"version\", \"minor\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n    assert (path / \"my_app\" / \"lib.so\").is_file()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 4\n\n    test_file = build_directory / \"test.txt\"\n    test_file.touch()\n\n    with path.as_cwd():\n        result = hatch(\"version\", \"9000\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\", \"-c\")\n        assert result.exit_code == 0, result.output\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 3\n    assert test_file in artifacts\n\n    sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".tar.gz\"))\n    assert \"9000\" in str(sdist_path)\n\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n    assert \"9000\" in str(wheel_path)\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        {sdist_path.relative_to(path)}\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path.relative_to(path)}\n        \"\"\"\n    )\n\n\ndef test_clean_env_var(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"version\", \"minor\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 4\n\n    test_file = build_directory / \"test.txt\"\n    test_file.touch()\n\n    with path.as_cwd({BuildEnvVars.CLEAN: \"true\"}):\n        result = hatch(\"version\", \"9000\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 3\n    assert test_file in artifacts\n\n    sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".tar.gz\"))\n    assert \"9000\" in str(sdist_path)\n\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n    assert \"9000\" in str(wheel_path)\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        {sdist_path.relative_to(path)}\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path.relative_to(path)}\n        \"\"\"\n    )\n\n\ndef test_clean_only(hatch, temp_dir, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def clean(self, versions):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').unlink()\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n    build_artifact = path / \"my_app\" / \"lib.so\"\n    assert build_artifact.is_file()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    with path.as_cwd():\n        result = hatch(\"version\", \"minor\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\", \"--clean-only\")\n        assert result.exit_code == 0, result.output\n\n    artifacts = list(build_directory.iterdir())\n    assert not artifacts\n    assert not build_artifact.exists()\n\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Inspecting build dependencies\n        \"\"\"\n    )\n\n\ndef test_clean_only_hooks_only(hatch, temp_dir, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def clean(self, versions):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').unlink()\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n    build_artifact = path / \"my_app\" / \"lib.so\"\n    assert build_artifact.is_file()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    with path.as_cwd():\n        result = hatch(\"version\", \"minor\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\", \"--clean-only\", \"--hooks-only\")\n        assert result.exit_code == 0, result.output\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n    assert not build_artifact.exists()\n\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Inspecting build dependencies\n        \"\"\"\n    )\n\n\ndef test_clean_hooks_after(hatch, temp_dir, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def clean(self, versions):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').unlink()\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"build\", \"--clean-hooks-after\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n    build_artifact = path / \"my_app\" / \"lib.so\"\n    assert not build_artifact.exists()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".tar.gz\"))\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        {sdist_path.relative_to(path)}\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path.relative_to(path)}\n        \"\"\"\n    )\n\n\ndef test_clean_hooks_after_env_var(hatch, temp_dir, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def clean(self, versions):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').unlink()\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd({BuildEnvVars.CLEAN_HOOKS_AFTER: \"true\"}):\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n    build_artifact = path / \"my_app\" / \"lib.so\"\n    assert not build_artifact.exists()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".tar.gz\"))\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        {sdist_path.relative_to(path)}\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path.relative_to(path)}\n        \"\"\"\n    )\n\n\ndef test_clean_only_no_hooks(hatch, temp_dir, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def clean(self, versions):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').unlink()\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n    build_artifact = path / \"my_app\" / \"lib.so\"\n    assert build_artifact.is_file()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    with path.as_cwd():\n        result = hatch(\"version\", \"minor\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\", \"--clean-only\", \"--no-hooks\")\n        assert result.exit_code == 0, result.output\n\n    artifacts = list(build_directory.iterdir())\n    assert not artifacts\n    assert build_artifact.is_file()\n\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Inspecting build dependencies\n        \"\"\"\n    )\n\n\ndef test_hooks_only(hatch, temp_dir, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"-v\", \"build\", \"-t\", \"wheel\", \"--hooks-only\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 0\n    assert (path / \"my_app\" / \"lib.so\").is_file()\n\n    helpers.assert_output_match(\n        result.output,\n        r\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        cmd \\[1\\] \\| python -u .+\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        cmd \\[1\\] \\| python -u -m hatchling build --target wheel --hooks-only\n        Building `wheel` version `standard`\n        Only ran build hooks for `wheel` version `standard`\n        \"\"\",\n    )\n\n\ndef test_hooks_only_env_var(hatch, temp_dir, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd({BuildEnvVars.HOOKS_ONLY: \"true\"}):\n        result = hatch(\"-v\", \"build\", \"-t\", \"wheel\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 0\n    assert (path / \"my_app\" / \"lib.so\").is_file()\n\n    helpers.assert_output_match(\n        result.output,\n        r\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        cmd \\[1\\] \\| python -u .+\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        cmd \\[1\\] \\| python -u -m hatchling build --target wheel --hooks-only\n        Building `wheel` version `standard`\n        Only ran build hooks for `wheel` version `standard`\n        \"\"\",\n    )\n\n\ndef test_extensions_only(hatch, temp_dir, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"-v\", \"build\", \"--ext\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 0\n    assert (path / \"my_app\" / \"lib.so\").is_file()\n\n    helpers.assert_output_match(\n        result.output,\n        r\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        cmd \\[1\\] \\| python -u .+\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        cmd \\[1\\] \\| python -u -m hatchling build --target wheel --hooks-only\n        Building `wheel` version `standard`\n        Only ran build hooks for `wheel` version `standard`\n        \"\"\",\n    )\n\n\ndef test_no_hooks(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"build\", \"-t\", \"wheel\", \"--no-hooks\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 1\n    assert not (path / \"my_app\" / \"lib.so\").exists()\n\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path.relative_to(path)}\n        \"\"\"\n    )\n\n\ndef test_no_hooks_env_var(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd({BuildEnvVars.NO_HOOKS: \"true\"}):\n        result = hatch(\"build\", \"-t\", \"wheel\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 1\n    assert not (path / \"my_app\" / \"lib.so\").exists()\n\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path.relative_to(path)}\n        \"\"\"\n    )\n\n\ndef test_debug_verbosity(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"-v\", \"build\", \"-t\", \"wheel:standard\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 1\n\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    helpers.assert_output_match(\n        result.output,\n        rf\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        cmd \\[1\\] \\| python -u -m hatchling build --target wheel:standard\n        Building `wheel` version `standard`\n        {re.escape(str(wheel_path.relative_to(path)))}\n        \"\"\",\n    )\n\n\n@pytest.mark.allow_backend_process\n@pytest.mark.requires_internet\ndef test_shipped(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"hatch-build\"\n\n    build_directory = project_path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        \"\"\"\n    )\n\n    # Test removal while we're here\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"remove\", \"hatch-build\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Removing environment: hatch-build\n        \"\"\"\n    )\n\n    assert not storage_path.is_dir()\n\n\n@pytest.mark.allow_backend_process\n@pytest.mark.requires_internet\ndef test_build_dependencies(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    build_script = project_path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            import binary\n            from hatchling.builders.wheel import WheelBuilder\n\n            def get_builder():\n                return CustomWheelBuilder\n\n            class CustomWheelBuilder(WheelBuilder):\n                def build(self, **kwargs):\n                    pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))\n                    yield from super().build(**kwargs)\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\n        \"targets\": {\"custom\": {\"dependencies\": [\"binary\"], \"path\": DEFAULT_BUILD_SCRIPT}},\n    }\n    project.save_config(config)\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"build\", \"-t\", \"custom\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = project_path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 1\n\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    assert str(output_file.read_text()) == \"(1.0, 'KiB')\"\n\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        Syncing dependencies\n        ──────────────────────────────────── custom ────────────────────────────────────\n        \"\"\"\n    )\n\n\ndef test_plugin_dependencies_unmet(hatch, temp_dir, helpers, mock_plugin_installation):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    dependency = os.urandom(16).hex()\n    (path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    with path.as_cwd():\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".tar.gz\"))\n    wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith(\".whl\"))\n\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Syncing environment plugin requirements\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        ──────────────────────────────────── sdist ─────────────────────────────────────\n        {sdist_path.relative_to(path)}\n        ──────────────────────────────────── wheel ─────────────────────────────────────\n        {wheel_path.relative_to(path)}\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n"
  },
  {
    "path": "tests/cli/clean/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/clean/test_clean.py",
    "content": "import os\n\nimport pytest\n\nfrom hatch.project.core import Project\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE\n\npytestmark = [pytest.mark.usefixtures(\"mock_backend_process\")]\n\n\ndef test(hatch, temp_dir, helpers, config_file, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    build_script = path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            import pathlib\n\n            from hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\n            class CustomHook(BuildHookInterface):\n                def clean(self, versions):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').unlink()\n                def initialize(self, version, build_data):\n                    if self.target_name == 'wheel':\n                        pathlib.Path('my_app', 'lib.so').touch()\n            \"\"\"\n        )\n    )\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"hooks\": {\"custom\": {\"path\": build_script.name}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n    build_directory = path / \"dist\"\n    assert build_directory.is_dir()\n    build_artifact = path / \"my_app\" / \"lib.so\"\n    assert build_artifact.is_file()\n\n    artifacts = list(build_directory.iterdir())\n    assert len(artifacts) == 2\n\n    dependency = os.urandom(16).hex()\n    (path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    with path.as_cwd():\n        result = hatch(\"version\", \"minor\")\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"clean\")\n        assert result.exit_code == 0, result.output\n\n    artifacts = list(build_directory.iterdir())\n    assert not artifacts\n    assert not build_artifact.exists()\n\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        Inspecting build dependencies\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency], count=2)\n"
  },
  {
    "path": "tests/cli/config/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/config/test_explore.py",
    "content": "def test_call(hatch, config_file, mocker):\n    mock = mocker.patch(\"click.launch\")\n    result = hatch(\"config\", \"explore\")\n\n    assert result.exit_code == 0, result.output\n    mock.assert_called_once_with(str(config_file.path), locate=True)\n"
  },
  {
    "path": "tests/cli/config/test_find.py",
    "content": "def test(hatch, config_file, helpers):\n    result = hatch(\"config\", \"find\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {config_file.path}\n        \"\"\"\n    )\n"
  },
  {
    "path": "tests/cli/config/test_restore.py",
    "content": "def test_standard(hatch, config_file):\n    config_file.model.project = \"foo\"\n    config_file.save()\n\n    result = hatch(\"config\", \"restore\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == \"Settings were successfully restored.\\n\"\n\n    config_file.load()\n    assert config_file.model.project == \"\"\n\n\ndef test_allow_invalid_config(hatch, config_file, helpers):\n    config_file.model.project = [\"foo\"]\n    config_file.save()\n\n    result = hatch(\"config\", \"restore\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Settings were successfully restored.\n        \"\"\"\n    )\n"
  },
  {
    "path": "tests/cli/config/test_set.py",
    "content": "def test_standard(hatch, config_file, helpers):\n    result = hatch(\"config\", \"set\", \"project\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        New setting:\n        project = \"foo\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.project == \"foo\"\n\n\ndef test_standard_deep(hatch, config_file, helpers):\n    result = hatch(\"config\", \"set\", \"template.name\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        New setting:\n        [template]\n        name = \"foo\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.template.name == \"foo\"\n\n\ndef test_standard_complex_sequence(hatch, config_file, helpers):\n    result = hatch(\"config\", \"set\", \"dirs.project\", \"['/foo', '/bar']\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        New setting:\n        [dirs]\n        project = [\"/foo\", \"/bar\"]\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.dirs.project == [\"/foo\", \"/bar\"]\n\n\ndef test_standard_complex_map(hatch, config_file, helpers):\n    result = hatch(\"config\", \"set\", \"projects\", \"{'a': '/foo', 'b': '/bar'}\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        New setting:\n        [projects]\n        a = \"/foo\"\n        b = \"/bar\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.projects[\"a\"].location == \"/foo\"\n    assert config_file.model.projects[\"b\"].location == \"/bar\"\n\n\ndef test_standard_hidden(hatch, config_file, helpers):\n    result = hatch(\"config\", \"set\", \"publish.index.auth\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        New setting:\n        [publish.index]\n        auth = \"<...>\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.publish[\"index\"][\"auth\"] == \"foo\"\n\n\ndef test_prompt(hatch, config_file, helpers):\n    result = hatch(\"config\", \"set\", \"project\", input=\"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Value for `project`: foo\n        New setting:\n        project = \"foo\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.project == \"foo\"\n\n\ndef test_prompt_hidden(hatch, config_file, helpers):\n    result = hatch(\"config\", \"set\", \"publish.index.auth\", input=\"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Value for `publish.index.auth`:{\" \"}\n        New setting:\n        [publish.index]\n        auth = \"<...>\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.publish[\"index\"][\"auth\"] == \"foo\"\n\n\ndef test_prevent_invalid_config(hatch, config_file, helpers):\n    original_mode = config_file.model.mode\n    result = hatch(\"config\", \"set\", \"mode\", \"foo\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Error parsing config:\n        mode\n          must be one of: aware, local, project\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.mode == original_mode\n\n\ndef test_resolve_project_location_basic(hatch, config_file, helpers, temp_dir):\n    config_file.model.project = \"foo\"\n    config_file.save()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"config\", \"set\", \"projects.foo\", \".\")\n\n    path = str(temp_dir).replace(\"\\\\\", \"\\\\\\\\\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        New setting:\n        [projects]\n        foo = \"{path}\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.projects[\"foo\"].location == str(temp_dir)\n\n\ndef test_resolve_project_location_complex(hatch, config_file, helpers, temp_dir):\n    config_file.model.project = \"foo\"\n    config_file.save()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"config\", \"set\", \"projects.foo.location\", \".\")\n\n    path = str(temp_dir).replace(\"\\\\\", \"\\\\\\\\\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        New setting:\n        [projects.foo]\n        location = \"{path}\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.projects[\"foo\"].location == str(temp_dir)\n\n\ndef test_project_location_basic_set_first_project(hatch, config_file, helpers, temp_dir):\n    with temp_dir.as_cwd():\n        result = hatch(\"config\", \"set\", \"projects.foo\", \".\")\n\n    path = str(temp_dir).replace(\"\\\\\", \"\\\\\\\\\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        New setting:\n        project = \"foo\"\n\n        [projects]\n        foo = \"{path}\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.project == \"foo\"\n    assert config_file.model.projects[\"foo\"].location == str(temp_dir)\n\n\ndef test_project_location_complex_set_first_project(hatch, config_file, helpers, temp_dir):\n    with temp_dir.as_cwd():\n        result = hatch(\"config\", \"set\", \"projects.foo.location\", \".\")\n\n    path = str(temp_dir).replace(\"\\\\\", \"\\\\\\\\\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        New setting:\n        project = \"foo\"\n\n        [projects.foo]\n        location = \"{path}\"\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.project == \"foo\"\n    assert config_file.model.projects[\"foo\"].location == str(temp_dir)\n\n\ndef test_booleans(hatch, config_file, helpers, temp_dir):\n    assert config_file.model.template.licenses.headers is True\n\n    with temp_dir.as_cwd():\n        result = hatch(\"config\", \"set\", \"template.licenses.headers\", \"false\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        New setting:\n        [template.licenses]\n        headers = false\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.template.licenses.headers is False\n\n    with temp_dir.as_cwd():\n        result = hatch(\"config\", \"set\", \"template.licenses.headers\", \"TruE\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        New setting:\n        [template.licenses]\n        headers = true\n        \"\"\"\n    )\n\n    config_file.load()\n    assert config_file.model.template.licenses.headers is True\n"
  },
  {
    "path": "tests/cli/config/test_show.py",
    "content": "def test_default_scrubbed(hatch, config_file, helpers, default_cache_dir, default_data_dir):\n    config_file.model.project = \"foo\"\n    config_file.model.publish[\"index\"][\"auth\"] = \"bar\"\n    config_file.save()\n\n    result = hatch(\"config\", \"show\")\n\n    default_cache_directory = str(default_cache_dir).replace(\"\\\\\", \"\\\\\\\\\")\n    default_data_directory = str(default_data_dir).replace(\"\\\\\", \"\\\\\\\\\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        mode = \"local\"\n        project = \"foo\"\n        shell = \"\"\n\n        [dirs]\n        project = []\n        python = \"isolated\"\n        data = \"{default_data_directory}\"\n        cache = \"{default_cache_directory}\"\n\n        [dirs.env]\n\n        [projects]\n\n        [template]\n        name = \"Foo Bar\"\n        email = \"foo@bar.baz\"\n\n        [template.licenses]\n        headers = true\n        default = [\n            \"MIT\",\n        ]\n\n        [template.plugins.default]\n        tests = true\n        ci = false\n        src-layout = true\n\n        [terminal.styles]\n        info = \"bold\"\n        success = \"bold cyan\"\n        error = \"bold red\"\n        warning = \"bold yellow\"\n        waiting = \"bold magenta\"\n        debug = \"bold\"\n        spinner = \"simpleDotsScrolling\"\n        \"\"\"\n    )\n\n\ndef test_reveal(hatch, config_file, helpers, default_cache_dir, default_data_dir):\n    config_file.model.project = \"foo\"\n    config_file.model.publish[\"index\"][\"auth\"] = \"bar\"\n    config_file.save()\n\n    result = hatch(\"config\", \"show\", \"-a\")\n\n    default_cache_directory = str(default_cache_dir).replace(\"\\\\\", \"\\\\\\\\\")\n    default_data_directory = str(default_data_dir).replace(\"\\\\\", \"\\\\\\\\\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        mode = \"local\"\n        project = \"foo\"\n        shell = \"\"\n\n        [dirs]\n        project = []\n        python = \"isolated\"\n        data = \"{default_data_directory}\"\n        cache = \"{default_cache_directory}\"\n\n        [dirs.env]\n\n        [projects]\n\n        [publish.index]\n        repo = \"main\"\n        auth = \"bar\"\n\n        [template]\n        name = \"Foo Bar\"\n        email = \"foo@bar.baz\"\n\n        [template.licenses]\n        headers = true\n        default = [\n            \"MIT\",\n        ]\n\n        [template.plugins.default]\n        tests = true\n        ci = false\n        src-layout = true\n\n        [terminal.styles]\n        info = \"bold\"\n        success = \"bold cyan\"\n        error = \"bold red\"\n        warning = \"bold yellow\"\n        waiting = \"bold magenta\"\n        debug = \"bold\"\n        spinner = \"simpleDotsScrolling\"\n        \"\"\"\n    )\n"
  },
  {
    "path": "tests/cli/dep/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/dep/show/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/dep/show/test_requirements.py",
    "content": "import os\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.project.core import Project\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\n\ndef test_incompatible_environment(hatch, temp_dir, helpers, build_env_config):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"].append(\"foo\")\n    config[\"project\"][\"dynamic\"].append(\"dependencies\")\n    project.save_config(config)\n    helpers.update_project_environment(project, \"hatch-build\", {\"python\": \"9000\", **build_env_config})\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"dep\", \"show\", \"requirements\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `hatch-build` is incompatible: cannot locate Python: 9000\n        \"\"\"\n    )\n\n\ndef test_project_only(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"foo-bar-baz\"]\n    project.save_config(config)\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"requirements\", \"-p\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        foo-bar-baz\n        \"\"\"\n    )\n\n\ndef test_environment_only(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"dependencies\": [\"foo-bar-baz\"]})\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"requirements\", \"-e\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        foo-bar-baz\n        \"\"\"\n    )\n\n\ndef test_default_both(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"foo-bar-baz\"]\n    config[\"project\"][\"optional-dependencies\"] = {\n        \"feature1\": [\"bar-baz-foo\"],\n        \"feature2\": [\"bar-foo-baz\"],\n        \"feature3\": [\"foo-baz-bar\"],\n    }\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"dependencies\": [\"baz-bar-foo\"]})\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"requirements\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        baz-bar-foo\n        foo-bar-baz\n        \"\"\"\n    )\n\n\ndef test_unknown_feature(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"requirements\", \"-f\", \"foo\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Feature `foo` is not defined in field `project.optional-dependencies`\n        \"\"\"\n    )\n\n\ndef test_features_only(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"foo-bar-baz\"]\n    config[\"project\"][\"optional-dependencies\"] = {\n        \"feature1\": [\"bar-baz-foo\"],\n        \"feature2\": [\"bar-foo-baz\"],\n        \"feature3\": [\"foo-baz-bar\"],\n        \"feature4\": [\"baz-foo-bar\"],\n    }\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"dependencies\": [\"baz-bar-foo\"]})\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"requirements\", \"-f\", \"feature2\", \"-f\", \"feature1\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        bar-baz-foo\n        bar-foo-baz\n        \"\"\"\n    )\n\n\ndef test_include_features(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"foo-bar-baz\"]\n    config[\"project\"][\"optional-dependencies\"] = {\n        \"feature1\": [\"bar-baz-foo\"],\n        \"feature2\": [\"bar-foo-baz\"],\n        \"feature3\": [\"foo-baz-bar\"],\n        \"feature4\": [\"baz-foo-bar\"],\n    }\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"dependencies\": [\"baz-bar-foo\"]})\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"requirements\", \"--all\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        bar-baz-foo\n        bar-foo-baz\n        baz-bar-foo\n        baz-foo-bar\n        foo-bar-baz\n        foo-baz-bar\n        \"\"\"\n    )\n\n\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir, config_file, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"foo-bar-baz\"]\n    project.save_config(config)\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"requirements\", \"-p\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        foo-bar-baz\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n"
  },
  {
    "path": "tests/cli/dep/show/test_table.py",
    "content": "import os\n\nimport pytest\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.project.core import Project\nfrom hatch.utils.structures import EnvVars\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\n\n@pytest.fixture(scope=\"module\", autouse=True)\ndef _terminal_width():\n    with EnvVars({\"COLUMNS\": \"200\"}):\n        yield\n\n\ndef test_incompatible_environment(hatch, temp_dir, helpers, build_env_config):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"].append(\"foo\")\n    config[\"project\"][\"dynamic\"].append(\"dependencies\")\n    project.save_config(config)\n    helpers.update_project_environment(project, \"hatch-build\", {\"python\": \"9000\", **build_env_config})\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"dep\", \"show\", \"table\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `hatch-build` is incompatible: cannot locate Python: 9000\n        \"\"\"\n    )\n\n\ndef test_project_only(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"foo-bar-baz\"]\n    project.save_config(config)\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"table\", \"--ascii\", \"-p\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Project\n        +-------------+\n        | Name        |\n        +=============+\n        | foo-bar-baz |\n        +-------------+\n        \"\"\"\n    )\n\n\ndef test_environment_only(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"dependencies\": [\"foo-bar-baz\"]})\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"table\", \"--ascii\", \"-e\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Env: default\n        +-------------+\n        | Name        |\n        +=============+\n        | foo-bar-baz |\n        +-------------+\n        \"\"\"\n    )\n\n\ndef test_default_both(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"foo-bar-baz\"]\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"dependencies\": [\"baz-bar-foo\"]})\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"table\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Project\n        +-------------+\n        | Name        |\n        +=============+\n        | foo-bar-baz |\n        +-------------+\n        Env: default\n        +-------------+\n        | Name        |\n        +=============+\n        | baz-bar-foo |\n        +-------------+\n        \"\"\"\n    )\n\n\ndef test_optional_columns(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\n        \"python___dateutil\",\n        \"bAr.Baz[TLS, EdDSA]   >=1.2RC5\",\n        'Foo;python_version<\"3.8\"',\n    ]\n    project.save_config(config)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"dependencies\": [\n                \"proj @ git+https://github.com/org/proj.git@v1\",\n                'bAr.Baz   [TLS, EdDSA]   >=1.2RC5;python_version<\"3.8\"',\n            ],\n        },\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"table\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Project\n        +-----------------+----------+------------------------+------------+\n        | Name            | Versions | Markers                | Features   |\n        +=================+==========+========================+============+\n        | bar-baz         | >=1.2rc5 |                        | eddsa, tls |\n        | foo             |          | python_version < '3.8' |            |\n        | python-dateutil |          |                        |            |\n        +-----------------+----------+------------------------+------------+\n        Env: default\n        +---------+----------------------------------------+----------+------------------------+------------+\n        | Name    | URL                                    | Versions | Markers                | Features   |\n        +=========+========================================+==========+========================+============+\n        | bar-baz |                                        | >=1.2rc5 | python_version < '3.8' | eddsa, tls |\n        | proj    | git+https://github.com/org/proj.git@v1 |          |                        |            |\n        +---------+----------------------------------------+----------+------------------------+------------+\n        \"\"\"\n    )\n\n\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir, config_file, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"foo-bar-baz\"]\n    project.save_config(config)\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"show\", \"table\", \"--ascii\", \"-p\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        Project\n        +-------------+\n        | Name        |\n        +=============+\n        | foo-bar-baz |\n        +-------------+\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n"
  },
  {
    "path": "tests/cli/dep/test_hash.py",
    "content": "import os\nfrom hashlib import sha256\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.project.core import Project\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\n\ndef test_incompatible_environment(hatch, temp_dir, helpers, build_env_config):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"].append(\"foo\")\n    config[\"project\"][\"dynamic\"].append(\"dependencies\")\n    project.save_config(config)\n    helpers.update_project_environment(project, \"hatch-build\", {\"python\": \"9000\", **build_env_config})\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"dep\", \"hash\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `hatch-build` is incompatible: cannot locate Python: 9000\n        \"\"\"\n    )\n\n\ndef test_all(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"Foo\", \"bar[ A, b]\"]\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"dependencies\": [\"bAZ >= 0\"]})\n    expected_hash = sha256(b\"bar[a,b]baz>=0foo\").hexdigest()\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"hash\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {expected_hash}\n        \"\"\"\n    )\n\n\ndef test_project_only(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"Foo\", \"bar[ A, b]\"]\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"dependencies\": [\"bAZ >= 0\"]})\n    expected_hash = sha256(b\"bar[a,b]foo\").hexdigest()\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"hash\", \"-p\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {expected_hash}\n        \"\"\"\n    )\n\n\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir, mock_plugin_installation):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"Foo\", \"bar[ A, b]\"]\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"dependencies\": [\"bAZ >= 0\"]})\n    expected_hash = sha256(b\"bar[a,b]foo\").hexdigest()\n\n    with project_path.as_cwd():\n        result = hatch(\"dep\", \"hash\", \"-p\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Syncing environment plugin requirements\n        {expected_hash}\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n"
  },
  {
    "path": "tests/cli/env/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/env/test_create.py",
    "content": "import os\nimport sys\n\nimport pytest\n\nfrom hatch.config.constants import AppEnvVars, ConfigEnvVars\nfrom hatch.env.utils import get_env_var\nfrom hatch.project.core import Project\nfrom hatch.utils.structures import EnvVars\nfrom hatch.venv.core import UVVirtualEnv, VirtualEnv\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE\nfrom hatchling.utils.fs import path_to_uri\n\n\ndef test_undefined(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `test` is not defined by project config\n        \"\"\"\n    )\n\n\ndef test_unknown_type(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.config.envs[\"default\"])\n    config[\"type\"] = \"foo\"\n    helpers.update_project_environment(project, \"default\", config)\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `test` has unknown type: foo\n        \"\"\"\n    )\n\n\ndef test_new(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"type\": \"virtual\", \"skip-install\": True},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {},\n    }\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n\ndef test_uv_shipped(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"skip-install\": True, \"installer\": \"uv\", **project.config.envs[\"default\"]},\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with (\n        project_path.as_cwd(),\n        EnvVars({ConfigEnvVars.DATA: str(data_path)}, exclude=[get_env_var(plugin_name=\"virtual\", option=\"uv_path\")]),\n    ):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n\n@pytest.mark.requires_internet\ndef test_uv_env(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"skip-install\": True, \"installer\": \"uv\", **project.config.envs[\"default\"]},\n    )\n    helpers.update_project_environment(project, \"hatch-uv\", {\"dependencies\": [\"uv>=0.1.31\"]})\n    helpers.update_project_environment(project, \"test\", {})\n\n    with (\n        project_path.as_cwd(),\n        EnvVars({ConfigEnvVars.DATA: str(data_path)}, exclude=[get_env_var(plugin_name=\"virtual\", option=\"uv_path\")]),\n    ):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Creating environment: hatch-uv\n        Checking dependencies\n        Syncing dependencies\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 2\n\n    assert sorted(p.name for p in env_dirs) == [\"hatch-uv\", \"test\"]\n\n\ndef test_new_selected_python(hatch, helpers, temp_dir, config_file, python_on_path, mocker):\n    mocker.patch(\"sys.executable\")\n\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path), AppEnvVars.PYTHON: python_on_path}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"type\": \"virtual\", \"skip-install\": True},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {},\n    }\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n\ndef test_selected_absolute_directory(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.model.dirs.env = {\"virtual\": \"$VENVS_DIR\"}\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    env_data_path = temp_dir / \".venvs\"\n\n    project = Project(project_path)\n    assert project.config.envs == {\"default\": {\"type\": \"virtual\"}}\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd({\"VENVS_DIR\": str(env_data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"type\": \"virtual\", \"skip-install\": True},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {},\n    }\n\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n\ndef test_option_absolute_directory(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.model.dirs.env = {\"virtual\": \"$VENVS_DIR\"}\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    env_data_path = temp_dir / \".venvs\"\n    env_path = temp_dir / \"foo\"\n\n    project = Project(project_path)\n    assert project.config.envs == {\"default\": {\"type\": \"virtual\"}}\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"path\": str(env_path)})\n\n    with project_path.as_cwd({\"VENVS_DIR\": str(env_data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"type\": \"virtual\", \"skip-install\": True, \"path\": str(env_path)},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"path\": str(env_path)},\n    }\n\n    assert not env_data_path.is_dir()\n    assert env_path.is_dir()\n\n\ndef test_env_var_absolute_directory(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.model.dirs.env = {\"virtual\": \"$VENVS_DIR\"}\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    env_data_path = temp_dir / \".venvs\"\n    env_path = temp_dir / \"foo\"\n    env_path_overridden = temp_dir / \"bar\"\n\n    project = Project(project_path)\n    assert project.config.envs == {\"default\": {\"type\": \"virtual\"}}\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"path\": str(env_path_overridden)})\n\n    with project_path.as_cwd({\"VENVS_DIR\": str(env_data_path), \"HATCH_ENV_TYPE_VIRTUAL_PATH\": str(env_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"type\": \"virtual\", \"skip-install\": True, \"path\": str(env_path_overridden)},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"path\": str(env_path_overridden)},\n    }\n\n    assert not env_data_path.is_dir()\n    assert env_path.is_dir()\n\n\ndef test_selected_local_directory(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.model.dirs.env = {\"virtual\": \"$VENVS_DIR\"}\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd({\"VENVS_DIR\": \".hatch\"}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    with project_path.as_cwd({\"VENVS_DIR\": \".hatch\"}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test.9000\n        Checking dependencies\n        Creating environment: test.42\n        Checking dependencies\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test.9000\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test.42\": {\"type\": \"virtual\", \"skip-install\": True},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]},\n    }\n\n    env_data_path = project_path / \".hatch\"\n    assert env_data_path.is_dir()\n\n    env_dirs = list(env_data_path.iterdir())\n    assert len(env_dirs) == 4\n\n    assert sorted(entry.name for entry in env_dirs) == [\".gitignore\", \"my-app\", \"test.42\", \"test.9000\"]\n\n\ndef test_option_local_directory(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.model.dirs.env = {\"virtual\": \"$VENVS_DIR\"}\n    config_file.save()\n\n    project_name = \"My.App\"\n    env_data_path = temp_dir / \".venvs\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    assert project.config.envs == {\"default\": {\"type\": \"virtual\"}}\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"path\": \".venv\"})\n\n    with project_path.as_cwd({\"VENVS_DIR\": str(env_data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"type\": \"virtual\", \"skip-install\": True, \"path\": \".venv\"},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"path\": \".venv\"},\n    }\n    assert not env_data_path.is_dir()\n    assert (project_path / \".venv\").is_dir()\n\n\ndef test_env_var_local_directory(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.model.dirs.env = {\"virtual\": \"$VENVS_DIR\"}\n    config_file.save()\n\n    project_name = \"My.App\"\n    env_data_path = temp_dir / \".venvs\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    project = Project(project_path)\n    assert project.config.envs == {\"default\": {\"type\": \"virtual\"}}\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"path\": \".foo\"})\n\n    with project_path.as_cwd({\"VENVS_DIR\": str(env_data_path), \"HATCH_ENV_TYPE_VIRTUAL_PATH\": \".venv\"}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"type\": \"virtual\", \"skip-install\": True, \"path\": \".foo\"},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"path\": \".foo\"},\n    }\n    assert not env_data_path.is_dir()\n    assert (project_path / \".venv\").is_dir()\n\n\ndef test_enter_project_directory(hatch, config_file, helpers, temp_dir):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = \"foo\"\n    config_file.model.mode = \"project\"\n    config_file.model.project = project\n    config_file.model.projects = {project: str(project_path)}\n    config_file.save()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {})\n\n    with EnvVars({ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n\ndef test_already_created(hatch, config_file, helpers, temp_dir):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `test` already exists\n        \"\"\"\n    )\n\n\ndef test_default(hatch, config_file, helpers, temp_dir):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n\ndef test_matrix(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test.9000\n        Checking dependencies\n        Creating environment: test.42\n        Checking dependencies\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test.9000\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test.42\": {\"type\": \"virtual\", \"skip-install\": True},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]},\n    }\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = sorted(storage_path.iterdir(), key=lambda d: d.name)\n    assert len(env_dirs) == 2\n\n    assert env_dirs[0].name == \"test.42\"\n    assert env_dirs[1].name == \"test.9000\"\n\n\ndef test_incompatible_single(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"skip-install\": True, \"platforms\": [\"foo\"], **project.config.envs[\"default\"]}\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `test` is incompatible: unsupported platform\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True, \"platforms\": [\"foo\"]},\n        \"test\": {\"type\": \"virtual\", \"skip-install\": True, \"platforms\": [\"foo\"]},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True, \"platforms\": [\"foo\"]},\n        \"test\": {},\n    }\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert not env_data_path.is_dir()\n\n\ndef test_incompatible_matrix_full(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"skip-install\": True, \"platforms\": [\"foo\"], **project.config.envs[\"default\"]}\n    )\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Skipped 2 incompatible environments:\n        test.9000 -> unsupported platform\n        test.42 -> unsupported platform\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True, \"platforms\": [\"foo\"]},\n        \"test.9000\": {\"type\": \"virtual\", \"skip-install\": True, \"platforms\": [\"foo\"]},\n        \"test.42\": {\"type\": \"virtual\", \"skip-install\": True, \"platforms\": [\"foo\"]},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True, \"platforms\": [\"foo\"]},\n        \"test\": {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]},\n    }\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert not env_data_path.is_dir()\n\n\ndef test_incompatible_matrix_partial(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(\n        project,\n        \"test\",\n        {\n            \"matrix\": [{\"version\": [\"9000\", \"42\"]}],\n            \"overrides\": {\"matrix\": {\"version\": {\"platforms\": [{\"value\": \"foo\", \"if\": [\"9000\"]}]}}},\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test.42\n        Checking dependencies\n        Skipped 1 incompatible environment:\n        test.9000 -> unsupported platform\n        \"\"\"\n    )\n\n    project = Project(project_path)\n    assert project.config.envs == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test.9000\": {\"type\": \"virtual\", \"skip-install\": True, \"platforms\": [\"foo\"]},\n        \"test.42\": {\"type\": \"virtual\", \"skip-install\": True},\n    }\n    assert project.raw_config[\"tool\"][\"hatch\"][\"envs\"] == {\n        \"default\": {\"type\": \"virtual\", \"skip-install\": True},\n        \"test\": {\n            \"matrix\": [{\"version\": [\"9000\", \"42\"]}],\n            \"overrides\": {\"matrix\": {\"version\": {\"platforms\": [{\"value\": \"foo\", \"if\": [\"9000\"]}]}}},\n        },\n    }\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    assert env_dirs[0].name == \"test.42\"\n\n\n@pytest.mark.requires_internet\ndef test_install_project_default_dev_mode(\n    hatch, helpers, temp_dir, platform, uv_on_path, extract_installed_requirements\n):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Installing project in development mode\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n    with UVVirtualEnv(env_path, platform):\n        output = platform.run_command([uv_on_path, \"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\n            \"utf-8\"\n        )\n        requirements = extract_installed_requirements(output.splitlines())\n\n        assert len(requirements) == 1\n        assert requirements[0].lower() == f\"-e {project_path.as_uri().lower()}\"\n\n\n@pytest.mark.requires_internet\ndef test_install_project_no_dev_mode(hatch, helpers, temp_dir, platform, uv_on_path, extract_installed_requirements):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"dev-mode\": False, \"extra-dependencies\": [\"binary\"], **project.config.envs[\"default\"]}\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Installing project\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n    with UVVirtualEnv(env_path, platform):\n        output = platform.run_command([uv_on_path, \"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\n            \"utf-8\"\n        )\n        requirements = extract_installed_requirements(output.splitlines())\n\n        assert len(requirements) == 2\n        assert f\"my-app @ {project_path.as_uri().lower()}\" in [req.lower() for req in requirements]\n\n\n@pytest.mark.requires_internet\ndef test_pre_install_commands(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"pre-install-commands\": [\"python -c \\\"with open('test.txt', 'w') as f: f.write('content')\\\"\"],\n            **project.config.envs[\"default\"],\n        },\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Running pre-installation commands\n        Installing project in development mode\n        Checking dependencies\n        \"\"\"\n    )\n    assert (project_path / \"test.txt\").is_file()\n\n\ndef test_pre_install_commands_error(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"pre-install-commands\": ['python -c \"import sys;sys.exit(7)\"'], **project.config.envs[\"default\"]},\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 7\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Running pre-installation commands\n        Failed with exit code: 7\n        \"\"\"\n    )\n\n\n@pytest.mark.requires_internet\ndef test_post_install_commands(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"post-install-commands\": [\"python -c \\\"with open('test.txt', 'w') as f: f.write('content')\\\"\"],\n            **project.config.envs[\"default\"],\n        },\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Installing project in development mode\n        Running post-installation commands\n        Checking dependencies\n        \"\"\"\n    )\n    assert (project_path / \"test.txt\").is_file()\n\n\n@pytest.mark.requires_internet\ndef test_post_install_commands_error(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"post-install-commands\": ['python -c \"import sys;sys.exit(7)\"'], **project.config.envs[\"default\"]},\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 7\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Installing project in development mode\n        Running post-installation commands\n        Failed with exit code: 7\n        \"\"\"\n    )\n\n\n@pytest.mark.requires_internet\ndef test_sync_dependencies_uv(hatch, helpers, temp_dir, platform, uv_on_path, extract_installed_requirements):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"dependencies\": [\"binary\"],\n            \"post-install-commands\": [\"python -c \\\"with open('test.txt', 'w') as f: f.write('content')\\\"\"],\n            **project.config.envs[\"default\"],\n        },\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Installing project in development mode\n        Running post-installation commands\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n    assert (project_path / \"test.txt\").is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n    with UVVirtualEnv(env_path, platform):\n        output = platform.run_command([uv_on_path, \"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\n            \"utf-8\"\n        )\n        requirements = extract_installed_requirements(output.splitlines())\n\n        assert len(requirements) == 2\n        assert requirements[0].startswith(\"binary==\")\n        assert requirements[1].lower() == f\"-e {project_path.as_uri().lower()}\"\n\n\n@pytest.mark.requires_internet\ndef test_sync_dependencies_pip(hatch, helpers, temp_dir, platform, extract_installed_requirements):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"dependencies\": [\"binary\"],\n            \"post-install-commands\": [\"python -c \\\"with open('test.txt', 'w') as f: f.write('content')\\\"\"],\n            **project.config.envs[\"default\"],\n        },\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with (\n        project_path.as_cwd(),\n        EnvVars({ConfigEnvVars.DATA: str(data_path)}, exclude=[get_env_var(plugin_name=\"virtual\", option=\"uv_path\")]),\n    ):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Installing project in development mode\n        Running post-installation commands\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n    assert (project_path / \"test.txt\").is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n    with VirtualEnv(env_path, platform):\n        output = platform.run_command([\"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\"utf-8\")\n        requirements = extract_installed_requirements(output.splitlines())\n\n        assert len(requirements) == 2\n        assert requirements[0].startswith(\"binary==\")\n        assert requirements[1].lower() == f\"-e {str(project_path).lower()}\"\n\n\n@pytest.mark.requires_internet\ndef test_features(hatch, helpers, temp_dir, platform, uv_on_path, extract_installed_requirements):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"optional-dependencies\"] = {\"foo\": [\"binary\"]}\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"features\": [\"foo\"], **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Installing project in development mode\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n    with UVVirtualEnv(env_path, platform):\n        output = platform.run_command([uv_on_path, \"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\n            \"utf-8\"\n        )\n        requirements = extract_installed_requirements(output.splitlines())\n\n        assert len(requirements) == 2\n        assert requirements[0].startswith(\"binary==\")\n        assert requirements[1].lower() == f\"-e {project_path.as_uri().lower()}\"\n\n\n@pytest.mark.requires_internet\ndef test_sync_dynamic_dependencies(hatch, helpers, temp_dir, platform, uv_on_path, extract_installed_requirements):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    for i in range(2):\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", f\"{project_name}{i}\")\n\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"].pop(\"dependencies\")\n    config[\"project\"][\"dynamic\"].extend((\"dependencies\", \"optional-dependencies\"))\n    config[\"tool\"][\"hatch\"][\"metadata\"] = {\"allow-direct-references\": True, \"hooks\": {\"custom\": {}}}\n    project.save_config(config)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"dependencies\": [\"my-app1 @ {root:uri}/../my-app1\"],\n            \"features\": [\"foo\"],\n            \"post-install-commands\": [\"python -c \\\"with open('test.txt', 'w') as f: f.write('content')\\\"\"],\n            **project.config.envs[\"default\"],\n        },\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    build_script = project_path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n            class CustomHook(MetadataHookInterface):\n                def update(self, metadata):\n                    metadata['dependencies'] = ['my-app0 @ {root:uri}/../my-app0']\n                    metadata['optional-dependencies'] = {'foo': ['binary']}\n            \"\"\"\n        )\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Installing project in development mode\n        Running post-installation commands\n        Polling dependency state\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n    assert (project_path / \"test.txt\").is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = sorted(storage_path.iterdir())\n    assert [d.name for d in env_dirs] == [\"hatch-build\", \"test\"]\n\n    env_path = env_dirs[1]\n\n    with UVVirtualEnv(env_path, platform):\n        output = platform.run_command([uv_on_path, \"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\n            \"utf-8\"\n        )\n        requirements = extract_installed_requirements(output.splitlines())\n\n        assert len(requirements) == 4\n        assert requirements[0].startswith(\"binary==\")\n        assert requirements[1].lower() == f\"-e {project_path.as_uri().lower()}\"\n        assert requirements[2].lower() == f\"my-app0 @ {project_path.parent.as_uri().lower()}/my-app0\"\n        assert requirements[3].lower() == f\"my-app1 @ {project_path.parent.as_uri().lower()}/my-app1\"\n\n\n@pytest.mark.requires_internet\ndef test_unknown_dynamic_feature(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", f\"{project_name}1\")\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"].append(f\"my-app1 @ {path_to_uri(project_path).lower()}/../my-app1\")\n    config[\"project\"][\"dynamic\"].append(\"optional-dependencies\")\n    config[\"tool\"][\"hatch\"][\"metadata\"] = {\"hooks\": {\"custom\": {}}}\n    project.save_config(config)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"features\": [\"foo\"],\n            \"post-install-commands\": [\"python -c \\\"with open('test.txt', 'w') as f: f.write('content')\\\"\"],\n            **project.config.envs[\"default\"],\n        },\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    build_script = project_path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n            class CustomHook(MetadataHookInterface):\n                def update(self, metadata):\n                    metadata['optional-dependencies'] = {'bar': ['binary']}\n            \"\"\"\n        )\n    )\n\n    with (\n        project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}),\n        pytest.raises(\n            ValueError,\n            match=(\n                \"Feature `foo` of field `tool.hatch.envs.test.features` is not defined in the dynamic \"\n                \"field `project.optional-dependencies`\"\n            ),\n        ),\n    ):\n        hatch(\"env\", \"create\", \"test\")\n\n\ndef test_no_project_file(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    (project_path / \"pyproject.toml\").remove()\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n\ndef test_plugin_dependencies_unmet(hatch, config_file, helpers, temp_dir, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n\n@pytest.mark.usefixtures(\"mock_plugin_installation\")\ndef test_plugin_dependencies_met(hatch, config_file, helpers, temp_dir):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependency = \"hatch\"\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n\n@pytest.mark.usefixtures(\"mock_plugin_installation\")\ndef test_plugin_dependencies_met_as_app(hatch, config_file, helpers, temp_dir):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependency = \"hatch\"\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(\n        env_vars={ConfigEnvVars.DATA: str(data_path), \"PYAPP\": sys.executable, \"PYAPP_COMMAND_NAME\": \"self\"}\n    ):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n\n@pytest.mark.requires_internet\ndef test_no_compatible_python(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"requires-python\"] = \"==9000\"\n    project.save_config(config)\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `default` is incompatible: no compatible Python distribution available\n        \"\"\"\n    )\n\n\ndef test_no_compatible_python_ok_if_not_installed(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"requires-python\"] = \"==9000\"\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n\n@pytest.mark.requires_internet\ndef test_workspace(hatch, helpers, temp_dir, platform, uv_on_path, extract_installed_requirements):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    members = [\"foo\", \"bar\", \"baz\"]\n    for member in members:\n        with project_path.as_cwd():\n            result = hatch(\"new\", member)\n            assert result.exit_code == 0, result.output\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"workspace\": {\"members\": [{\"path\": member} for member in members]},\n            **project.config.envs[\"default\"],\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Installing project in development mode\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    with UVVirtualEnv(env_path, platform):\n        output = platform.run_command([uv_on_path, \"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\n            \"utf-8\"\n        )\n        requirements = extract_installed_requirements(output.splitlines())\n\n        assert len(requirements) == 4\n        assert requirements[0].lower() == f\"-e {project_path.as_uri().lower()}/bar\"\n        assert requirements[1].lower() == f\"-e {project_path.as_uri().lower()}/baz\"\n        assert requirements[2].lower() == f\"-e {project_path.as_uri().lower()}/foo\"\n        assert requirements[3].lower() == f\"-e {project_path.as_uri().lower()}\"\n\n\n@pytest.mark.requires_internet\ndef test_workspace_members_always_editable_with_dev_mode_false(\n    hatch, helpers, temp_dir, platform, uv_on_path, extract_installed_requirements\n):\n    \"\"\"Verify workspace members are always installed as editable even when dev-mode=false.\"\"\"\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    # Create workspace members\n    members = [\"member-a\", \"member-b\"]\n    for member in members:\n        with project_path.as_cwd():\n            result = hatch(\"new\", member)\n            assert result.exit_code == 0, result.output\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"dev-mode\": False,\n            \"workspace\": {\"members\": members},\n            **project.config.envs[\"default\"],\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"default\")\n\n    assert result.exit_code == 0, result.output\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    project_data_path = env_data_path / project_path.name\n    storage_dirs = list(project_data_path.iterdir())\n    storage_path = storage_dirs[0]\n    env_dirs = list(storage_path.iterdir())\n    env_path = env_dirs[0]\n\n    with UVVirtualEnv(env_path, platform):\n        output = platform.run_command([uv_on_path, \"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\n            \"utf-8\"\n        )\n        requirements = extract_installed_requirements(output.splitlines())\n\n        # Find project and member requirements - be more precise\n        my_app_reqs = [\n            r\n            for r in requirements\n            if (r.lower().startswith(\"-e file:\") and r.lower().endswith(\"/my-app\"))\n            or (r.lower().startswith(\"my-app @\") and \"/member-\" not in r.lower())\n        ]\n        member_a_reqs = [r for r in requirements if \"member-a\" in r.lower() and \"/member-a\" in r.lower()]\n        member_b_reqs = [r for r in requirements if \"member-b\" in r.lower() and \"/member-b\" in r.lower()]\n\n        assert len(my_app_reqs) == 1, f\"Expected 1 my-app requirement, got {len(my_app_reqs)}: {my_app_reqs}\"\n        assert len(member_a_reqs) == 1, f\"Expected 1 member-a requirement, got {len(member_a_reqs)}: {member_a_reqs}\"\n        assert len(member_b_reqs) == 1, f\"Expected 1 member-b requirement, got {len(member_b_reqs)}: {member_b_reqs}\"\n\n        my_app_req = my_app_reqs[0]\n        member_a_req = member_a_reqs[0]\n        member_b_req = member_b_reqs[0]\n\n        # Project should NOT be editable (dev-mode=false)\n        assert not my_app_req.lower().startswith(\"-e\"), f\"Project should not be editable: {my_app_req}\"\n        assert my_app_req.lower().startswith(\"my-app @\"), f\"Project should be non-editable install: {my_app_req}\"\n\n        # Workspace members MUST be editable (always)\n        assert member_a_req.lower().startswith(\"-e\"), f\"Member A should be editable: {member_a_req}\"\n        assert member_b_req.lower().startswith(\"-e\"), f\"Member B should be editable: {member_b_req}\"\n"
  },
  {
    "path": "tests/cli/env/test_find.py",
    "content": "import os\nimport sys\n\nimport pytest\n\nfrom hatch.project.core import Project\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\n\ndef test_undefined(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"find\", \"test\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `test` is not defined by project config\n        \"\"\"\n    )\n\n\ndef test_single(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = temp_dir_data / \"data\" / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    env_path = storage_path / \"my-app\"\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"find\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {env_path}\n        \"\"\"\n    )\n\n\ndef test_matrix(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(\n        project,\n        \"test\",\n        {\n            \"matrix\": [{\"version\": [\"9000\", \"42\"]}],\n            \"overrides\": {\"matrix\": {\"version\": {\"platforms\": [{\"value\": \"foo\", \"if\": [\"9000\"]}]}}},\n        },\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test.42\n        Checking dependencies\n        Skipped 1 incompatible environment:\n        test.9000 -> unsupported platform\n        \"\"\"\n    )\n\n    env_data_path = temp_dir_data / \"data\" / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"find\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {storage_path / \"test.9000\"}\n        {storage_path / \"test.42\"}\n        \"\"\"\n    )\n\n\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir_data, config_file, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = temp_dir_data / \"data\" / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    env_path = storage_path / \"my-app\"\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"find\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Syncing environment plugin requirements\n        {env_path}\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n\n\n@pytest.mark.skipif(sys.platform not in {\"win32\", \"darwin\"}, reason=\"Case insensitive file system required\")\ndef test_case_sensitivity(hatch, temp_dir_data):\n    from hatch.utils.fs import Path\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"find\")\n\n    assert result.exit_code == 0, result.output\n    path_default = result.output.strip()\n\n    with Path(str(project_path).upper()).as_cwd():\n        result = hatch(\"env\", \"find\")\n\n    assert result.exit_code == 0, result.output\n    path_upper = result.output.strip()\n\n    with Path(str(project_path).lower()).as_cwd():\n        result = hatch(\"env\", \"find\")\n\n    assert result.exit_code == 0, result.output\n    path_lower = result.output.strip()\n\n    assert path_default == path_upper == path_lower\n"
  },
  {
    "path": "tests/cli/env/test_prune.py",
    "content": "import os\n\nfrom hatch.config.constants import AppEnvVars\nfrom hatch.project.core import Project\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\n\ndef test_unknown_type(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    config = dict(project.config.envs[\"default\"])\n    config[\"type\"] = \"foo\"\n    helpers.update_project_environment(project, \"default\", config)\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"prune\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `default` has unknown type: foo\n        \"\"\"\n    )\n\n\ndef test_all(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"foo\", {})\n    helpers.update_project_environment(project, \"bar\", {})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"bar\")\n\n    assert result.exit_code == 0, result.output\n\n    env_data_path = temp_dir_data / \"data\" / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 2\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"prune\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Removing environment: foo\n        Removing environment: bar\n        \"\"\"\n    )\n\n    assert not storage_path.is_dir()\n\n\ndef test_incompatible_ok(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"skip-install\": True, \"platforms\": [\"foo\"], **project.config.envs[\"default\"]}\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"prune\")\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n\ndef test_active(hatch, temp_dir_data, helpers, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    with project_path.as_cwd(env_vars={AppEnvVars.ENV_ACTIVE: \"default\"}):\n        result = hatch(\"env\", \"prune\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Cannot remove active environment: default\n        \"\"\"\n    )\n\n\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir_data, config_file, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"skip-install\": True, \"platforms\": [\"foo\"], **project.config.envs[\"default\"]}\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"prune\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n"
  },
  {
    "path": "tests/cli/env/test_remove.py",
    "content": "import os\n\nfrom hatch.config.constants import AppEnvVars\nfrom hatch.project.core import Project\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\n\ndef test_unknown(hatch, temp_dir_data, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"remove\", \"foo\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `foo` is not defined by project config\n        \"\"\"\n    )\n\n\ndef test_nonexistent(hatch, temp_dir_data):\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"remove\", \"default\")\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n\ndef test_single(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"foo\", {})\n    helpers.update_project_environment(project, \"bar\", {})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"bar\")\n\n    assert result.exit_code == 0, result.output\n\n    env_data_path = temp_dir_data / \"data\" / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 2\n\n    foo_env_path = storage_path / \"foo\"\n    bar_env_path = storage_path / \"bar\"\n\n    assert foo_env_path.is_dir()\n    assert bar_env_path.is_dir()\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"remove\", \"bar\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Removing environment: bar\n        \"\"\"\n    )\n\n    assert foo_env_path.is_dir()\n    assert not bar_env_path.is_dir()\n\n\ndef test_all(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"foo\", {})\n    helpers.update_project_environment(project, \"bar\", {})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"bar\")\n\n    assert result.exit_code == 0, result.output\n\n    env_data_path = temp_dir_data / \"data\" / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 2\n\n    foo_env_path = storage_path / \"foo\"\n    bar_env_path = storage_path / \"bar\"\n\n    assert foo_env_path.is_dir()\n    assert bar_env_path.is_dir()\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"remove\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Removing environment: foo\n        \"\"\"\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"remove\", \"bar\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Removing environment: bar\n        \"\"\"\n    )\n\n    assert not storage_path.is_dir()\n\n\ndef test_matrix_all(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"foo\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n\n    env_data_path = temp_dir_data / \"data\" / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 2\n\n    foo_env_path = storage_path / \"foo.42\"\n    bar_env_path = storage_path / \"foo.9000\"\n\n    assert foo_env_path.is_dir()\n    assert bar_env_path.is_dir()\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"remove\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Removing environment: foo.9000\n        Removing environment: foo.42\n        \"\"\"\n    )\n\n    assert not storage_path.is_dir()\n\n\ndef test_matrix_all_local_directory(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.model.dirs.env = {\"virtual\": \".hatch\"}\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"foo\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n\n    env_data_path = project_path / \".hatch\"\n    assert env_data_path.is_dir()\n\n    env_dirs = list(env_data_path.iterdir())\n    assert len(env_dirs) == 3\n\n    assert sorted(entry.name for entry in env_dirs) == [\".gitignore\", \"foo.42\", \"foo.9000\"]\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"remove\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Removing environment: foo.9000\n        Removing environment: foo.42\n        \"\"\"\n    )\n\n    assert not env_data_path.is_dir()\n\n\ndef test_incompatible_ok(hatch, helpers, temp_dir_data):\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"skip-install\": True, \"platforms\": [\"foo\"], **project.config.envs[\"default\"]}\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"remove\")\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n\ndef test_active(hatch, temp_dir_data, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    with project_path.as_cwd(env_vars={AppEnvVars.ENV_ACTIVE: \"default\"}):\n        result = hatch(\"env\", \"remove\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Cannot remove active environment: default\n        \"\"\"\n    )\n\n\ndef test_active_override(hatch, helpers, temp_dir_data, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"foo\", {})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n\n    env_data_path = temp_dir_data / \"data\" / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    (storage_path / \"default\").is_dir()\n\n    with project_path.as_cwd(env_vars={AppEnvVars.ENV_ACTIVE: \"foo\"}):\n        result = hatch(\"env\", \"remove\", \"default\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Removing environment: default\n        \"\"\"\n    )\n\n    assert not storage_path.is_dir()\n\n\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir_data, config_file, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir_data.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir_data / \"my-app\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"foo\", {})\n    helpers.update_project_environment(project, \"bar\", {})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"create\", \"bar\")\n\n    assert result.exit_code == 0, result.output\n\n    env_data_path = temp_dir_data / \"data\" / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 2\n\n    foo_env_path = storage_path / \"foo\"\n    bar_env_path = storage_path / \"bar\"\n\n    assert foo_env_path.is_dir()\n    assert bar_env_path.is_dir()\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"remove\", \"bar\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        Removing environment: bar\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n\n    assert foo_env_path.is_dir()\n    assert not bar_env_path.is_dir()\n"
  },
  {
    "path": "tests/cli/env/test_run.py",
    "content": "import os\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.project.core import Project\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\n\ndef test_filter_not_mapping(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"skip-install\": True,\n            \"scripts\": {\n                \"error\": [\n                    'python -c \"import sys;sys.exit(3)\"',\n                    \"python -c \\\"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\\\"\",\n                ],\n            },\n            **project.config.envs[\"default\"],\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"run\", \"error\", \"--filter\", \"[]\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        The --filter/-f option must be a JSON mapping\n        \"\"\"\n    )\n\n\ndef test_filter(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(\n        project,\n        \"test\",\n        {\n            \"matrix\": [{\"version\": [\"9000\", \"42\"]}],\n            \"overrides\": {\"matrix\": {\"version\": {\"foo-bar-option\": {\"value\": True, \"if\": [\"42\"]}}}},\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"env\",\n            \"run\",\n            \"--env\",\n            \"test\",\n            \"--filter\",\n            '{\"foo-bar-option\":true}',\n            \"--\",\n            \"python\",\n            \"-c\",\n            \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        ─────────────────────────────────── test.42 ────────────────────────────────────\n        Creating environment: test.42\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n    assert env_path.name == \"test.42\"\n\n    python_path = str(output_file.read_text()).strip()\n    assert str(env_path) in python_path\n\n\ndef test_force_continue(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"skip-install\": True,\n            \"scripts\": {\n                \"error\": [\n                    'python -c \"import sys;sys.exit(2)\"',\n                    '- python -c \"import sys;sys.exit(3)\"',\n                    'python -c \"import sys;sys.exit(1)\"',\n                    \"python -c \\\"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\\\"\",\n                ],\n            },\n            **project.config.envs[\"default\"],\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"run\", \"--force-continue\", \"--\", \"error\")\n\n    assert result.exit_code == 2\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        cmd [1] | python -c \"import sys;sys.exit(2)\"\n        cmd [2] | - python -c \"import sys;sys.exit(3)\"\n        cmd [3] | python -c \"import sys;sys.exit(1)\"\n        cmd [4] | python -c \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\"\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n\ndef test_ignore_compatibility(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"skip-install\": True, \"platforms\": [\"foo\"], **project.config.envs[\"default\"]}\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"env\",\n            \"run\",\n            \"--ignore-compat\",\n            \"--env\",\n            \"test\",\n            \"--\",\n            \"python\",\n            \"-c\",\n            \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\",\n        )\n\n    assert result.exit_code == 0\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Skipped 1 incompatible environment:\n        test -> unsupported platform\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert not output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert not env_data_path.is_dir()\n\n\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir, config_file, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"env\", \"run\", \"--\", \"python\", \"-c\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\"\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n"
  },
  {
    "path": "tests/cli/env/test_show.py",
    "content": "import json\nimport os\n\nimport pytest\n\nfrom hatch.env.utils import get_env_var\nfrom hatch.project.core import Project\nfrom hatch.utils.structures import EnvVars\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\n\n@pytest.fixture(scope=\"module\", autouse=True)\ndef _terminal_width():\n    with EnvVars({\"COLUMNS\": \"200\"}, exclude=[get_env_var(plugin_name=\"virtual\", option=\"uv_path\")]):\n        yield\n\n\ndef test_default(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Standalone\n        +---------+---------+\n        | Name    | Type    |\n        +=========+=========+\n        | default | virtual |\n        +---------+---------+\n        \"\"\"\n    )\n\n\ndef test_default_as_json(hatch, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"--json\")\n\n    assert result.exit_code == 0, result.output\n\n    environments = json.loads(result.output)\n    assert list(environments) == [\n        \"default\",\n        \"hatch-build\",\n        \"hatch-static-analysis\",\n        \"hatch-test.py3.14\",\n        \"hatch-test.py3.14t\",\n        \"hatch-test.py3.13\",\n        \"hatch-test.py3.12\",\n        \"hatch-test.py3.11\",\n        \"hatch-test.py3.10\",\n        \"hatch-uv\",\n    ]\n    assert environments[\"default\"] == {\"type\": \"virtual\"}\n\n\ndef test_single_only(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"foo\", {})\n    helpers.update_project_environment(project, \"bar\", {})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Standalone\n        +---------+---------+\n        | Name    | Type    |\n        +=========+=========+\n        | default | virtual |\n        +---------+---------+\n        | foo     | virtual |\n        +---------+---------+\n        | bar     | virtual |\n        +---------+---------+\n        \"\"\"\n    )\n\n\ndef test_single_and_matrix(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"foo\", {\"matrix\": [{\"version\": [\"9000\", \"3.14\"], \"py\": [\"39\", \"310\"]}]})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Standalone\n        +---------+---------+\n        | Name    | Type    |\n        +=========+=========+\n        | default | virtual |\n        +---------+---------+\n        Matrices\n        +------+---------+----------------+\n        | Name | Type    | Envs           |\n        +======+=========+================+\n        | foo  | virtual | foo.py39-9000  |\n        |      |         | foo.py39-3.14  |\n        |      |         | foo.py310-9000 |\n        |      |         | foo.py310-3.14 |\n        +------+---------+----------------+\n        \"\"\"\n    )\n\n\ndef test_default_matrix_only(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"matrix\": [{\"version\": [\"9000\", \"3.14\"], \"py\": [\"39\", \"310\"]}]}\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Matrices\n        +---------+---------+------------+\n        | Name    | Type    | Envs       |\n        +=========+=========+============+\n        | default | virtual | py39-9000  |\n        |         |         | py39-3.14  |\n        |         |         | py310-9000 |\n        |         |         | py310-3.14 |\n        +---------+---------+------------+\n        \"\"\"\n    )\n\n\ndef test_all_matrix_types_with_single(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"matrix\": [{\"version\": [\"9000\", \"3.14\"], \"py\": [\"39\", \"310\"]}]}\n    )\n    helpers.update_project_environment(project, \"foo\", {\"matrix\": [{\"version\": [\"9000\", \"3.14\"], \"py\": [\"39\", \"310\"]}]})\n    helpers.update_project_environment(project, \"bar\", {})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Standalone\n        +------+---------+\n        | Name | Type    |\n        +======+=========+\n        | bar  | virtual |\n        +------+---------+\n        Matrices\n        +---------+---------+----------------+\n        | Name    | Type    | Envs           |\n        +=========+=========+================+\n        | default | virtual | py39-9000      |\n        |         |         | py39-3.14      |\n        |         |         | py310-9000     |\n        |         |         | py310-3.14     |\n        +---------+---------+----------------+\n        | foo     | virtual | foo.py39-9000  |\n        |         |         | foo.py39-3.14  |\n        |         |         | foo.py310-9000 |\n        |         |         | foo.py310-3.14 |\n        +---------+---------+----------------+\n        \"\"\"\n    )\n\n\ndef test_specific(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"foo\", {})\n    helpers.update_project_environment(project, \"bar\", {})\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"bar\", \"foo\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Standalone\n        +------+---------+\n        | Name | Type    |\n        +======+=========+\n        | foo  | virtual |\n        +------+---------+\n        | bar  | virtual |\n        +------+---------+\n        \"\"\"\n    )\n\n\ndef test_specific_unknown(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"foo\", \"--ascii\")\n\n    assert result.exit_code == 1, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Environment `foo` is not defined by project config\n        \"\"\"\n    )\n\n\ndef test_optional_columns(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependencies = [\"python___dateutil\", \"bAr.Baz[TLS]   >=1.2RC5\"]\n    extra_dependencies = ['Foo;python_version<\"3.8\"']\n    env_vars = {\"FOO\": \"1\", \"BAR\": \"2\"}\n    description = \"\"\"\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna \\\naliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. \\\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \\\noccaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n\"\"\"\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"optional-dependencies\"] = {\"foo_bar\": [], \"baz\": []}\n    project.save_config(config)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"matrix\": [{\"version\": [\"9000\", \"3.14\"], \"py\": [\"39\", \"310\"]}],\n            \"description\": description,\n            \"dependencies\": dependencies,\n            \"extra-dependencies\": extra_dependencies,\n            \"env-vars\": env_vars,\n            \"features\": [\"Foo...Bar\", \"Baz\", \"baZ\"],\n            \"scripts\": {\"test\": \"pytest\", \"build\": \"python -m build\", \"_foo\": \"test\"},\n        },\n    )\n    helpers.update_project_environment(\n        project,\n        \"foo\",\n        {\n            \"description\": description,\n            \"dependencies\": dependencies,\n            \"extra-dependencies\": extra_dependencies,\n            \"env-vars\": env_vars,\n        },\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Standalone\n        +------+---------+----------+-----------------------------+-----------------------+---------+----------------------------------------------------------------------------------------------------------+\n        | Name | Type    | Features | Dependencies                | Environment variables | Scripts | Description                                                                                              |\n        +======+=========+==========+=============================+=======================+=========+==========================================================================================================+\n        | foo  | virtual | baz      | bar-baz[tls]>=1.2rc5        | BAR=2                 | build   | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et   |\n        |      |         | foo-bar  | foo; python_version < '3.8' | FOO=1                 | test    | dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip  |\n        |      |         |          | python-dateutil             |                       |         | ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu |\n        |      |         |          |                             |                       |         | fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia         |\n        |      |         |          |                             |                       |         | deserunt mollit anim id est laborum.                                                                     |\n        +------+---------+----------+-----------------------------+-----------------------+---------+----------------------------------------------------------------------------------------------------------+\n        Matrices\n        +---------+---------+------------+----------+-----------------------------+-----------------------+---------+------------------------------------------------------------------------------------------+\n        | Name    | Type    | Envs       | Features | Dependencies                | Environment variables | Scripts | Description                                                                              |\n        +=========+=========+============+==========+=============================+=======================+=========+==========================================================================================+\n        | default | virtual | py39-9000  | baz      | bar-baz[tls]>=1.2rc5        | BAR=2                 | build   | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor           |\n        |         |         | py39-3.14  | foo-bar  | foo; python_version < '3.8' | FOO=1                 | test    | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud       |\n        |         |         | py310-9000 |          | python-dateutil             |                       |         | exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure    |\n        |         |         | py310-3.14 |          |                             |                       |         | dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.   |\n        |         |         |            |          |                             |                       |         | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt       |\n        |         |         |            |          |                             |                       |         | mollit anim id est laborum.                                                              |\n        +---------+---------+------------+----------+-----------------------------+-----------------------+---------+------------------------------------------------------------------------------------------+\n        \"\"\"\n    )\n\n\ndef test_context_formatting(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n\n    # Without context formatting\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"matrix\": [{\"version\": [\"9000\", \"3.14\"], \"py\": [\"39\", \"310\"]}],\n            \"dependencies\": [\"foo@ {root:uri}/../foo\"],\n        },\n    )\n\n    # With context formatting\n    helpers.update_project_environment(\n        project,\n        \"foo\",\n        {\n            \"env-vars\": {\"BAR\": \"{env:FOO_BAZ}\"},\n            \"dependencies\": [\"pydantic\"],\n        },\n    )\n\n    with project_path.as_cwd(env_vars={\"FOO_BAZ\": \"FOO_BAR\"}):\n        result = hatch(\"env\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Standalone\n        +------+---------+--------------+-----------------------+\n        | Name | Type    | Dependencies | Environment variables |\n        +======+=========+==============+=======================+\n        | foo  | virtual | pydantic     | BAR=FOO_BAR           |\n        +------+---------+--------------+-----------------------+\n        Matrices\n        +---------+---------+------------+-------------------------+\n        | Name    | Type    | Envs       | Dependencies            |\n        +=========+=========+============+=========================+\n        | default | virtual | py39-9000  | foo @ {root:uri}/../foo |\n        |         |         | py39-3.14  |                         |\n        |         |         | py310-9000 |                         |\n        |         |         | py310-3.14 |                         |\n        +---------+---------+------------+-------------------------+\n        \"\"\"\n    )\n\n\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir, config_file, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    with project_path.as_cwd():\n        result = hatch(\"env\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        Standalone\n        +---------+---------+\n        | Name    | Type    |\n        +=========+=========+\n        | default | virtual |\n        +---------+---------+\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n"
  },
  {
    "path": "tests/cli/fmt/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/fmt/test_fmt.py",
    "content": "from __future__ import annotations\n\nimport pytest\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.project.core import Project\n\n\ndef construct_ruff_defaults_file(rules: tuple[str, ...]) -> str:\n    from hatch.cli.fmt.core import PER_FILE_IGNORED_RULES\n\n    lines = [\n        \"line-length = 120\",\n        \"\",\n        \"[format]\",\n        \"docstring-code-format = true\",\n        \"docstring-code-line-length = 80\",\n        \"\",\n        \"[lint]\",\n    ]\n\n    # Selected rules\n    lines.append(\"select = [\")\n    lines.extend(f'  \"{rule}\",' for rule in sorted(rules))\n    lines.extend((\"]\", \"\"))\n\n    # Ignored rules\n    lines.append(\"[lint.per-file-ignores]\")\n    for glob, ignored_rules in PER_FILE_IGNORED_RULES.items():\n        lines.append(f'\"{glob}\" = [')\n        lines.extend(f'  \"{ignored_rule}\",' for ignored_rule in ignored_rules)\n        lines.append(\"]\")\n\n    # Default config\n    lines.extend((\n        \"\",\n        \"[lint.flake8-tidy-imports]\",\n        'ban-relative-imports = \"all\"',\n        \"\",\n        \"[lint.isort]\",\n        'known-first-party = [\"my_app\"]',\n        \"\",\n        \"[lint.flake8-pytest-style]\",\n        \"fixture-parentheses = false\",\n        \"mark-parentheses = false\",\n    ))\n\n    # Ensure the file ends with a newline to satisfy other linters\n    lines.append(\"\")\n\n    return \"\\n\".join(lines)\n\n\n@pytest.fixture(scope=\"module\")\ndef defaults_file_stable() -> str:\n    from hatch.cli.fmt.core import STABLE_RULES\n\n    return construct_ruff_defaults_file(STABLE_RULES)\n\n\n@pytest.fixture(scope=\"module\")\ndef defaults_file_preview() -> str:\n    from hatch.cli.fmt.core import PREVIEW_RULES, STABLE_RULES\n\n    return construct_ruff_defaults_file(STABLE_RULES + PREVIEW_RULES)\n\n\nclass TestDefaults:\n    def test_fix(self, hatch, helpers, temp_dir, config_file, env_run, mocker, platform, defaults_file_stable):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        config_dir = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\" / project_path.id\n        default_config = config_dir / \"ruff_defaults.toml\"\n        user_config = config_dir / \"pyproject.toml\"\n        user_config_path = platform.join_command_args([str(user_config)])\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            cmd [1] | ruff check --config {user_config_path} --fix .\n            cmd [2] | ruff format --config {user_config_path} .\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(f\"ruff check --config {user_config_path} --fix .\", shell=True),\n            mocker.call(f\"ruff format --config {user_config_path} .\", shell=True),\n        ]\n\n        assert default_config.read_text() == defaults_file_stable\n\n        old_contents = (project_path / \"pyproject.toml\").read_text()\n        config_path = str(default_config).replace(\"\\\\\", \"\\\\\\\\\")\n        assert (\n            user_config.read_text()\n            == f\"\"\"\\\n{old_contents}\n[tool.ruff]\nextend = \"{config_path}\\\"\"\"\"\n        )\n\n    def test_check(self, hatch, helpers, temp_dir, config_file, env_run, mocker, platform, defaults_file_stable):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        config_dir = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\" / project_path.id\n        default_config = config_dir / \"ruff_defaults.toml\"\n        user_config = config_dir / \"pyproject.toml\"\n        user_config_path = platform.join_command_args([str(user_config)])\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--check\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            cmd [1] | ruff check --config {user_config_path} .\n            cmd [2] | ruff format --config {user_config_path} --check --diff .\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(f\"ruff check --config {user_config_path} .\", shell=True),\n            mocker.call(f\"ruff format --config {user_config_path} --check --diff .\", shell=True),\n        ]\n\n        assert default_config.read_text() == defaults_file_stable\n\n        old_contents = (project_path / \"pyproject.toml\").read_text()\n        config_path = str(default_config).replace(\"\\\\\", \"\\\\\\\\\")\n        assert (\n            user_config.read_text()\n            == f\"\"\"\\\n{old_contents}\n[tool.ruff]\nextend = \"{config_path}\\\"\"\"\"\n        )\n\n    def test_existing_config(\n        self, hatch, helpers, temp_dir, config_file, env_run, mocker, platform, defaults_file_stable\n    ):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        config_dir = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\" / project_path.id\n        default_config = config_dir / \"ruff_defaults.toml\"\n        user_config = config_dir / \"pyproject.toml\"\n        user_config_path = platform.join_command_args([str(user_config)])\n\n        project_file = project_path / \"pyproject.toml\"\n        old_contents = project_file.read_text()\n        project_file.write_text(f\"[tool.ruff]\\n{old_contents}\")\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--check\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            cmd [1] | ruff check --config {user_config_path} .\n            cmd [2] | ruff format --config {user_config_path} --check --diff .\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(f\"ruff check --config {user_config_path} .\", shell=True),\n            mocker.call(f\"ruff format --config {user_config_path} --check --diff .\", shell=True),\n        ]\n\n        assert default_config.read_text() == defaults_file_stable\n\n        config_path = str(default_config).replace(\"\\\\\", \"\\\\\\\\\")\n        assert (\n            user_config.read_text()\n            == f\"\"\"\\\n[tool.ruff]\nextend = \"{config_path}\\\"\n{old_contents.rstrip()}\"\"\"\n        )\n\n\nclass TestPreview:\n    def test_fix_flag(self, hatch, helpers, temp_dir, config_file, env_run, mocker, platform, defaults_file_preview):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        config_dir = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\" / project_path.id\n        default_config = config_dir / \"ruff_defaults.toml\"\n        user_config = config_dir / \"pyproject.toml\"\n        user_config_path = platform.join_command_args([str(user_config)])\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--preview\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            cmd [1] | ruff check --config {user_config_path} --preview --fix .\n            cmd [2] | ruff format --config {user_config_path} --preview .\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(f\"ruff check --config {user_config_path} --preview --fix .\", shell=True),\n            mocker.call(f\"ruff format --config {user_config_path} --preview .\", shell=True),\n        ]\n\n        assert default_config.read_text() == defaults_file_preview\n\n        old_contents = (project_path / \"pyproject.toml\").read_text()\n        config_path = str(default_config).replace(\"\\\\\", \"\\\\\\\\\")\n        assert (\n            user_config.read_text()\n            == f\"\"\"\\\n{old_contents}\n[tool.ruff]\nextend = \"{config_path}\\\"\"\"\"\n        )\n\n    def test_check_flag(self, hatch, helpers, temp_dir, config_file, env_run, mocker, platform, defaults_file_preview):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        config_dir = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\" / project_path.id\n        default_config = config_dir / \"ruff_defaults.toml\"\n        user_config = config_dir / \"pyproject.toml\"\n        user_config_path = platform.join_command_args([str(user_config)])\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--check\", \"--preview\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            cmd [1] | ruff check --config {user_config_path} --preview .\n            cmd [2] | ruff format --config {user_config_path} --preview --check --diff .\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(f\"ruff check --config {user_config_path} --preview .\", shell=True),\n            mocker.call(f\"ruff format --config {user_config_path} --preview --check --diff .\", shell=True),\n        ]\n\n        assert default_config.read_text() == defaults_file_preview\n\n        old_contents = (project_path / \"pyproject.toml\").read_text()\n        config_path = str(default_config).replace(\"\\\\\", \"\\\\\\\\\")\n        assert (\n            user_config.read_text()\n            == f\"\"\"\\\n{old_contents}\n[tool.ruff]\nextend = \"{config_path}\\\"\"\"\"\n        )\n\n\nclass TestComponents:\n    def test_only_linter(self, hatch, temp_dir, config_file, env_run, mocker, platform, defaults_file_stable):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--linter\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        config_dir = next(root_data_path.iterdir())\n        default_config = config_dir / \"ruff_defaults.toml\"\n        user_config = config_dir / \"pyproject.toml\"\n        user_config_path = platform.join_command_args([str(user_config)])\n\n        assert env_run.call_args_list == [\n            mocker.call(f\"ruff check --config {user_config_path} --fix .\", shell=True),\n        ]\n\n        assert default_config.read_text() == defaults_file_stable\n\n        old_contents = (project_path / \"pyproject.toml\").read_text()\n        config_path = str(default_config).replace(\"\\\\\", \"\\\\\\\\\")\n        assert (\n            user_config.read_text()\n            == f\"\"\"\\\n{old_contents}\n[tool.ruff]\nextend = \"{config_path}\\\"\"\"\"\n        )\n\n    def test_only_formatter(self, hatch, temp_dir, config_file, env_run, mocker, platform, defaults_file_stable):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--formatter\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        config_dir = next(root_data_path.iterdir())\n        default_config = config_dir / \"ruff_defaults.toml\"\n        user_config = config_dir / \"pyproject.toml\"\n        user_config_path = platform.join_command_args([str(user_config)])\n\n        assert env_run.call_args_list == [\n            mocker.call(f\"ruff format --config {user_config_path} .\", shell=True),\n        ]\n\n        assert default_config.read_text() == defaults_file_stable\n\n        old_contents = (project_path / \"pyproject.toml\").read_text()\n        config_path = str(default_config).replace(\"\\\\\", \"\\\\\\\\\")\n        assert (\n            user_config.read_text()\n            == f\"\"\"\\\n{old_contents}\n[tool.ruff]\nextend = \"{config_path}\\\"\"\"\"\n        )\n\n    @pytest.mark.usefixtures(\"env_run\")\n    def test_select_multiple(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--linter\", \"--formatter\")\n\n        assert result.exit_code == 1, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            Cannot specify both --linter and --formatter\n            \"\"\"\n        )\n\n\nclass TestArguments:\n    def test_forwarding(self, hatch, helpers, temp_dir, config_file, env_run, mocker, platform, defaults_file_stable):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        config_dir = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\" / project_path.id\n        default_config = config_dir / \"ruff_defaults.toml\"\n        user_config = config_dir / \"pyproject.toml\"\n        user_config_path = platform.join_command_args([str(user_config)])\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--\", \"--foo\", \"bar\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            cmd [1] | ruff check --config {user_config_path} --fix --foo bar\n            cmd [2] | ruff format --config {user_config_path} --foo bar\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(f\"ruff check --config {user_config_path} --fix --foo bar\", shell=True),\n            mocker.call(f\"ruff format --config {user_config_path} --foo bar\", shell=True),\n        ]\n\n        assert default_config.read_text() == defaults_file_stable\n\n        old_contents = (project_path / \"pyproject.toml\").read_text()\n        config_path = str(default_config).replace(\"\\\\\", \"\\\\\\\\\")\n        assert (\n            user_config.read_text()\n            == f\"\"\"\\\n{old_contents}\n[tool.ruff]\nextend = \"{config_path}\\\"\"\"\"\n        )\n\n\nclass TestConfigPath:\n    @pytest.mark.usefixtures(\"env_run\")\n    def test_sync_without_config(self, hatch, helpers, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--sync\")\n\n        assert result.exit_code == 1, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            The --sync flag can only be used when the `tool.hatch.format.config-path` option is defined\n            \"\"\"\n        )\n\n    def test_sync(self, hatch, helpers, temp_dir, config_file, env_run, mocker, defaults_file_stable):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n        default_config_file = project_path / \"ruff_defaults.toml\"\n        assert not default_config_file.is_file()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\"hatch-static-analysis\": {\"config-path\": \"ruff_defaults.toml\"}}\n        config[\"tool\"][\"ruff\"] = {\"extend\": \"ruff_defaults.toml\"}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--sync\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            cmd [1] | ruff check --fix .\n            cmd [2] | ruff format .\n            \"\"\"\n        )\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        assert not root_data_path.is_dir()\n\n        assert env_run.call_args_list == [\n            mocker.call(\"ruff check --fix .\", shell=True),\n            mocker.call(\"ruff format .\", shell=True),\n        ]\n\n        assert default_config_file.read_text() == defaults_file_stable\n\n    def test_no_sync(self, hatch, helpers, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n        default_config_file = project_path / \"ruff_defaults.toml\"\n        default_config_file.touch()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\"hatch-static-analysis\": {\"config-path\": \"ruff_defaults.toml\"}}\n        config[\"tool\"][\"ruff\"] = {\"extend\": \"ruff_defaults.toml\"}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            cmd [1] | ruff check --fix .\n            cmd [2] | ruff format .\n            \"\"\"\n        )\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        assert not root_data_path.is_dir()\n\n        assert env_run.call_args_list == [\n            mocker.call(\"ruff check --fix .\", shell=True),\n            mocker.call(\"ruff format .\", shell=True),\n        ]\n\n        assert not default_config_file.read_text()\n\n    def test_sync_legacy_config(self, hatch, helpers, temp_dir, config_file, env_run, mocker, defaults_file_stable):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n        default_config_file = project_path / \"ruff_defaults.toml\"\n        assert not default_config_file.is_file()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"format\"] = {\"config-path\": \"ruff_defaults.toml\"}\n        config[\"tool\"][\"ruff\"] = {\"extend\": \"ruff_defaults.toml\"}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--sync\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            The `tool.hatch.format.config-path` option is deprecated and will be removed in a future release. Use `tool.hatch.envs.hatch-static-analysis.config-path` instead.\n            cmd [1] | ruff check --fix .\n            cmd [2] | ruff format .\n            \"\"\"\n        )\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        assert not root_data_path.is_dir()\n\n        assert env_run.call_args_list == [\n            mocker.call(\"ruff check --fix .\", shell=True),\n            mocker.call(\"ruff format .\", shell=True),\n        ]\n\n        assert default_config_file.read_text() == defaults_file_stable\n\n\nclass TestCustomScripts:\n    def test_only_linter_fix(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-static-analysis\": {\n                \"config-path\": \"none\",\n                \"dependencies\": [\"black\", \"flake8\", \"isort\"],\n                \"scripts\": {\n                    \"format-check\": [\n                        \"black --check --diff {args:.}\",\n                        \"isort --check-only --diff {args:.}\",\n                    ],\n                    \"format-fix\": [\n                        \"isort {args:.}\",\n                        \"black {args:.}\",\n                    ],\n                    \"lint-check\": \"flake8 {args:.}\",\n                    \"lint-fix\": \"lint-check\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--linter\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        assert not root_data_path.is_dir()\n\n        assert env_run.call_args_list == [\n            mocker.call(\"flake8 .\", shell=True),\n        ]\n\n    def test_only_linter_check(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-static-analysis\": {\n                \"config-path\": \"none\",\n                \"dependencies\": [\"black\", \"flake8\", \"isort\"],\n                \"scripts\": {\n                    \"format-check\": [\n                        \"black --check --diff {args:.}\",\n                        \"isort --check-only --diff {args:.}\",\n                    ],\n                    \"format-fix\": [\n                        \"isort {args:.}\",\n                        \"black {args:.}\",\n                    ],\n                    \"lint-check\": \"flake8 {args:.}\",\n                    \"lint-fix\": \"lint-check\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--check\", \"--linter\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        assert not root_data_path.is_dir()\n\n        assert env_run.call_args_list == [\n            mocker.call(\"flake8 .\", shell=True),\n        ]\n\n    def test_only_formatter_fix(self, hatch, helpers, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-static-analysis\": {\n                \"config-path\": \"none\",\n                \"dependencies\": [\"black\", \"flake8\", \"isort\"],\n                \"scripts\": {\n                    \"format-check\": [\n                        \"black --check --diff {args:.}\",\n                        \"isort --check-only --diff {args:.}\",\n                    ],\n                    \"format-fix\": [\n                        \"isort {args:.}\",\n                        \"black {args:.}\",\n                    ],\n                    \"lint-check\": \"flake8 {args:.}\",\n                    \"lint-fix\": \"lint-check\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--formatter\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            cmd [1] | isort .\n            cmd [2] | black .\n            \"\"\"\n        )\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        assert not root_data_path.is_dir()\n\n        assert env_run.call_args_list == [\n            mocker.call(\"isort .\", shell=True),\n            mocker.call(\"black .\", shell=True),\n        ]\n\n    def test_only_formatter_check(self, hatch, helpers, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-static-analysis\": {\n                \"config-path\": \"none\",\n                \"dependencies\": [\"black\", \"flake8\", \"isort\"],\n                \"scripts\": {\n                    \"format-check\": [\n                        \"black --check --diff {args:.}\",\n                        \"isort --check-only --diff {args:.}\",\n                    ],\n                    \"format-fix\": [\n                        \"isort {args:.}\",\n                        \"black {args:.}\",\n                    ],\n                    \"lint-check\": \"flake8 {args:.}\",\n                    \"lint-fix\": \"lint-check\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--check\", \"--formatter\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            cmd [1] | black --check --diff .\n            cmd [2] | isort --check-only --diff .\n            \"\"\"\n        )\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        assert not root_data_path.is_dir()\n\n        assert env_run.call_args_list == [\n            mocker.call(\"black --check --diff .\", shell=True),\n            mocker.call(\"isort --check-only --diff .\", shell=True),\n        ]\n\n    def test_fix(self, hatch, helpers, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-static-analysis\": {\n                \"config-path\": \"none\",\n                \"dependencies\": [\"black\", \"flake8\", \"isort\"],\n                \"scripts\": {\n                    \"format-check\": [\n                        \"black --check --diff {args:.}\",\n                        \"isort --check-only --diff {args:.}\",\n                    ],\n                    \"format-fix\": [\n                        \"isort {args:.}\",\n                        \"black {args:.}\",\n                    ],\n                    \"lint-check\": \"flake8 {args:.}\",\n                    \"lint-fix\": \"lint-check\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            cmd [1] | flake8 .\n            cmd [2] | isort .\n            cmd [3] | black .\n            \"\"\"\n        )\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        assert not root_data_path.is_dir()\n\n        assert env_run.call_args_list == [\n            mocker.call(\"flake8 .\", shell=True),\n            mocker.call(\"isort .\", shell=True),\n            mocker.call(\"black .\", shell=True),\n        ]\n\n    def test_check(self, hatch, helpers, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-static-analysis\": {\n                \"config-path\": \"none\",\n                \"dependencies\": [\"black\", \"flake8\", \"isort\"],\n                \"scripts\": {\n                    \"format-check\": [\n                        \"black --check --diff {args:.}\",\n                        \"isort --check-only --diff {args:.}\",\n                    ],\n                    \"format-fix\": [\n                        \"isort {args:.}\",\n                        \"black {args:.}\",\n                    ],\n                    \"lint-check\": \"flake8 {args:.}\",\n                    \"lint-fix\": \"lint-check\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"fmt\", \"--check\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            cmd [1] | flake8 .\n            cmd [2] | black --check --diff .\n            cmd [3] | isort --check-only --diff .\n            \"\"\"\n        )\n\n        root_data_path = data_path / \"env\" / \".internal\" / \"hatch-static-analysis\" / \".config\"\n        assert not root_data_path.is_dir()\n\n        assert env_run.call_args_list == [\n            mocker.call(\"flake8 .\", shell=True),\n            mocker.call(\"black --check --diff .\", shell=True),\n            mocker.call(\"isort --check-only --diff .\", shell=True),\n        ]\n"
  },
  {
    "path": "tests/cli/new/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/new/test_new.py",
    "content": "import pytest\n\nfrom hatch.config.constants import ConfigEnvVars\n\n\ndef remove_trailing_spaces(text):\n    return \"\".join(f\"{line.rstrip()}\\n\" for line in text.splitlines(True))\n\n\nclass TestErrors:\n    def test_path_is_file(self, hatch, temp_dir):\n        with temp_dir.as_cwd():\n            path = temp_dir / \"foo\"\n            path.touch()\n\n            result = hatch(\"new\", \"foo\")\n\n        assert result.exit_code == 1\n        assert result.output == f\"Path `{path}` points to a file.\\n\"\n\n    def test_path_not_empty(self, hatch, temp_dir):\n        with temp_dir.as_cwd():\n            path = temp_dir / \"foo\"\n            (path / \"bar\").ensure_dir_exists()\n\n            result = hatch(\"new\", \"foo\")\n\n        assert result.exit_code == 1\n        assert result.output == f\"Directory `{path}` is not empty.\\n\"\n\n    def test_no_plugins_found(self, hatch, config_file, temp_dir):\n        project_name = \"My.App\"\n        config_file.model.template.plugins = {\"foo\": {}}\n        config_file.save()\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 1\n        assert result.output == \"None of the defined plugins were found: foo\\n\"\n\n    def test_some_not_plugins_found(self, hatch, config_file, temp_dir):\n        project_name = \"My.App\"\n        config_file.model.template.plugins[\"foo\"] = {}\n        config_file.save()\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 1\n        assert result.output == \"Some of the defined plugins were not found: foo\\n\"\n\n\ndef test_default(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.default\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_default_explicit_path(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name, \".\")\n\n    expected_files = helpers.get_template_files(\"new.default\", project_name)\n    helpers.assert_files(temp_dir, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        src\n        └── my_app\n            ├── __about__.py\n            └── __init__.py\n        tests\n        └── __init__.py\n        LICENSE.txt\n        README.md\n        pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_default_empty_plugins_table(hatch, helpers, config_file, temp_dir):\n    project_name = \"My.App\"\n    config_file.model.template.plugins = {}\n    config_file.save()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.default\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\n@pytest.mark.requires_internet\ndef test_default_no_license_cache(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n    cache_dir = temp_dir / \"cache\"\n    cache_dir.mkdir()\n\n    with temp_dir.as_cwd({ConfigEnvVars.CACHE: str(cache_dir)}):\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.default\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_licenses_multiple(hatch, helpers, config_file, temp_dir):\n    project_name = \"My.App\"\n    config_file.model.template.licenses.default = [\"MIT\", \"Apache-2.0\"]\n    config_file.save()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.licenses_multiple\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── LICENSES\n        │   ├── Apache-2.0.txt\n        │   └── MIT.txt\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_licenses_empty(hatch, helpers, config_file, temp_dir):\n    project_name = \"My.App\"\n    config_file.model.template.licenses.default = []\n    config_file.save()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.licenses_empty\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_projects_urls_space_in_label(hatch, helpers, config_file, temp_dir):\n    project_name = \"My.App\"\n    config_file.model.template.plugins[\"default\"][\"project_urls\"] = {\n        \"Documentation\": \"https://github.com/{name}/{project_name_normalized}#readme\",\n        \"Source\": \"https://github.com/{name}/{project_name_normalized}\",\n        \"Bug Tracker\": \"https://github.com/{name}/{project_name_normalized}/issues\",\n    }\n    config_file.save()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.projects_urls_space_in_label\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_projects_urls_empty(hatch, helpers, config_file, temp_dir):\n    project_name = \"My.App\"\n    config_file.model.template.plugins[\"default\"][\"project_urls\"] = {}\n    config_file.save()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.projects_urls_empty\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_feature_cli(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name, \"--cli\")\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.feature_cli\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── cli\n        │       │   └── __init__.py\n        │       ├── __about__.py\n        │       ├── __init__.py\n        │       └── __main__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_feature_ci(hatch, helpers, config_file, temp_dir):\n    config_file.model.template.plugins[\"default\"][\"ci\"] = True\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.feature_ci\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── .github\n        │   └── workflows\n        │       └── test.yml\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_feature_no_src_layout(hatch, helpers, config_file, temp_dir):\n    config_file.model.template.plugins[\"default\"][\"src-layout\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.feature_no_src_layout\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── my_app\n        │   ├── __about__.py\n        │   └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_feature_tests_disable(hatch, helpers, config_file, temp_dir):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.basic\", project_name)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_no_project_name_error(hatch, helpers, temp_dir):\n    with temp_dir.as_cwd():\n        result = hatch(\"new\")\n\n    assert result.exit_code == 1\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Missing required argument for the project name, use the -i/--interactive flag.\n        \"\"\"\n    )\n\n\ndef test_interactive(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n    description = \"foo \\u2764\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", \"-i\", input=f\"{project_name}\\n{description}\")\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.default\", project_name, description=description)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        f\"\"\"\n        Project name: {project_name}\n        Description []: {description}\n\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_no_project_name_enables_interactive(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n    description = \"foo\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", \"-i\", input=f\"{project_name}\\n{description}\")\n\n    path = temp_dir / \"my-app\"\n\n    expected_files = helpers.get_template_files(\"new.default\", project_name, description=description)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        f\"\"\"\n        Project name: {project_name}\n        Description []: {description}\n\n        my-app\n        ├── src\n        │   └── my_app\n        │       ├── __about__.py\n        │       └── __init__.py\n        ├── tests\n        │   └── __init__.py\n        ├── LICENSE.txt\n        ├── README.md\n        └── pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_initialize_fresh(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n    description = \"foo\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    project_file = path / \"pyproject.toml\"\n    project_file.remove()\n    assert not project_file.is_file()\n\n    with path.as_cwd():\n        result = hatch(\"new\", \"--init\", input=f\"{project_name}\\n{description}\")\n\n    expected_files = helpers.get_template_files(\"new.default\", project_name, description=description)\n    helpers.assert_files(path, expected_files)\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        f\"\"\"\n        Project name: {project_name}\n        Description []: {description}\n\n        Wrote: pyproject.toml\n        \"\"\"\n    )\n\n\ndef test_initialize_update(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n    description = \"foo\"\n\n    project_file = temp_dir / \"pyproject.toml\"\n    project_file.write_text(\n        \"\"\"\\\n[build-system]\nreq = [\"hatchling\"]\nbuild-backend = \"build\"\n\n[project]\nname = \"\"\ndescription = \"\"\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"MIT\"\ndynamic = [\"version\"]\n\n[tool.hatch.version]\npath = \"o/__init__.py\"\n\"\"\"\n    )\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", \"--init\", input=f\"{project_name}\\n{description}\")\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        f\"\"\"\n        Project name: {project_name}\n        Description []: {description}\n\n        Updated: pyproject.toml\n        \"\"\"\n    )\n    assert len(list(temp_dir.iterdir())) == 1\n    assert project_file.read_text() == (\n        f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"my-app\"\ndescription = \"{description}\"\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"MIT\"\ndynamic = [\"version\"]\n\n[tool.hatch.version]\npath = \"my_app/__init__.py\"\n\"\"\"\n    )\n\n\ndef test_initialize_setup_cfg_only(hatch, helpers, temp_dir):\n    \"\"\"\n    Test initializing a project with a setup.cfg file only.\n    \"\"\"\n    setup_cfg_file = temp_dir / \"setup.cfg\"\n    setup_cfg_file.write_text(\n        \"\"\"\\\n[metadata]\nname = testapp\nversion = attr:testapp.__version__\ndescription = Foo\nauthor = U.N. Owen\nauthor_email = void@some.where\nurl = https://example.com\nlicense = MIT\n\"\"\"\n    )\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", \"--init\")\n\n    assert result.exit_code == 0, result.output\n    assert remove_trailing_spaces(result.output) == helpers.dedent(\n        \"\"\"\n        Migrating project metadata from setuptools\n        \"\"\"\n    )\n\n    project_file = temp_dir / \"pyproject.toml\"\n    assert project_file.read_text() == (\n        \"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"testapp\"\ndynamic = [\"version\"]\ndescription = \"Foo\"\nlicense = \"MIT\"\nauthors = [\n    { name = \"U.N. Owen\", email = \"void@some.where\" },\n]\n\n[project.urls]\nHomepage = \"https://example.com\"\n\n[tool.hatch.version]\npath = \"testapp/__init__.py\"\n\n[tool.hatch.build.targets.sdist]\ninclude = [\n    \"/testapp\",\n]\n\"\"\"\n    )\n"
  },
  {
    "path": "tests/cli/project/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/project/test_metadata.py",
    "content": "import os\n\nimport pytest\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.project.core import Project\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\npytestmark = [pytest.mark.usefixtures(\"mock_backend_process_output\")]\n\n\ndef read_readme(project_dir):\n    return repr((project_dir / \"README.txt\").read_text())[1:-1]\n\n\ndef test_other_backend(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    (path / \"README.md\").replace(path / \"README.txt\")\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"] = [\"flit-core==3.10.1\"]\n    config[\"build-system\"][\"build-backend\"] = \"flit_core.buildapi\"\n    config[\"project\"][\"version\"] = \"0.0.1\"\n    config[\"project\"][\"dynamic\"] = []\n    config[\"project\"][\"readme\"] = \"README.txt\"\n    del config[\"project\"][\"license\"]\n    project.save_config(config)\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        {{\n            \"name\": \"my-app\",\n            \"version\": \"0.0.1\",\n            \"readme\": {{\n                \"content-type\": \"text/plain\",\n                \"text\": \"{read_readme(path)}\\\\n\"\n            }},\n            \"keywords\": [\n                \"\"\n            ],\n            \"classifiers\": [\n                \"Development Status :: 4 - Beta\",\n                \"Programming Language :: Python\",\n                \"Programming Language :: Python :: 3.8\",\n                \"Programming Language :: Python :: 3.9\",\n                \"Programming Language :: Python :: 3.10\",\n                \"Programming Language :: Python :: 3.11\",\n                \"Programming Language :: Python :: 3.12\",\n                \"Programming Language :: Python :: Implementation :: CPython\",\n                \"Programming Language :: Python :: Implementation :: PyPy\"\n            ],\n            \"urls\": {{\n                \"Documentation\": \"https://github.com/Foo Bar/my-app#readme\",\n                \"Issues\": \"https://github.com/Foo Bar/my-app/issues\",\n                \"Source\": \"https://github.com/Foo Bar/my-app\"\n            }},\n            \"authors\": [\n                {{\n                    \"email\": \"foo@bar.baz\",\n                    \"name\": \"Foo Bar\"\n                }}\n            ],\n            \"requires-python\": \">=3.8\"\n        }}\n        \"\"\"\n    )\n\n\ndef test_default_all(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    (path / \"README.md\").replace(path / \"README.txt\")\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"readme\"] = \"README.txt\"\n    project.save_config(config)\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        {{\n            \"name\": \"my-app\",\n            \"version\": \"0.0.1\",\n            \"readme\": {{\n                \"content-type\": \"text/plain\",\n                \"text\": \"{read_readme(path)}\"\n            }},\n            \"requires-python\": \">=3.8\",\n            \"license\": \"MIT\",\n            \"authors\": [\n                {{\n                    \"name\": \"Foo Bar\",\n                    \"email\": \"foo@bar.baz\"\n                }}\n            ],\n            \"classifiers\": [\n                \"Development Status :: 4 - Beta\",\n                \"Programming Language :: Python\",\n                \"Programming Language :: Python :: 3.8\",\n                \"Programming Language :: Python :: 3.9\",\n                \"Programming Language :: Python :: 3.10\",\n                \"Programming Language :: Python :: 3.11\",\n                \"Programming Language :: Python :: 3.12\",\n                \"Programming Language :: Python :: Implementation :: CPython\",\n                \"Programming Language :: Python :: Implementation :: PyPy\"\n            ],\n            \"urls\": {{\n                \"Documentation\": \"https://github.com/Foo Bar/my-app#readme\",\n                \"Issues\": \"https://github.com/Foo Bar/my-app/issues\",\n                \"Source\": \"https://github.com/Foo Bar/my-app\"\n            }}\n        }}\n        \"\"\"\n    )\n\n\ndef test_field_readme(hatch, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    (path / \"README.md\").replace(path / \"README.txt\")\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"readme\"] = \"README.txt\"\n    project.save_config(config)\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\", \"readme\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == (\n        f\"\"\"\\\nCreating environment: hatch-build\nChecking dependencies\nSyncing dependencies\nInspecting build dependencies\n{(path / \"README.txt\").read_text()}\n\"\"\"\n    )\n\n\ndef test_field_string(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\", \"license\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        MIT\n        \"\"\"\n    )\n\n\ndef test_field_complex(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\", \"urls\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        {\n            \"Documentation\": \"https://github.com/Foo Bar/my-app#readme\",\n            \"Issues\": \"https://github.com/Foo Bar/my-app/issues\",\n            \"Source\": \"https://github.com/Foo Bar/my-app\"\n        }\n        \"\"\"\n    )\n\n\n@pytest.mark.allow_backend_process\ndef test_incompatible_environment(hatch, temp_dir, helpers, build_env_config):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(path)\n    helpers.update_project_environment(project, \"hatch-build\", {\"python\": \"9000\", **build_env_config})\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `hatch-build` is incompatible: cannot locate Python: 9000\n        \"\"\"\n    )\n\n\ndef test_plugin_dependencies_unmet(hatch, temp_dir, helpers, mock_plugin_installation):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependency = os.urandom(16).hex()\n    (path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    (path / \"README.md\").replace(path / \"README.txt\")\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"readme\"] = \"README.txt\"\n    project.save_config(config)\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Syncing environment plugin requirements\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        {{\n            \"name\": \"my-app\",\n            \"version\": \"0.0.1\",\n            \"readme\": {{\n                \"content-type\": \"text/plain\",\n                \"text\": \"{read_readme(path)}\"\n            }},\n            \"requires-python\": \">=3.8\",\n            \"license\": \"MIT\",\n            \"authors\": [\n                {{\n                    \"name\": \"Foo Bar\",\n                    \"email\": \"foo@bar.baz\"\n                }}\n            ],\n            \"classifiers\": [\n                \"Development Status :: 4 - Beta\",\n                \"Programming Language :: Python\",\n                \"Programming Language :: Python :: 3.8\",\n                \"Programming Language :: Python :: 3.9\",\n                \"Programming Language :: Python :: 3.10\",\n                \"Programming Language :: Python :: 3.11\",\n                \"Programming Language :: Python :: 3.12\",\n                \"Programming Language :: Python :: Implementation :: CPython\",\n                \"Programming Language :: Python :: Implementation :: PyPy\"\n            ],\n            \"urls\": {{\n                \"Documentation\": \"https://github.com/Foo Bar/my-app#readme\",\n                \"Issues\": \"https://github.com/Foo Bar/my-app/issues\",\n                \"Source\": \"https://github.com/Foo Bar/my-app\"\n            }}\n        }}\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n\n\ndef test_build_dependencies_unmet(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    (path / \"README.md\").replace(path / \"README.txt\")\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"readme\"] = \"README.txt\"\n    config[\"tool\"][\"hatch\"][\"build\"] = {\"dependencies\": [\"binary\"]}\n    project.save_config(config)\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\", \"license\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        Syncing dependencies\n        MIT\n        \"\"\"\n    )\n\n\n@pytest.mark.allow_backend_process\n@pytest.mark.requires_internet\ndef test_no_compatibility_check_if_exists(hatch, temp_dir, helpers, mocker):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"].append(\"binary\")\n    project.save_config(config)\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\", \"license\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        MIT\n        \"\"\"\n    )\n\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment.check_compatibility\", side_effect=Exception(\"incompatible\"))\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"project\", \"metadata\", \"license\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Inspecting build dependencies\n        MIT\n        \"\"\"\n    )\n"
  },
  {
    "path": "tests/cli/publish/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/publish/test_publish.py",
    "content": "import os\nimport secrets\nimport tarfile\nimport zipfile\nfrom collections import defaultdict\n\nimport pytest\n\nfrom hatch.config.constants import PublishEnvVars\n\npytestmark = [\n    pytest.mark.requires_docker,\n    pytest.mark.requires_internet,\n    pytest.mark.usefixtures(\"devpi\"),\n    pytest.mark.usefixtures(\"mock_backend_process\"),\n]\n\n\n@pytest.fixture(autouse=True)\ndef keyring_store(mocker):\n    mock_store = defaultdict(dict)\n    mocker.patch(\n        \"keyring.get_password\",\n        side_effect=lambda system, user: mock_store[system].get(user),\n    )\n    mocker.patch(\n        \"keyring.set_password\",\n        side_effect=lambda system, user, auth: mock_store[system].__setitem__(user, auth),\n    )\n    return mock_store\n\n\n@pytest.fixture\ndef published_project_name():\n    return f\"c4880cdbe05de9a28415fbad{secrets.choice(range(100))}\"\n\n\ndef remove_metadata_field(field: str, metadata_file_contents: str):\n    lines = metadata_file_contents.splitlines(True)\n\n    field_marker = f\"{field}: \"\n    indices_to_remove = []\n\n    for i, line in enumerate(lines):\n        if line.lower().startswith(field_marker):\n            indices_to_remove.append(i)\n\n    for i, index in enumerate(indices_to_remove):\n        del lines[index - i]\n\n    return \"\".join(lines)\n\n\ndef timestamp_to_version(timestamp):\n    major, minor = str(timestamp).split(\".\")\n    if minor.startswith(\"0\"):\n        normalized_minor = str(int(minor))\n        padding = \".\".join(\"0\" for _ in range(len(minor) - len(normalized_minor)))\n        return f\"{major}.{padding}.{normalized_minor}\"\n\n    return f\"{major}.{minor}\"\n\n\ndef test_timestamp_to_version():\n    assert timestamp_to_version(123.4) == \"123.4\"\n    assert timestamp_to_version(123.04) == \"123.0.4\"\n    assert timestamp_to_version(123.004) == \"123.0.0.4\"\n\n\ndef test_explicit_options(hatch, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"publish\", \"-o\", \"foo=bar\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == (\n        \"Use the standard CLI flags rather than passing explicit options when using the `index` plugin\\n\"\n    )\n\n\ndef test_unknown_publisher(hatch, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"publish\", \"-p\", \"foo\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == \"Unknown publisher: foo\\n\"\n\n\ndef test_disabled(hatch, temp_dir, config_file):\n    config_file.model.publish[\"index\"][\"disable\"] = True\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"publish\", \"-n\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == \"Publisher is disabled: index\\n\"\n\n\ndef test_repo_invalid_type(hatch, temp_dir, config_file):\n    config_file.model.publish[\"index\"][\"repos\"] = {\"dev\": 9000}\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"publish\", \"--user\", \"foo\", \"--auth\", \"bar\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == \"Hatch config field `publish.index.repos.dev` must be a string or a mapping\\n\"\n\n\ndef test_repo_missing_url(hatch, temp_dir, config_file):\n    config_file.model.publish[\"index\"][\"repos\"] = {\"dev\": {}}\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"publish\", \"--user\", \"foo\", \"--auth\", \"bar\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == \"Hatch config field `publish.index.repos.dev` must define a `url` key\\n\"\n\n\ndef test_missing_user(hatch, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"publish\", \"-n\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == \"Missing required option: user\\n\"\n\n\ndef test_missing_auth(hatch, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n\n    with path.as_cwd():\n        result = hatch(\"publish\", \"-n\", \"--user\", \"foo\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == \"Missing required option: auth\\n\"\n\n\ndef test_flags(hatch, devpi, temp_dir_cache, helpers, published_project_name):\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n\n    with path.as_cwd():\n        current_version = timestamp_to_version(helpers.get_current_timestamp())\n        result = hatch(\"version\", current_version)\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n        build_directory = path / \"dist\"\n        artifacts = list(build_directory.iterdir())\n\n        result = hatch(\n            \"publish\", \"--repo\", devpi.repo, \"--user\", devpi.user, \"--auth\", devpi.auth, \"--ca-cert\", devpi.ca_cert\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {artifacts[0].relative_to(path)} ... success\n        {artifacts[1].relative_to(path)} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n\ndef test_plugin_config(hatch, devpi, temp_dir_cache, helpers, published_project_name, config_file):\n    config_file.model.publish[\"index\"][\"user\"] = devpi.user\n    config_file.model.publish[\"index\"][\"auth\"] = devpi.auth\n    config_file.model.publish[\"index\"][\"ca-cert\"] = devpi.ca_cert\n    config_file.model.publish[\"index\"][\"repo\"] = \"dev\"\n    config_file.model.publish[\"index\"][\"repos\"] = {\"dev\": devpi.repo}\n    config_file.save()\n\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n\n    with path.as_cwd():\n        del os.environ[PublishEnvVars.REPO]\n\n        current_version = timestamp_to_version(helpers.get_current_timestamp())\n        result = hatch(\"version\", current_version)\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n        build_directory = path / \"dist\"\n        artifacts = list(build_directory.iterdir())\n\n        result = hatch(\"publish\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {artifacts[0].relative_to(path)} ... success\n        {artifacts[1].relative_to(path)} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n\ndef test_plugin_config_repo_override(hatch, devpi, temp_dir_cache, helpers, published_project_name, config_file):\n    config_file.model.publish[\"index\"][\"user\"] = \"foo\"\n    config_file.model.publish[\"index\"][\"auth\"] = \"bar\"\n    config_file.model.publish[\"index\"][\"ca-cert\"] = \"cert\"\n    config_file.model.publish[\"index\"][\"repo\"] = \"dev\"\n    config_file.model.publish[\"index\"][\"repos\"] = {\n        \"dev\": {\"url\": devpi.repo, \"user\": devpi.user, \"auth\": devpi.auth, \"ca-cert\": devpi.ca_cert},\n    }\n    config_file.save()\n\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n\n    with path.as_cwd():\n        del os.environ[PublishEnvVars.REPO]\n\n        current_version = timestamp_to_version(helpers.get_current_timestamp())\n        result = hatch(\"version\", current_version)\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n        build_directory = path / \"dist\"\n        artifacts = list(build_directory.iterdir())\n\n        result = hatch(\"publish\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {artifacts[0].relative_to(path)} ... success\n        {artifacts[1].relative_to(path)} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n\ndef test_prompt(hatch, devpi, temp_dir_cache, helpers, published_project_name, config_file):\n    config_file.model.publish[\"index\"][\"ca-cert\"] = devpi.ca_cert\n    config_file.model.publish[\"index\"][\"repo\"] = \"dev\"\n    config_file.model.publish[\"index\"][\"repos\"] = {\"dev\": devpi.repo}\n    config_file.save()\n\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n\n    with path.as_cwd():\n        current_version = timestamp_to_version(helpers.get_current_timestamp())\n        result = hatch(\"version\", current_version)\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n        build_directory = path / \"dist\"\n        artifacts = list(build_directory.iterdir())\n\n        result = hatch(\"publish\", input=f\"{devpi.user}\\nfoo\")\n\n    assert result.exit_code == 1, result.output\n    assert \"401\" in result.output\n    assert \"Unauthorized\" in result.output\n\n    # Ensure nothing is saved for errors\n    with path.as_cwd():\n        result = hatch(\"publish\", \"-n\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == \"Missing required option: user\\n\"\n\n    # Trigger save\n    with path.as_cwd():\n        result = hatch(\"publish\", str(artifacts[0]), input=f\"{devpi.user}\\n{devpi.auth}\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Username for '{devpi.repo}' [__token__]: {devpi.user}\n        Password / Token:{\" \"}\n        {artifacts[0].relative_to(path)} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n    # Use saved results\n    with path.as_cwd():\n        result = hatch(\"publish\", str(artifacts[1]))\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {artifacts[1].relative_to(path)} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n\ndef test_initialize_auth(hatch, devpi, temp_dir_cache, helpers, published_project_name, config_file):\n    config_file.model.publish[\"index\"][\"ca-cert\"] = devpi.ca_cert\n    config_file.model.publish[\"index\"][\"repo\"] = \"dev\"\n    config_file.model.publish[\"index\"][\"repos\"] = {\"dev\": devpi.repo}\n    config_file.save()\n\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n\n    # Trigger save\n    with path.as_cwd():\n        result = hatch(\"publish\", \"--initialize-auth\", input=f\"{devpi.user}\\n{devpi.auth}\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Username for '{devpi.repo}' [__token__]: {devpi.user}\n        Password / Token:{\" \"}\n        \"\"\"\n    )\n\n    with path.as_cwd():\n        current_version = timestamp_to_version(helpers.get_current_timestamp())\n        result = hatch(\"version\", current_version)\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\", \"-t\", \"wheel\")\n        assert result.exit_code == 0, result.output\n\n        build_directory = path / \"dist\"\n        artifacts = list(build_directory.iterdir())\n\n    # Use saved results\n    with path.as_cwd():\n        result = hatch(\"publish\", str(artifacts[0]))\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {artifacts[0].relative_to(path)} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n\ndef test_external_artifact_path(hatch, devpi, temp_dir_cache, helpers, published_project_name, config_file):\n    config_file.model.publish[\"index\"][\"ca-cert\"] = devpi.ca_cert\n    config_file.model.publish[\"index\"][\"repo\"] = \"dev\"\n    config_file.model.publish[\"index\"][\"repos\"] = {\"dev\": devpi.repo}\n    config_file.save()\n\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n    external_build_directory = temp_dir_cache / \"dist\"\n\n    with path.as_cwd():\n        current_version = timestamp_to_version(helpers.get_current_timestamp())\n        result = hatch(\"version\", current_version)\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\", \"-t\", \"sdist\", str(external_build_directory))\n        assert result.exit_code == 0, result.output\n\n        external_artifacts = list(external_build_directory.iterdir())\n\n        result = hatch(\"build\", \"-t\", \"wheel\")\n        assert result.exit_code == 0, result.output\n\n        internal_build_directory = path / \"dist\"\n        internal_artifacts = list(internal_build_directory.iterdir())\n\n        result = hatch(\"publish\", \"--user\", devpi.user, \"--auth\", devpi.auth, \"dist\", str(external_build_directory))\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {internal_artifacts[0].relative_to(path)} ... success\n        {external_artifacts[0]} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n\ndef test_already_exists(hatch, devpi, temp_dir_cache, helpers, published_project_name, config_file):\n    config_file.model.publish[\"index\"][\"ca-cert\"] = devpi.ca_cert\n    config_file.model.publish[\"index\"][\"repo\"] = \"dev\"\n    config_file.model.publish[\"index\"][\"repos\"] = {\"dev\": devpi.repo}\n    config_file.save()\n\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n\n    with path.as_cwd():\n        current_version = timestamp_to_version(helpers.get_current_timestamp())\n        result = hatch(\"version\", current_version)\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n        build_directory = path / \"dist\"\n        artifacts = list(build_directory.iterdir())\n\n        result = hatch(\"publish\", \"--user\", devpi.user, \"--auth\", devpi.auth)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {artifacts[0].relative_to(path)} ... success\n        {artifacts[1].relative_to(path)} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n    with path.as_cwd():\n        result = hatch(\"publish\", \"--user\", devpi.user, \"--auth\", devpi.auth)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {artifacts[0].relative_to(path)} ... already exists\n        {artifacts[1].relative_to(path)} ... already exists\n        \"\"\"\n    )\n\n\ndef test_no_artifacts(hatch, temp_dir_cache, helpers, published_project_name):\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n\n    with path.as_cwd():\n        directory = path / \"dir2\"\n        directory.mkdir()\n        (directory / \"test.txt\").touch()\n\n        result = hatch(\"publish\", \"dir1\", \"dir2\", \"--user\", \"foo\", \"--auth\", \"bar\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        No artifacts found\n        \"\"\"\n    )\n\n\ndef test_enable_with_flag(hatch, devpi, temp_dir_cache, helpers, published_project_name, config_file):\n    config_file.model.publish[\"index\"][\"user\"] = devpi.user\n    config_file.model.publish[\"index\"][\"auth\"] = devpi.auth\n    config_file.model.publish[\"index\"][\"ca-cert\"] = devpi.ca_cert\n    config_file.model.publish[\"index\"][\"repo\"] = \"dev\"\n    config_file.model.publish[\"index\"][\"repos\"] = {\"dev\": devpi.repo}\n    config_file.model.publish[\"index\"][\"disable\"] = True\n    config_file.save()\n\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n\n    with path.as_cwd():\n        del os.environ[PublishEnvVars.REPO]\n\n        current_version = timestamp_to_version(helpers.get_current_timestamp())\n        result = hatch(\"version\", current_version)\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n        build_directory = path / \"dist\"\n        artifacts = list(build_directory.iterdir())\n\n        result = hatch(\"publish\", \"-y\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {artifacts[0].relative_to(path)} ... success\n        {artifacts[1].relative_to(path)} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n\ndef test_enable_with_prompt(hatch, devpi, temp_dir_cache, helpers, published_project_name, config_file):\n    config_file.model.publish[\"index\"][\"user\"] = devpi.user\n    config_file.model.publish[\"index\"][\"auth\"] = devpi.auth\n    config_file.model.publish[\"index\"][\"ca-cert\"] = devpi.ca_cert\n    config_file.model.publish[\"index\"][\"repo\"] = \"dev\"\n    config_file.model.publish[\"index\"][\"repos\"] = {\"dev\": devpi.repo}\n    config_file.model.publish[\"index\"][\"disable\"] = True\n    config_file.save()\n\n    with temp_dir_cache.as_cwd():\n        result = hatch(\"new\", published_project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir_cache / published_project_name\n\n    with path.as_cwd():\n        del os.environ[PublishEnvVars.REPO]\n\n        current_version = timestamp_to_version(helpers.get_current_timestamp())\n        result = hatch(\"version\", current_version)\n        assert result.exit_code == 0, result.output\n\n        result = hatch(\"build\")\n        assert result.exit_code == 0, result.output\n\n        build_directory = path / \"dist\"\n        artifacts = list(build_directory.iterdir())\n\n        result = hatch(\"publish\", input=\"y\\n\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Confirm `index` publishing [y/N]: y\n        {artifacts[0].relative_to(path)} ... success\n        {artifacts[1].relative_to(path)} ... success\n\n        [{published_project_name}]\n        {devpi.repo}{published_project_name}/{current_version}/\n        \"\"\"\n    )\n\n\nclass TestWheel:\n    @pytest.mark.parametrize(\"field\", [\"name\", \"version\"])\n    def test_missing_required_metadata_field(self, hatch, temp_dir_cache, helpers, published_project_name, field):\n        with temp_dir_cache.as_cwd():\n            result = hatch(\"new\", published_project_name)\n            assert result.exit_code == 0, result.output\n\n        path = temp_dir_cache / published_project_name\n\n        with path.as_cwd():\n            current_version = timestamp_to_version(helpers.get_current_timestamp())\n            result = hatch(\"version\", current_version)\n            assert result.exit_code == 0, result.output\n\n            result = hatch(\"build\", \"-t\", \"wheel\")\n            assert result.exit_code == 0, result.output\n\n            build_directory = path / \"dist\"\n            artifacts = list(build_directory.iterdir())\n\n        artifact_path = str(artifacts[0])\n        metadata_file_path = f\"{published_project_name}-{current_version}.dist-info/METADATA\"\n\n        with zipfile.ZipFile(artifact_path, \"r\") as zip_archive, zip_archive.open(metadata_file_path) as metadata_file:\n            metadata_file_contents = metadata_file.read().decode(\"utf-8\")\n\n        with (\n            zipfile.ZipFile(artifact_path, \"w\") as zip_archive,\n            zip_archive.open(metadata_file_path, \"w\") as metadata_file,\n        ):\n            metadata_file.write(remove_metadata_field(field, metadata_file_contents).encode(\"utf-8\"))\n\n        with path.as_cwd():\n            result = hatch(\"publish\", \"--user\", \"foo\", \"--auth\", \"bar\")\n\n        assert result.exit_code == 1, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Missing required field `{field}` in artifact: {artifact_path}\n            \"\"\"\n        )\n\n\nclass TestSourceDistribution:\n    @pytest.mark.parametrize(\"field\", [\"name\", \"version\"])\n    def test_missing_required_metadata_field(self, hatch, temp_dir_cache, helpers, published_project_name, field):\n        with temp_dir_cache.as_cwd():\n            result = hatch(\"new\", published_project_name)\n            assert result.exit_code == 0, result.output\n\n        path = temp_dir_cache / published_project_name\n\n        with path.as_cwd():\n            current_version = timestamp_to_version(helpers.get_current_timestamp())\n            result = hatch(\"version\", current_version)\n            assert result.exit_code == 0, result.output\n\n            result = hatch(\"build\", \"-t\", \"sdist\")\n            assert result.exit_code == 0, result.output\n\n            build_directory = path / \"dist\"\n            artifacts = list(build_directory.iterdir())\n\n        artifact_path = str(artifacts[0])\n        extraction_directory = path / \"extraction\"\n\n        with tarfile.open(artifact_path, \"r:gz\") as tar_archive:\n            tar_archive.extractall(extraction_directory, **helpers.tarfile_extraction_compat_options())\n\n        metadata_file_path = extraction_directory / f\"{published_project_name}-{current_version}\" / \"PKG-INFO\"\n        metadata_file_path.write_text(remove_metadata_field(field, metadata_file_path.read_text()))\n\n        with tarfile.open(artifact_path, \"w:gz\") as tar_archive:\n            tar_archive.add(extraction_directory, arcname=\"\")\n\n        with path.as_cwd():\n            result = hatch(\"publish\", \"--user\", \"foo\", \"--auth\", \"bar\")\n\n        assert result.exit_code == 1, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Missing required field `{field}` in artifact: {artifact_path}\n            \"\"\"\n        )\n"
  },
  {
    "path": "tests/cli/python/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/python/conftest.py",
    "content": "import secrets\n\nimport pytest\n\nfrom hatch.utils.shells import detect_shell\n\n\n@pytest.fixture(autouse=True)\ndef default_shells(platform):\n    return [] if platform.windows else [detect_shell(platform)[0]]\n\n\n@pytest.fixture(autouse=True)\ndef isolated_python_directory(config_file):\n    config_file.model.dirs.python = \"isolated\"\n    config_file.save()\n\n\n@pytest.fixture(autouse=True)\ndef path_append(mocker):\n    return mocker.patch(\"userpath.append\")\n\n\n@pytest.fixture(autouse=True)\ndef disable_path_detectors(mocker):\n    mocker.patch(\"userpath.in_current_path\", return_value=False)\n    mocker.patch(\"userpath.in_new_path\", return_value=False)\n\n\n@pytest.fixture\ndef dist_name(compatible_python_distributions):\n    return secrets.choice(compatible_python_distributions)\n"
  },
  {
    "path": "tests/cli/python/test_find.py",
    "content": "def test_not_installed(hatch, helpers):\n    name = \"3.10\"\n    result = hatch(\"python\", \"find\", name)\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Distribution not installed: {name}\n        \"\"\"\n    )\n\n\ndef test_binary(hatch, helpers, temp_dir_data, dist_name):\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    dist = helpers.write_distribution(install_dir, dist_name)\n\n    result = hatch(\"python\", \"find\", dist_name)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {dist.python_path}\n        \"\"\"\n    )\n\n\ndef test_parent(hatch, helpers, temp_dir_data, dist_name):\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    dist = helpers.write_distribution(install_dir, dist_name)\n\n    result = hatch(\"python\", \"find\", dist_name, \"--parent\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        {dist.python_path.parent}\n        \"\"\"\n    )\n"
  },
  {
    "path": "tests/cli/python/test_install.py",
    "content": "import json\nimport secrets\n\nimport pytest\n\nfrom hatch.errors import PythonDistributionResolutionError\nfrom hatch.python.core import InstalledDistribution\nfrom hatch.python.distributions import ORDERED_DISTRIBUTIONS\nfrom hatch.python.resolve import get_distribution\n\n\ndef test_unknown(hatch, helpers, path_append, mocker):\n    install = mocker.patch(\"hatch.python.core.PythonManager.install\")\n\n    result = hatch(\"python\", \"install\", \"foo\", \"bar\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Unknown distributions: foo, bar\n        \"\"\"\n    )\n\n    install.assert_not_called()\n    path_append.assert_not_called()\n\n\ndef test_incompatible_single(hatch, helpers, path_append, dist_name, mocker):\n    mocker.patch(\"hatch.python.resolve.get_distribution\", side_effect=PythonDistributionResolutionError)\n    install = mocker.patch(\"hatch.python.core.PythonManager.install\")\n\n    result = hatch(\"python\", \"install\", dist_name)\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Incompatible distributions: {dist_name}\n        \"\"\"\n    )\n\n    install.assert_not_called()\n    path_append.assert_not_called()\n\n\ndef test_incompatible_all(hatch, helpers, path_append, mocker):\n    mocker.patch(\"hatch.python.resolve.get_distribution\", side_effect=PythonDistributionResolutionError)\n    install = mocker.patch(\"hatch.python.core.PythonManager.install\")\n\n    result = hatch(\"python\", \"install\", \"all\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Incompatible distributions: {\", \".join(ORDERED_DISTRIBUTIONS)}\n        \"\"\"\n    )\n\n    install.assert_not_called()\n    path_append.assert_not_called()\n\n\n@pytest.mark.requires_internet\ndef test_installation(\n    hatch, helpers, temp_dir_data, platform, path_append, default_shells, compatible_python_distributions\n):\n    selection = [name for name in compatible_python_distributions if not name.startswith(\"pypy\")]\n    dist_name = secrets.choice(selection)\n    result = hatch(\"python\", \"install\", dist_name)\n\n    install_dir = temp_dir_data / \"data\" / \"pythons\" / dist_name\n    metadata_file = install_dir / InstalledDistribution.metadata_filename()\n    python_path = install_dir / json.loads(metadata_file.read_text())[\"python_path\"]\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Installing {dist_name}\n        Installed {dist_name} @ {install_dir}\n\n        The following directory has been added to your PATH (pending a shell restart):\n\n        {python_path.parent}\n        \"\"\"\n    )\n\n    assert python_path.is_file()\n\n    output = platform.check_command_output([python_path, \"-c\", \"import sys;print(sys.executable)\"]).strip()\n    assert output == str(python_path)\n\n    output = platform.check_command_output([python_path, \"--version\"]).strip()\n    assert output.startswith(f\"Python {dist_name}.\")\n\n    path_append.assert_called_once_with(str(python_path.parent), shells=default_shells)\n\n\ndef test_already_installed_latest(hatch, helpers, temp_dir_data, path_append, dist_name, mocker):\n    install = mocker.patch(\"hatch.python.core.PythonManager.install\")\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    installed_dist = helpers.write_distribution(install_dir, dist_name)\n\n    result = hatch(\"python\", \"install\", dist_name)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        The latest version is already installed: {installed_dist.version}\n        \"\"\"\n    )\n\n    install.assert_not_called()\n    path_append.assert_not_called()\n\n\ndef test_already_installed_update_disabled(hatch, helpers, temp_dir_data, path_append, dist_name, mocker):\n    install = mocker.patch(\"hatch.python.core.PythonManager.install\")\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    helpers.write_distribution(install_dir, dist_name)\n    helpers.downgrade_distribution_metadata(install_dir / dist_name)\n\n    result = hatch(\"python\", \"install\", dist_name, input=\"n\\n\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Update {dist_name}? [y/N]: n\n        Distribution is already installed: {dist_name}\n        \"\"\"\n    )\n\n    install.assert_not_called()\n    path_append.assert_not_called()\n\n\ndef test_already_installed_update_prompt(hatch, helpers, temp_dir_data, path_append, default_shells, dist_name, mocker):\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    helpers.write_distribution(install_dir, dist_name)\n\n    dist_dir = install_dir / dist_name\n    metadata = helpers.downgrade_distribution_metadata(dist_dir)\n    python_path = dist_dir / metadata[\"python_path\"]\n    install = mocker.patch(\n        \"hatch.python.core.PythonManager.install\", return_value=mocker.MagicMock(path=dist_dir, python_path=python_path)\n    )\n\n    result = hatch(\"python\", \"install\", dist_name, input=\"y\\n\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Update {dist_name}? [y/N]: y\n        Updating {dist_name}\n        Updated {dist_name} @ {dist_dir}\n\n        The following directory has been added to your PATH (pending a shell restart):\n\n        {python_path.parent}\n        \"\"\"\n    )\n\n    install.assert_called_once_with(dist_name)\n    path_append.assert_called_once_with(str(python_path.parent), shells=default_shells)\n\n\ndef test_already_installed_update_flag(hatch, helpers, temp_dir_data, path_append, default_shells, dist_name, mocker):\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    helpers.write_distribution(install_dir, dist_name)\n\n    dist_dir = install_dir / dist_name\n    metadata = helpers.downgrade_distribution_metadata(dist_dir)\n    python_path = dist_dir / metadata[\"python_path\"]\n    install = mocker.patch(\n        \"hatch.python.core.PythonManager.install\", return_value=mocker.MagicMock(path=dist_dir, python_path=python_path)\n    )\n\n    result = hatch(\"python\", \"install\", \"--update\", dist_name)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Updating {dist_name}\n        Updated {dist_name} @ {dist_dir}\n\n        The following directory has been added to your PATH (pending a shell restart):\n\n        {python_path.parent}\n        \"\"\"\n    )\n\n    install.assert_called_once_with(dist_name)\n    path_append.assert_called_once_with(str(python_path.parent), shells=default_shells)\n\n\n@pytest.mark.parametrize(\"detector\", [\"in_current_path\", \"in_new_path\"])\ndef test_already_in_path(hatch, helpers, temp_dir_data, path_append, mocker, detector, dist_name):\n    mocker.patch(f\"userpath.{detector}\", return_value=True)\n    dist_dir = temp_dir_data / \"data\" / \"pythons\" / dist_name\n    python_path = dist_dir / get_distribution(dist_name).python_path\n    install = mocker.patch(\n        \"hatch.python.core.PythonManager.install\", return_value=mocker.MagicMock(path=dist_dir, python_path=python_path)\n    )\n\n    result = hatch(\"python\", \"install\", dist_name)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Installing {dist_name}\n        Installed {dist_name} @ {dist_dir}\n        \"\"\"\n    )\n\n    install.assert_called_once_with(dist_name)\n    path_append.assert_not_called()\n\n\ndef test_private(hatch, helpers, temp_dir_data, path_append, dist_name, mocker):\n    dist_dir = temp_dir_data / \"data\" / \"pythons\" / dist_name\n    python_path = dist_dir / get_distribution(dist_name).python_path\n    install = mocker.patch(\n        \"hatch.python.core.PythonManager.install\", return_value=mocker.MagicMock(path=dist_dir, python_path=python_path)\n    )\n\n    result = hatch(\"python\", \"install\", \"--private\", dist_name)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Installing {dist_name}\n        Installed {dist_name} @ {dist_dir}\n        \"\"\"\n    )\n\n    install.assert_called_once_with(dist_name)\n    path_append.assert_not_called()\n\n\ndef test_specific_location(hatch, helpers, temp_dir_data, path_append, dist_name, mocker):\n    install_dir = temp_dir_data / \"foo\" / \"bar\" / \"baz\"\n    dist_dir = install_dir / dist_name\n    python_path = dist_dir / get_distribution(dist_name).python_path\n    install = mocker.patch(\n        \"hatch.python.core.PythonManager.install\", return_value=mocker.MagicMock(path=dist_dir, python_path=python_path)\n    )\n\n    result = hatch(\"python\", \"install\", \"--private\", \"-d\", str(install_dir), dist_name)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Installing {dist_name}\n        Installed {dist_name} @ {dist_dir}\n        \"\"\"\n    )\n\n    install.assert_called_once_with(dist_name)\n    path_append.assert_not_called()\n\n\ndef test_all(hatch, temp_dir_data, path_append, default_shells, mocker, compatible_python_distributions):\n    mocked_dists = []\n    for name in compatible_python_distributions:\n        dist_dir = temp_dir_data / \"data\" / \"pythons\" / name\n        python_path = dist_dir / get_distribution(name).python_path\n        mocked_dists.append(mocker.MagicMock(path=dist_dir, python_path=python_path))\n\n    install = mocker.patch(\"hatch.python.core.PythonManager.install\", side_effect=mocked_dists)\n\n    result = hatch(\"python\", \"install\", \"all\")\n\n    assert result.exit_code == 0, result.output\n\n    expected_lines = []\n    for dist in mocked_dists:\n        expected_lines.extend((f\"Installing {dist.path.name}\", f\"Installed {dist.path.name} @ {dist.path}\"))\n\n    expected_lines.extend((\n        \"\",\n        \"The following directories have been added to your PATH (pending a shell restart):\",\n        \"\",\n    ))\n    expected_lines.extend(str(dist.python_path.parent) for dist in mocked_dists)\n    expected_lines.append(\"\")\n\n    assert result.output == \"\\n\".join(expected_lines)\n\n    assert install.call_args_list == [mocker.call(name) for name in compatible_python_distributions]\n    assert path_append.call_args_list == [\n        mocker.call(str(dist.python_path.parent), shells=default_shells) for dist in mocked_dists\n    ]\n"
  },
  {
    "path": "tests/cli/python/test_remove.py",
    "content": "def test_not_installed(hatch, helpers):\n    result = hatch(\"python\", \"remove\", \"3.9\", \"3.10\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Distribution is not installed: 3.9\n        Distribution is not installed: 3.10\n        \"\"\"\n    )\n\n\ndef test_basic(hatch, helpers, temp_dir_data):\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    for name in (\"3.9\", \"3.10\"):\n        helpers.write_distribution(install_dir, name)\n\n    result = hatch(\"python\", \"remove\", \"3.9\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Removing 3.9\n        \"\"\"\n    )\n\n    assert not (install_dir / \"3.9\").exists()\n    assert (install_dir / \"3.10\").is_dir()\n\n\ndef test_specific_location(hatch, helpers, temp_dir_data, dist_name):\n    install_dir = temp_dir_data / \"foo\" / \"bar\" / \"baz\"\n    helpers.write_distribution(install_dir, dist_name)\n\n    result = hatch(\"python\", \"remove\", \"-d\", str(install_dir), dist_name)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Removing {dist_name}\n        \"\"\"\n    )\n\n    assert not any(install_dir.iterdir())\n\n\ndef test_all(hatch, helpers, temp_dir_data):\n    installed_distributions = (\"3.9\", \"3.10\", \"3.11\")\n    for name in installed_distributions:\n        install_dir = temp_dir_data / \"data\" / \"pythons\"\n        helpers.write_distribution(install_dir, name)\n\n    result = hatch(\"python\", \"remove\", \"all\")\n\n    assert result.exit_code == 0, result.output\n\n    expected_lines = [f\"Removing {name}\" for name in installed_distributions]\n    expected_lines.append(\"\")\n\n    assert result.output == \"\\n\".join(expected_lines)\n"
  },
  {
    "path": "tests/cli/python/test_show.py",
    "content": "from rich.box import ASCII_DOUBLE_HEAD\nfrom rich.console import Console\nfrom rich.table import Table\n\nfrom hatch.python.resolve import get_compatible_distributions\n\n\ndef render_table(title, rows):\n    console = Console(force_terminal=False, no_color=True, legacy_windows=False)\n    table = Table(title=title, show_lines=True, title_style=\"\", box=ASCII_DOUBLE_HEAD, safe_box=True)\n\n    for column in rows[0]:\n        table.add_column(column, style=\"bold\")\n\n    for row in rows[1:]:\n        table.add_row(*row)\n\n    with console.capture() as capture:\n        console.print(table, overflow=\"ignore\", no_wrap=True, crop=False)\n\n    return capture.get()\n\n\ndef test_nothing_installed(hatch):\n    compatible_distributions = get_compatible_distributions()\n    available_table = render_table(\n        \"Available\",\n        [\n            [\"Name\", \"Version\"],\n            *[[d.name, d.version.base_version] for d in compatible_distributions.values()],\n        ],\n    )\n\n    result = hatch(\"python\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == available_table\n\n\ndef test_some_installed(hatch, helpers, temp_dir_data, dist_name):\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    helpers.write_distribution(install_dir, dist_name)\n\n    compatible_distributions = get_compatible_distributions()\n    installed_distribution = compatible_distributions.pop(dist_name)\n    installed_table = render_table(\n        \"Installed\",\n        [\n            [\"Name\", \"Version\"],\n            [dist_name, installed_distribution.version.base_version],\n        ],\n    )\n    available_table = render_table(\n        \"Available\",\n        [\n            [\"Name\", \"Version\"],\n            *[[d.name, d.version.base_version] for d in compatible_distributions.values()],\n        ],\n    )\n\n    result = hatch(\"python\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == installed_table + available_table\n\n\ndef test_all_installed(hatch, helpers, temp_dir_data):\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    compatible_distributions = get_compatible_distributions()\n    for dist_name in compatible_distributions:\n        helpers.write_distribution(install_dir, dist_name)\n\n    installed_table = render_table(\n        \"Installed\",\n        [\n            [\"Name\", \"Version\"],\n            *[[d.name, d.version.base_version] for d in compatible_distributions.values()],\n        ],\n    )\n\n    result = hatch(\"python\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == installed_table\n\n\ndef test_specific_location(hatch, helpers, temp_dir_data, dist_name):\n    install_dir = temp_dir_data / \"foo\" / \"bar\" / \"baz\"\n    helpers.write_distribution(install_dir, dist_name)\n\n    compatible_distributions = get_compatible_distributions()\n    installed_distribution = compatible_distributions.pop(dist_name)\n    installed_table = render_table(\n        \"Installed\",\n        [\n            [\"Name\", \"Version\"],\n            [dist_name, installed_distribution.version.base_version],\n        ],\n    )\n    available_table = render_table(\n        \"Available\",\n        [\n            [\"Name\", \"Version\"],\n            *[[d.name, d.version.base_version] for d in compatible_distributions.values()],\n        ],\n    )\n\n    result = hatch(\"python\", \"show\", \"--ascii\", \"-d\", str(install_dir))\n\n    assert result.exit_code == 0, result.output\n    assert result.output == installed_table + available_table\n\n\ndef test_outdated(hatch, helpers, temp_dir_data, dist_name):\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    helpers.write_distribution(install_dir, dist_name)\n    helpers.downgrade_distribution_metadata(install_dir / dist_name)\n\n    compatible_distributions = get_compatible_distributions()\n    installed_distribution = compatible_distributions.pop(dist_name)\n    installed_table = render_table(\n        \"Installed\",\n        [\n            [\"Name\", \"Version\", \"Status\"],\n            [dist_name, helpers.downgrade_version(installed_distribution.version.base_version), \"Update available\"],\n        ],\n    )\n    available_table = render_table(\n        \"Available\",\n        [\n            [\"Name\", \"Version\"],\n            *[[d.name, d.version.base_version] for d in compatible_distributions.values()],\n        ],\n    )\n\n    result = hatch(\"python\", \"show\", \"--ascii\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == installed_table + available_table\n"
  },
  {
    "path": "tests/cli/python/test_update.py",
    "content": "def test_not_installed(hatch, helpers):\n    result = hatch(\"python\", \"update\", \"3.9\", \"3.10\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Distributions not installed: 3.9, 3.10\n        \"\"\"\n    )\n\n\ndef test_basic(hatch, helpers, temp_dir_data, path_append, dist_name, mocker):\n    install_dir = temp_dir_data / \"data\" / \"pythons\"\n    helpers.write_distribution(install_dir, dist_name)\n\n    dist_dir = install_dir / dist_name\n    metadata = helpers.downgrade_distribution_metadata(dist_dir)\n    python_path = dist_dir / metadata[\"python_path\"]\n    install = mocker.patch(\n        \"hatch.python.core.PythonManager.install\", return_value=mocker.MagicMock(path=dist_dir, python_path=python_path)\n    )\n\n    result = hatch(\"python\", \"update\", dist_name)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Updating {dist_name}\n        Updated {dist_name} @ {dist_dir}\n        \"\"\"\n    )\n\n    install.assert_called_once_with(dist_name)\n    path_append.assert_not_called()\n\n\ndef test_specific_location(hatch, helpers, temp_dir_data, path_append, dist_name, mocker):\n    install = mocker.patch(\"hatch.python.core.PythonManager.install\")\n    install_dir = temp_dir_data / \"foo\" / \"bar\" / \"baz\"\n    helpers.write_distribution(install_dir, dist_name)\n\n    dist_dir = install_dir / dist_name\n    metadata = helpers.downgrade_distribution_metadata(dist_dir)\n    python_path = dist_dir / metadata[\"python_path\"]\n    install = mocker.patch(\n        \"hatch.python.core.PythonManager.install\", return_value=mocker.MagicMock(path=dist_dir, python_path=python_path)\n    )\n\n    result = hatch(\"python\", \"update\", \"-d\", str(install_dir), dist_name)\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Updating {dist_name}\n        Updated {dist_name} @ {dist_dir}\n        \"\"\"\n    )\n\n    install.assert_called_once_with(dist_name)\n    path_append.assert_not_called()\n\n\ndef test_all(hatch, helpers, temp_dir_data, path_append, mocker):\n    installed_distributions = (\"3.9\", \"3.10\", \"3.11\")\n\n    mocked_dists = []\n    for name in installed_distributions:\n        install_dir = temp_dir_data / \"data\" / \"pythons\"\n        helpers.write_distribution(install_dir, name)\n\n        dist_dir = install_dir / name\n        metadata = helpers.downgrade_distribution_metadata(dist_dir)\n        python_path = dist_dir / metadata[\"python_path\"]\n        mocked_dists.append(mocker.MagicMock(path=dist_dir, python_path=python_path))\n\n    install = mocker.patch(\"hatch.python.core.PythonManager.install\", side_effect=mocked_dists)\n\n    result = hatch(\"python\", \"update\", \"all\")\n\n    assert result.exit_code == 0, result.output\n\n    expected_lines = []\n    for dist in mocked_dists:\n        expected_lines.extend((f\"Updating {dist.path.name}\", f\"Updated {dist.path.name} @ {dist.path}\"))\n    expected_lines.append(\"\")\n\n    assert result.output == \"\\n\".join(expected_lines)\n\n    assert install.call_args_list == [mocker.call(name) for name in installed_distributions]\n    path_append.assert_not_called()\n"
  },
  {
    "path": "tests/cli/run/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/run/test_run.py",
    "content": "import os\nimport sys\nimport sysconfig\n\nimport pytest\n\nfrom hatch.config.constants import AppEnvVars, ConfigEnvVars\nfrom hatch.project.core import Project\nfrom hatch.python.core import PythonManager\nfrom hatch.python.resolve import get_compatible_distributions\nfrom hatch.utils.fs import Path\nfrom hatch.utils.structures import EnvVars\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE\n\nFREE_THREADED_BUILD = bool(sysconfig.get_config_var(\"Py_GIL_DISABLED\"))\n\n\n@pytest.fixture(scope=\"module\")\ndef available_python_version():\n    compatible_distributions = get_compatible_distributions()\n    current_version = f\"{sys.version_info.major}.{sys.version_info.minor}\"\n    if current_version in compatible_distributions:\n        return current_version\n\n    versions = [d for d in get_compatible_distributions() if not d.startswith(\"pypy\")]\n    return versions[-1]\n\n\ndef test_help(hatch):\n    short = hatch(\"run\", \"-h\")\n    assert short.exit_code == 0, short.output\n\n    long = hatch(\"run\", \"--help\")\n    assert long.exit_code == 0, long.output\n\n    assert short.output == long.output\n\n\ndef test_automatic_creation(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"python\", \"-c\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n\ndef test_no_compatibility_check_if_exists(hatch, helpers, temp_dir, config_file, mocker):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"python\", \"-c\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n    output_file.unlink()\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment.check_compatibility\", side_effect=Exception(\"incompatible\"))\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"python\", \"-c\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\")\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n    assert str(env_path) in str(output_file.read_text())\n\n\ndef test_enter_project_directory(hatch, config_file, helpers, temp_dir):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = \"foo\"\n    config_file.model.mode = \"project\"\n    config_file.model.project = project\n    config_file.model.projects = {project: str(project_path)}\n    config_file.save()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with EnvVars({ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"python\", \"-c\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n\n@pytest.mark.requires_internet\ndef test_sync_dependencies(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"default\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"dependencies\": [\"binary\"], **project.config.envs[\"default\"]}\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"python\",\n            \"-c\",\n            \"import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    assert str(output_file.read_text()) == \"(1.0, 'KiB')\"\n\n\n@pytest.mark.requires_internet\ndef test_sync_project_dependencies(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"default\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Installing project in development mode\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"dependencies\"] = [\"binary\"]\n    project.save_config(config)\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"python\",\n            \"-c\",\n            \"import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    assert str(output_file.read_text()) == \"(1.0, 'KiB')\"\n\n\n@pytest.mark.requires_internet\ndef test_sync_project_features(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"optional-dependencies\"] = {\"foo\": []}\n    project.save_config(config)\n    helpers.update_project_environment(project, \"default\", {\"features\": [\"foo\"], **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"default\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Installing project in development mode\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"optional-dependencies\"][\"foo\"].append(\"binary\")\n    project.save_config(config)\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"python\",\n            \"-c\",\n            \"import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    assert str(output_file.read_text()) == \"(1.0, 'KiB')\"\n\n\n@pytest.mark.requires_internet\ndef test_dependency_hash_checking(hatch, helpers, temp_dir, config_file, mocker):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"default\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"dependencies\": [\"binary\"], **project.config.envs[\"default\"]}\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"python\",\n            \"-c\",\n            \"import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    assert str(output_file.read_text()) == \"(1.0, 'KiB')\"\n    output_file.unlink()\n\n    # Now there should be no output because there is no dependency checking\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"python\",\n            \"-c\",\n            \"import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    assert str(output_file.read_text()) == \"(1.0, 'KiB')\"\n    output_file.unlink()\n\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment.dependencies_in_sync\", return_value=False)\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment.dependency_hash\", side_effect=[\"foo\", \"bar\", \"bar\"])\n\n    # Ensure that the saved value is the second value\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"python\",\n            \"-c\",\n            \"import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Checking dependencies\n        Syncing dependencies\n        \"\"\"\n    )\n\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    assert str(output_file.read_text()) == \"(1.0, 'KiB')\"\n    output_file.unlink()\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"python\",\n            \"-c\",\n            \"import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    assert str(output_file.read_text()) == \"(1.0, 'KiB')\"\n\n\ndef test_scripts(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"skip-install\": True, \"scripts\": {\"py\": \"python -c {args}\"}, **project.config.envs[\"default\"]},\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"py\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n\ndef test_scripts_specific_environment(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"skip-install\": True, \"scripts\": {\"py\": \"python -c {args}\"}, **project.config.envs[\"default\"]},\n    )\n    helpers.update_project_environment(project, \"test\", {\"env-vars\": {\"foo\": \"bar\"}})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"test:py\",\n            \"import os,pathlib,sys;pathlib.Path('test.txt').write_text(\"\n            \"sys.executable+os.linesep[-1]+os.environ['foo'])\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: test\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == \"test\"\n\n    python_executable_path, env_var_value = str(output_file.read_text()).splitlines()\n    assert str(env_path) in python_executable_path\n    assert env_var_value == \"bar\"\n\n\n@pytest.mark.requires_internet\ndef test_scripts_no_environment(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"tool\"][\"hatch\"][\"scripts\"] = {\"py\": \"python -c {args}\"}\n    project.save_config(config)\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \":py\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Checking dependencies\n        \"\"\"\n    )\n\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert not env_data_path.exists()\n\n    assert os.path.realpath(output_file.read_text().strip()).lower() == os.path.realpath(sys.executable).lower()\n\n\ndef test_error(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"skip-install\": True,\n            \"scripts\": {\n                \"error\": [\n                    'python -c \"import sys;sys.exit(3)\"',\n                    \"python -c \\\"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\\\"\",\n                ],\n            },\n            **project.config.envs[\"default\"],\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"error\")\n\n    assert result.exit_code == 3\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        cmd [1] | python -c \"import sys;sys.exit(3)\"\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert not output_file.is_file()\n\n\ndef test_ignore_error(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"skip-install\": True,\n            \"scripts\": {\n                \"error\": [\n                    '- python -c \"import sys;sys.exit(3)\"',\n                    \"python -c \\\"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\\\"\",\n                ],\n            },\n            **project.config.envs[\"default\"],\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"error\")\n\n    assert result.exit_code == 0\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        cmd [1] | - python -c \"import sys;sys.exit(3)\"\n        cmd [2] | python -c \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\"\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n\ndef test_command_expansion_error(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"skip-install\": True, \"scripts\": {\"error\": [\"echo {env:FOOBAR}\"]}, **project.config.envs[\"default\"]},\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"error\")\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        Nonexistent environment variable must set a default: FOOBAR\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert not output_file.is_file()\n\n\ndef test_verbosity(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"skip-install\": True,\n            \"scripts\": {\n                \"write-exe\": [\n                    \"python -c \\\"import pathlib,sys;pathlib.Path('{args}1.txt').write_text(sys.executable)\\\"\",\n                    \"python -c \\\"import pathlib,sys;pathlib.Path('{args}2.txt').write_text(sys.executable)\\\"\",\n                    \"python -c \\\"import pathlib,sys;pathlib.Path('{args}3.txt').write_text(sys.executable)\\\"\",\n                ],\n            },\n            **project.config.envs[\"default\"],\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"-v\", \"run\", \"write-exe\", \"test\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        ─────────────────────────────────── default ────────────────────────────────────\n        Creating environment: default\n        Checking dependencies\n        cmd [1] | python -c \"import pathlib,sys;pathlib.Path('test1.txt').write_text(sys.executable)\"\n        cmd [2] | python -c \"import pathlib,sys;pathlib.Path('test2.txt').write_text(sys.executable)\"\n        cmd [3] | python -c \"import pathlib,sys;pathlib.Path('test3.txt').write_text(sys.executable)\"\n        \"\"\"\n    )\n    output_files = []\n    for i in range(1, 4):\n        output_file = project_path / f\"test{i}.txt\"\n        assert output_file.is_file()\n        output_files.append(output_file)\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    for output_file in output_files:\n        assert str(env_path) in str(output_file.read_text())\n\n\ndef test_matrix_no_environments(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": []})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"test:python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        No variables defined for matrix: test\n        \"\"\"\n    )\n\n\ndef test_matrix(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"test:python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        ────────────────────────────────── test.9000 ───────────────────────────────────\n        Creating environment: test.9000\n        Checking dependencies\n        ─────────────────────────────────── test.42 ────────────────────────────────────\n        Creating environment: test.42\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = sorted(storage_path.iterdir(), key=lambda d: d.name)[::-1]\n    assert len(env_dirs) == 2\n\n    python_path1, python_path2 = str(output_file.read_text()).splitlines()\n    for python_path, env_dir, env_name in zip(\n        (python_path1, python_path2), env_dirs, (\"test.9000\", \"test.42\"), strict=False\n    ):\n        assert env_dir.name == env_name\n        assert str(env_dir) in python_path\n\n\ndef test_incompatible_single(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"skip-install\": True, \"platforms\": [\"foo\"], **project.config.envs[\"default\"]}\n    )\n    helpers.update_project_environment(project, \"test\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"test:python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 1\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `test` is incompatible: unsupported platform\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert not output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert not env_data_path.is_dir()\n\n\ndef test_incompatible_matrix_full(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project, \"default\", {\"skip-install\": True, \"platforms\": [\"foo\"], **project.config.envs[\"default\"]}\n    )\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"test:python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Skipped 2 incompatible environments:\n        test.9000 -> unsupported platform\n        test.42 -> unsupported platform\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert not output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert not env_data_path.is_dir()\n\n\ndef test_incompatible_matrix_partial(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(\n        project,\n        \"test\",\n        {\n            \"matrix\": [{\"version\": [\"9000\", \"42\"]}],\n            \"overrides\": {\"matrix\": {\"version\": {\"platforms\": [{\"value\": \"foo\", \"if\": [\"9000\"]}]}}},\n        },\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"test:python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        ─────────────────────────────────── test.42 ────────────────────────────────────\n        Creating environment: test.42\n        Checking dependencies\n\n        Skipped 1 incompatible environment:\n        test.9000 -> unsupported platform\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n    assert env_path.name == \"test.42\"\n\n    python_path = str(output_file.read_text()).strip()\n    assert str(env_path) in python_path\n\n\ndef test_incompatible_missing_python(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    known_version = \"\".join(map(str, sys.version_info[:2]))\n    if FREE_THREADED_BUILD:\n        known_version += \"t\"\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"python\": [known_version, \"9000\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"test:python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n    padding = \"─\"\n    if FREE_THREADED_BUILD:\n        pre_padding = \"\"\n    else:\n        pre_padding = \"─\"\n        if len(known_version) < 3:\n            padding += \"─\"\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        ─────────────────────────────────{pre_padding} test.py{known_version} ─────────────────────────────────{padding}\n        Creating environment: test.py{known_version}\n        Checking dependencies\n\n        Skipped 1 incompatible environment:\n        test.py9000 -> cannot locate Python: 9000\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n    assert env_path.name == f\"test.py{known_version}\"\n\n    python_path = str(output_file.read_text()).strip()\n    assert str(env_path) in python_path\n\n\ndef test_env_detection(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"foo\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: foo\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = sorted(storage_path.iterdir(), key=lambda d: d.name)\n    assert len(env_dirs) == 2\n\n    assert env_dirs[0].name == \"foo\"\n    assert env_dirs[1].name == \"my-app\"\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path), AppEnvVars.ENV_ACTIVE: \"foo\"}):\n        result = hatch(\"run\", \"python\", \"-c\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\")\n\n    assert result.exit_code == 0, result.output\n\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    python_path = str(output_file.read_text()).strip()\n    assert str(env_dirs[0]) in python_path\n\n\ndef test_env_detection_override(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"foo\", {})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"env\", \"create\", \"foo\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: foo\n        Checking dependencies\n        \"\"\"\n    )\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = sorted(storage_path.iterdir(), key=lambda d: d.name)\n    assert len(env_dirs) == 2\n\n    assert env_dirs[0].name == \"foo\"\n    assert env_dirs[1].name == \"my-app\"\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path), AppEnvVars.ENV_ACTIVE: \"foo\"}):\n        result = hatch(\n            \"run\", \"default:python\", \"-c\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\"\n        )\n\n    assert result.exit_code == 0, result.output\n\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    python_path = str(output_file.read_text()).strip()\n    assert str(env_dirs[1]) in python_path\n\n\ndef test_matrix_variable_selection_no_command(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"+version=9000\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Missing argument `MATRIX:ARGS...`\n        \"\"\"\n    )\n\n\ndef test_matrix_variable_selection_duplicate_inclusion(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"+version=9000\",\n            \"+version=42\",\n            \"python\",\n            \"-c\",\n            \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\",\n        )\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Duplicate included variable: version\n        \"\"\"\n    )\n\n\ndef test_matrix_variable_selection_duplicate_exclusion(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"-version=9000\",\n            \"-version=42\",\n            \"python\",\n            \"-c\",\n            \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\",\n        )\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Duplicate excluded variable: version\n        \"\"\"\n    )\n\n\ndef test_matrix_variable_selection_python_alias(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"python\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"+py=9000\",\n            \"+python=42\",\n            \"python\",\n            \"-c\",\n            \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\",\n        )\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Duplicate included variable: python\n        \"\"\"\n    )\n\n\ndef test_matrix_variable_selection_not_matrix(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"+version=9000\",\n            \"python\",\n            \"-c\",\n            \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\",\n        )\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Variable selection is unsupported for non-matrix environments: default\n        \"\"\"\n    )\n\n\ndef test_matrix_variable_selection_inclusion(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"+version=9000\",\n            \"test:python\",\n            \"-c\",\n            \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        ────────────────────────────────── test.9000 ───────────────────────────────────\n        Creating environment: test.9000\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n    assert env_path.name == \"test.9000\"\n\n    python_path = str(output_file.read_text()).strip()\n    assert str(env_path) in python_path\n\n\ndef test_matrix_variable_selection_exclusion(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"-version=9000\",\n            \"test:python\",\n            \"-c\",\n            \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        ─────────────────────────────────── test.42 ────────────────────────────────────\n        Creating environment: test.42\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n    assert env_path.name == \"test.42\"\n\n    python_path = str(output_file.read_text()).strip()\n    assert str(env_path) in python_path\n\n\ndef test_matrix_variable_selection_exclude_all(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"-version\",\n            \"test:python\",\n            \"-c\",\n            \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\",\n        )\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        No environments were selected\n        \"\"\"\n    )\n\n\ndef test_matrix_variable_selection_include_none(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(project, \"test\", {\"matrix\": [{\"version\": [\"9000\", \"42\"]}]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"+version=3.14\",\n            \"test:python\",\n            \"-c\",\n            \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\",\n        )\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        No environments were selected\n        \"\"\"\n    )\n\n\ndef test_matrix_variable_selection_inclusion_multiple_variables(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n    helpers.update_project_environment(\n        project, \"test\", {\"matrix\": [{\"version1\": [\"9000\", \"42\"], \"version2\": [\"3.14\"]}]}\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\",\n            \"+version1=9000\",\n            \"test:python\",\n            \"-c\",\n            \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\",\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        ──────────────────────────────── test.9000-3.14 ────────────────────────────────\n        Creating environment: test.9000-3.14\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n    assert env_path.name == \"test.9000-3.14\"\n\n    python_path = str(output_file.read_text()).strip()\n    assert str(env_path) in python_path\n\n\ndef test_context_formatting_recursion(hatch, helpers, temp_dir, config_file):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\n            \"skip-install\": True,\n            \"scripts\": {\"py\": 'python -c \"{env:FOO:{env:BAR:{env:BAZ}}}\"'},\n            **project.config.envs[\"default\"],\n        },\n    )\n\n    with project_path.as_cwd(\n        env_vars={\n            ConfigEnvVars.DATA: str(data_path),\n            \"BAZ\": \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\",\n        }\n    ):\n        result = hatch(\"run\", \"py\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir, config_file, mock_plugin_installation):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependency = os.urandom(16).hex()\n    (project_path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"run\", \"python\", \"-c\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        Creating environment: default\n        Checking dependencies\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n\n@pytest.mark.requires_internet\ndef test_install_python_specific(hatch, helpers, temp_dir, config_file, mocker, available_python_version):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"skip-install\": True, \"python\": available_python_version, **project.config.envs[\"default\"]},\n    )\n\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible\", return_value=False)\n    manager = PythonManager(data_path / \"env\" / \"virtual\" / \".pythons\")\n    assert not manager.get_installed()\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: default\n        Installing Python distribution: {available_python_version}\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n    assert list(manager.get_installed()) == [available_python_version]\n\n\n@pytest.mark.requires_internet\ndef test_update_python_specific(hatch, helpers, temp_dir, config_file, mocker, available_python_version):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"skip-install\": True, \"python\": available_python_version, **project.config.envs[\"default\"]},\n    )\n\n    install_dir = data_path / \"env\" / \"virtual\" / \".pythons\"\n    manager = PythonManager(install_dir)\n    dist = manager.install(available_python_version)\n    helpers.downgrade_distribution_metadata(install_dir / available_python_version)\n    mocker.patch(\n        \"hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible\",\n        side_effect=lambda interpreter: Path(interpreter.executable) == dist.python_path,\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: default\n        Updating Python distribution: {available_python_version}\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n\n@pytest.mark.requires_internet\ndef test_install_python_max_compatible(hatch, helpers, temp_dir, config_file, mocker, available_python_version):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible\", return_value=False)\n    manager = PythonManager(data_path / \"env\" / \"virtual\" / \".pythons\")\n    assert not manager.get_installed()\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: default\n        Installing Python distribution: {available_python_version}\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n    assert list(manager.get_installed()) == [available_python_version]\n\n\n@pytest.mark.requires_internet\ndef test_update_python_max_compatible(hatch, helpers, temp_dir, config_file, mocker, available_python_version):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    helpers.update_project_environment(project, \"default\", {\"skip-install\": True, **project.config.envs[\"default\"]})\n\n    install_dir = data_path / \"env\" / \"virtual\" / \".pythons\"\n    manager = PythonManager(install_dir)\n    dist = manager.install(available_python_version)\n    helpers.downgrade_distribution_metadata(install_dir / available_python_version)\n    mocker.patch(\n        \"hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible\",\n        side_effect=lambda interpreter: Path(interpreter.executable) == dist.python_path,\n    )\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: default\n        Updating Python distribution: {available_python_version}\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n\n@pytest.mark.requires_internet\ndef test_python_installation_with_metadata_hook(\n    hatch, helpers, temp_dir, config_file, mocker, available_python_version\n):\n    config_file.model.template.plugins[\"default\"][\"tests\"] = False\n    config_file.save()\n\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n\n    assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"].append(\"foo\")\n    config[\"tool\"][\"hatch\"][\"metadata\"] = {\"hooks\": {\"custom\": {\"dependencies\": [\"binary\"]}}}\n    project.save_config(config)\n\n    helpers.update_project_environment(\n        project,\n        \"default\",\n        {\"skip-install\": True, \"python\": available_python_version, **project.config.envs[\"default\"]},\n    )\n\n    build_script = project_path / DEFAULT_BUILD_SCRIPT\n    build_script.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatchling.metadata.plugin.interface import MetadataHookInterface\n\n            class CustomMetadataHook(MetadataHookInterface):\n                def update(self, metadata):\n                    import binary\n            \"\"\"\n        )\n    )\n\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible\", return_value=False)\n    manager = PythonManager(data_path / \"env\" / \"virtual\" / \".pythons\")\n    assert not manager.get_installed()\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\n            \"run\", \"python\", \"-c\", \"import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])\"\n        )\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        f\"\"\"\n        Creating environment: default\n        Installing Python distribution: {available_python_version}\n        Checking dependencies\n        \"\"\"\n    )\n    output_file = project_path / \"test.txt\"\n    assert output_file.is_file()\n\n    env_data_path = data_path / \"env\" / \"virtual\"\n    assert env_data_path.is_dir()\n\n    project_data_path = env_data_path / project_path.name\n    assert project_data_path.is_dir()\n\n    storage_dirs = list(project_data_path.iterdir())\n    assert len(storage_dirs) == 1\n\n    storage_path = storage_dirs[0]\n    assert len(storage_path.name) == 8\n\n    env_dirs = list(storage_path.iterdir())\n    assert len(env_dirs) == 1\n\n    env_path = env_dirs[0]\n\n    assert env_path.name == project_path.name\n\n    assert str(env_path) in str(output_file.read_text())\n\n    assert list(manager.get_installed()) == [available_python_version]\n\n\nclass TestScriptRunner:\n    @pytest.mark.requires_internet\n    def test_not_file(self, hatch, helpers, temp_dir):\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        helpers.update_project_environment(\n            project,\n            \"default\",\n            {\"skip-install\": True, \"scripts\": {\"script.py\": \"python -c {args}\"}, **project.config.envs[\"default\"]},\n        )\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"run\", \"script.py\", \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            Creating environment: default\n            Checking dependencies\n            \"\"\"\n        )\n        output_file = project_path / \"test.txt\"\n        assert output_file.is_file()\n\n        env_data_path = data_path / \"env\" / \"virtual\"\n        assert env_data_path.is_dir()\n\n        project_data_path = env_data_path / project_path.name\n        assert project_data_path.is_dir()\n\n        storage_dirs = list(project_data_path.iterdir())\n        assert len(storage_dirs) == 1\n\n        storage_path = storage_dirs[0]\n        assert len(storage_path.name) == 8\n\n        env_dirs = list(storage_path.iterdir())\n        assert len(env_dirs) == 1\n\n        env_path = env_dirs[0]\n\n        assert env_path.name == project_path.name\n\n        assert str(env_path) in str(output_file.read_text())\n\n    @pytest.mark.requires_internet\n    def test_dependencies(self, hatch, helpers, temp_dir):\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n        script = (temp_dir / \"script.py\").resolve()\n        script.write_text(\n            helpers.dedent(\n                \"\"\"\n                # /// script\n                # dependencies = [\n                #   \"binary\",\n                # ]\n                # ///\n                import pathlib\n                import sys\n\n                import binary\n\n                pathlib.Path('test.txt').write_text(\n                    f'{sys.executable}\\\\n{str(binary.convert_units(1024))}'\n                )\n                \"\"\"\n            )\n        )\n\n        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"run\", \"script.py\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Creating environment: {script.id}\n            Checking dependencies\n            Syncing dependencies\n            \"\"\"\n        )\n        output_file = temp_dir / \"test.txt\"\n        assert output_file.is_file()\n\n        env_data_path = data_path / \"env\" / \"virtual\" / \".scripts\"\n        assert env_data_path.is_dir()\n\n        env_path = env_data_path / script.id\n        assert env_path.is_dir()\n        assert env_path.name == script.id\n\n        executable_path, unit_conversion = output_file.read_text().splitlines()\n        executable = Path(executable_path)\n\n        assert executable.is_file()\n        assert data_path in executable.parents\n        assert unit_conversion == \"(1.0, 'KiB')\"\n\n    @pytest.mark.requires_internet\n    def test_dependencies_from_tool_config(self, hatch, helpers, temp_dir):\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n        script = (temp_dir / \"script.py\").resolve()\n        script.write_text(\n            helpers.dedent(\n                \"\"\"\n                # /// script\n                # dependencies = []\n                #\n                # [tool.hatch]\n                # dependencies = [\n                #   \"binary\",\n                # ]\n                # ///\n                import pathlib\n                import sys\n\n                import binary\n\n                pathlib.Path('test.txt').write_text(\n                    f'{sys.executable}\\\\n{str(binary.convert_units(1024))}'\n                )\n                \"\"\"\n            )\n        )\n\n        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"run\", \"script.py\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Creating environment: {script.id}\n            Checking dependencies\n            Syncing dependencies\n            \"\"\"\n        )\n        output_file = temp_dir / \"test.txt\"\n        assert output_file.is_file()\n\n        env_data_path = data_path / \"env\" / \"virtual\" / \".scripts\"\n        assert env_data_path.is_dir()\n\n        env_path = env_data_path / script.id\n        assert env_path.is_dir()\n        assert env_path.name == script.id\n\n        executable_path, unit_conversion = output_file.read_text().splitlines()\n        executable = Path(executable_path)\n\n        assert executable.is_file()\n        assert data_path in executable.parents\n        assert unit_conversion == \"(1.0, 'KiB')\"\n\n    def test_unsupported_python_version(self, hatch, helpers, temp_dir):\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n        script = (temp_dir / \"script.py\").resolve()\n        script.write_text(\n            helpers.dedent(\n                \"\"\"\n                # /// script\n                # requires-python = \">9000\"\n                # ///\n                import pathlib\n                import sys\n\n                pathlib.Path('test.txt').write_text(sys.executable)\n                \"\"\"\n            )\n        )\n\n        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"run\", \"script.py\")\n\n        assert result.exit_code == 1, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            Unable to satisfy Python version constraint: >9000\n            \"\"\"\n        )\n\n    @pytest.mark.requires_internet\n    def test_python_version_constraint(self, hatch, helpers, temp_dir):\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n        script = (temp_dir / \"script.py\").resolve()\n\n        # Cap the range at the current minor version so that the current Python\n        # will be used and distributions don't have to be downloaded\n        major, minor = sys.version_info[:2]\n        minor += 1\n\n        script.write_text(\n            helpers.dedent(\n                f\"\"\"\n                # /// script\n                # requires-python = \"<{major}.{minor}\"\n                # ///\n                import pathlib\n                import sys\n\n                pathlib.Path('test.txt').write_text(sys.executable)\n                \"\"\"\n            )\n        )\n\n        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"run\", \"script.py\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Creating environment: {script.id}\n            Checking dependencies\n            \"\"\"\n        )\n        output_file = temp_dir / \"test.txt\"\n        assert output_file.is_file()\n\n        env_data_path = data_path / \"env\" / \"virtual\" / \".scripts\"\n        assert env_data_path.is_dir()\n\n        env_path = env_data_path / script.id\n        assert env_path.is_dir()\n        assert env_path.name == script.id\n\n        executable = Path(output_file.read_text())\n        assert executable.is_file()\n        assert data_path in executable.parents\n\n    def test_python_version_constraint_from_tool_config(self, hatch, helpers, temp_dir):\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n        script = (temp_dir / \"script.py\").resolve()\n\n        # Use the current minor version so that the current Python\n        # will be used and distributions don't have to be downloaded\n        major, minor = sys.version_info[:2]\n        python_version = f\"{major}.{minor}\"\n        if FREE_THREADED_BUILD:\n            python_version += \"t\"\n\n        script.write_text(\n            helpers.dedent(\n                f\"\"\"\n                # /// script\n                # requires-python = \">9000\"\n                #\n                # [tool.hatch]\n                # python = \"{python_version}\"\n                # ///\n                import pathlib\n                import sys\n\n                pathlib.Path('test.txt').write_text(sys.executable)\n                \"\"\"\n            )\n        )\n\n        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"run\", \"script.py\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Creating environment: {script.id}\n            Checking dependencies\n            \"\"\"\n        )\n        output_file = temp_dir / \"test.txt\"\n        assert output_file.is_file()\n\n        env_data_path = data_path / \"env\" / \"virtual\" / \".scripts\"\n        assert env_data_path.is_dir()\n\n        env_path = env_data_path / script.id\n        assert env_path.is_dir()\n        assert env_path.name == script.id\n\n        executable = Path(output_file.read_text())\n        assert executable.is_file()\n        assert data_path in executable.parents\n"
  },
  {
    "path": "tests/cli/self/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/self/test_report.py",
    "content": "import os\nimport sys\nfrom textwrap import indent\nfrom urllib.parse import unquote_plus\n\nimport pytest\n\nfrom hatch._version import __version__\nfrom hatch.utils.structures import EnvVars\n\nURL = \"https://github.com/pypa/hatch/issues/new?body=\"\nSTATIC_BODY = \"\"\"\\\n## Current behavior\n<!-- A clear and concise description of the behavior. -->\n\n## Expected behavior\n<!-- A clear and concise description of what you expected to happen. -->\n\n## Additional context\n<!-- Add any other context about the problem here. If applicable, add screenshots to help explain. -->\n\"\"\"\n\n\ndef assert_call(open_new_tab, expected_body):\n    assert len(open_new_tab.mock_calls) == 1\n    assert len(open_new_tab.mock_calls[0].args) == 1\n\n    url = open_new_tab.mock_calls[0].args[0]\n    assert url.startswith(URL)\n\n    body = unquote_plus(url[len(URL) :])\n    assert body == expected_body\n\n\nclass TestDefault:\n    def test_open(self, hatch, mocker, platform):\n        open_new_tab = mocker.patch(\"webbrowser.open_new_tab\")\n        result = hatch(os.environ[\"PYAPP_COMMAND_NAME\"], \"report\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        expected_body = f\"\"\"\\\n{STATIC_BODY}\n## Debug\n\n### Installation\n\n- Source: pip\n- Version: {__version__}\n- Platform: {platform.display_name}\n- Python version:\n    ```\n{indent(sys.version, \" \" * 4)}\n    ```\n\n### Configuration\n\n```toml\nmode = \"local\"\nshell = \"\"\n```\n\"\"\"\n\n        assert_call(open_new_tab, expected_body)\n\n    def test_no_open(self, hatch, platform):\n        result = hatch(os.environ[\"PYAPP_COMMAND_NAME\"], \"report\", \"--no-open\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output.startswith(URL)\n\n        body = unquote_plus(result.output.rstrip()[len(URL) :])\n        assert (\n            body\n            == f\"\"\"\\\n{STATIC_BODY}\n## Debug\n\n### Installation\n\n- Source: pip\n- Version: {__version__}\n- Platform: {platform.display_name}\n- Python version:\n    ```\n{indent(sys.version, \" \" * 4)}\n    ```\n\n### Configuration\n\n```toml\nmode = \"local\"\nshell = \"\"\n```\n\"\"\"\n        )\n\n\ndef test_binary(hatch, mocker, platform, temp_dir):\n    mock_executable = temp_dir / \"exe\"\n    mock_executable.touch()\n    mocker.patch(\"sys.executable\", str(mock_executable))\n    mocker.patch(\"platformdirs.user_data_dir\", return_value=str(temp_dir))\n    open_new_tab = mocker.patch(\"webbrowser.open_new_tab\")\n\n    result = hatch(os.environ[\"PYAPP_COMMAND_NAME\"], \"report\")\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n    expected_body = f\"\"\"\\\n{STATIC_BODY}\n## Debug\n\n### Installation\n\n- Source: binary\n- Version: {__version__}\n- Platform: {platform.display_name}\n- Python version:\n    ```\n{indent(sys.version, \" \" * 4)}\n    ```\n\n### Configuration\n\n```toml\nmode = \"local\"\nshell = \"\"\n```\n\"\"\"\n\n    assert_call(open_new_tab, expected_body)\n\n\ndef test_pipx(hatch, mocker, platform, temp_dir):\n    mock_executable = temp_dir / \".local\" / \"pipx\" / \"venvs\" / \"exe\"\n    mock_executable.parent.ensure_dir_exists()\n    mock_executable.touch()\n    mocker.patch(\"sys.executable\", str(mock_executable))\n    mocker.patch(\"pathlib.Path.home\", return_value=temp_dir)\n    open_new_tab = mocker.patch(\"webbrowser.open_new_tab\")\n\n    result = hatch(os.environ[\"PYAPP_COMMAND_NAME\"], \"report\")\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n    expected_body = f\"\"\"\\\n{STATIC_BODY}\n## Debug\n\n### Installation\n\n- Source: pipx\n- Version: {__version__}\n- Platform: {platform.display_name}\n- Python version:\n    ```\n{indent(sys.version, \" \" * 4)}\n    ```\n\n### Configuration\n\n```toml\nmode = \"local\"\nshell = \"\"\n```\n\"\"\"\n\n    assert_call(open_new_tab, expected_body)\n\n\ndef test_system(hatch, mocker, platform, temp_dir):\n    indicator = temp_dir / \"EXTERNALLY-MANAGED\"\n    indicator.touch()\n    mocker.patch(\"sysconfig.get_path\", return_value=str(temp_dir))\n    mocker.patch(\"sys.prefix\", \"foo\")\n    mocker.patch(\"sys.base_prefix\", \"foo\")\n    open_new_tab = mocker.patch(\"webbrowser.open_new_tab\")\n\n    result = hatch(os.environ[\"PYAPP_COMMAND_NAME\"], \"report\")\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n    expected_body = f\"\"\"\\\n{STATIC_BODY}\n## Debug\n\n### Installation\n\n- Source: system\n- Version: {__version__}\n- Platform: {platform.display_name}\n- Python version:\n    ```\n{indent(sys.version, \" \" * 4)}\n    ```\n\n### Configuration\n\n```toml\nmode = \"local\"\nshell = \"\"\n```\n\"\"\"\n\n    assert_call(open_new_tab, expected_body)\n\n\n@pytest.mark.requires_windows\ndef test_windows_store(hatch, mocker, platform, temp_dir):\n    mock_executable = temp_dir / \"WindowsApps\" / \"python.exe\"\n    mock_executable.parent.ensure_dir_exists()\n    mock_executable.touch()\n    mocker.patch(\"sys.executable\", str(mock_executable))\n    open_new_tab = mocker.patch(\"webbrowser.open_new_tab\")\n\n    result = hatch(os.environ[\"PYAPP_COMMAND_NAME\"], \"report\")\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n    expected_body = f\"\"\"\\\n{STATIC_BODY}\n## Debug\n\n### Installation\n\n- Source: Windows Store\n- Version: {__version__}\n- Platform: {platform.display_name}\n- Python version:\n    ```\n{indent(sys.version, \" \" * 4)}\n    ```\n\n### Configuration\n\n```toml\nmode = \"local\"\nshell = \"\"\n```\n\"\"\"\n\n    assert_call(open_new_tab, expected_body)\n\n\n@pytest.mark.requires_unix\ndef test_pyenv(hatch, mocker, platform, temp_dir):\n    mock_executable = temp_dir / \"exe\"\n    mock_executable.parent.ensure_dir_exists()\n    mock_executable.touch()\n    mocker.patch(\"sys.executable\", str(mock_executable))\n    open_new_tab = mocker.patch(\"webbrowser.open_new_tab\")\n\n    with EnvVars({\"PYENV_ROOT\": str(temp_dir)}):\n        result = hatch(os.environ[\"PYAPP_COMMAND_NAME\"], \"report\")\n\n    assert result.exit_code == 0, result.output\n    assert not result.output\n\n    expected_body = f\"\"\"\\\n{STATIC_BODY}\n## Debug\n\n### Installation\n\n- Source: Pyenv\n- Version: {__version__}\n- Platform: {platform.display_name}\n- Python version:\n    ```\n{indent(sys.version, \" \" * 4)}\n    ```\n\n### Configuration\n\n```toml\nmode = \"local\"\nshell = \"\"\n```\n\"\"\"\n\n    assert_call(open_new_tab, expected_body)\n"
  },
  {
    "path": "tests/cli/self/test_self.py",
    "content": "import os\n\n\ndef test(hatch):\n    result = hatch(os.environ[\"PYAPP_COMMAND_NAME\"], \"-h\")\n\n    assert result.exit_code == 0, result.output\n"
  },
  {
    "path": "tests/cli/status/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/status/test_status.py",
    "content": "import pytest\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.utils.fs import temp_chdir\nfrom hatch.utils.structures import EnvVars\n\n\nclass TestModeLocalDefault:\n    def test_no_project(self, hatch, isolation, config_file, helpers):\n        result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            [Project] - <no project detected>\n            [Location] - {isolation}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    def test_found_project(self, hatch, temp_dir, config_file, helpers):\n        project_file = temp_dir / \"pyproject.toml\"\n        project_file.touch()\n\n        with temp_dir.as_cwd():\n            result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            [Project] - {temp_dir.name} (current directory)\n            [Location] - {temp_dir}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n\nclass TestProjectExplicit:\n    @pytest.mark.parametrize(\"file_name\", [\"pyproject.toml\", \"setup.py\"])\n    def test_found_project_flag(self, hatch, temp_dir, config_file, helpers, file_name):\n        project_file = temp_dir / file_name\n        project_file.touch()\n\n        project = \"foo\"\n        config_file.model.projects = {project: str(temp_dir)}\n        config_file.save()\n\n        result = hatch(\"-p\", project, \"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            [Project] - {project}\n            [Location] - {temp_dir}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    @pytest.mark.parametrize(\"file_name\", [\"pyproject.toml\", \"setup.py\"])\n    def test_found_project_env(self, hatch, temp_dir, config_file, helpers, file_name):\n        project_file = temp_dir / file_name\n        project_file.touch()\n\n        project = \"foo\"\n        config_file.model.projects = {project: str(temp_dir)}\n        config_file.save()\n\n        with EnvVars({ConfigEnvVars.PROJECT: project}):\n            result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            [Project] - {project}\n            [Location] - {temp_dir}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    def test_unknown_project(self, hatch):\n        project = \"foo\"\n        result = hatch(\"-p\", project, \"status\")\n\n        assert result.exit_code == 1\n        assert result.output == f\"Unable to locate project {project}\\n\"\n\n    def test_not_a_project(self, hatch, temp_dir, config_file):\n        project = \"foo\"\n        config_file.model.project = project\n        config_file.model.projects = {project: str(temp_dir)}\n        config_file.save()\n\n        result = hatch(\"-p\", project, \"status\")\n\n        assert result.exit_code == 1\n        assert result.output == f\"Unable to locate project {project}\\n\"\n\n\nclass TestModeProject:\n    def test_no_project(self, hatch, isolation, config_file, helpers):\n        config_file.model.mode = \"project\"\n        config_file.save()\n\n        result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Mode is set to `project` but no project is set, defaulting to the current directory\n            [Project] - <no project detected>\n            [Location] - {isolation}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    def test_unknown_project(self, hatch, isolation, config_file, helpers):\n        project = \"foo\"\n        config_file.model.mode = \"project\"\n        config_file.model.project = project\n        config_file.save()\n\n        result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Unable to locate project {project}, defaulting to the current directory\n            [Project] - <no project detected>\n            [Location] - {isolation}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    def test_not_a_project(self, hatch, temp_dir, config_file, helpers):\n        project = \"foo\"\n        config_file.model.mode = \"project\"\n        config_file.model.project = project\n        config_file.model.projects = {project: str(temp_dir)}\n        config_file.save()\n\n        result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            [Project] - {project} (not a project)\n            [Location] - {temp_dir}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    @pytest.mark.parametrize(\"file_name\", [\"pyproject.toml\", \"setup.py\"])\n    def test_found_project(self, hatch, temp_dir, config_file, helpers, file_name):\n        project_file = temp_dir / file_name\n        project_file.touch()\n\n        project = \"foo\"\n        config_file.model.mode = \"project\"\n        config_file.model.project = project\n        config_file.model.projects = {project: str(temp_dir)}\n        config_file.save()\n\n        result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            [Project] - {project}\n            [Location] - {temp_dir}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n\nclass TestModeAware:\n    def test_no_detection_no_project(self, hatch, config_file, helpers, isolation):\n        config_file.model.mode = \"aware\"\n        config_file.save()\n\n        result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Mode is set to `aware` but no project is set, defaulting to the current directory\n            [Project] - <no project detected>\n            [Location] - {isolation}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    def test_unknown_project(self, hatch, isolation, config_file, helpers):\n        project = \"foo\"\n        config_file.model.project = project\n        config_file.model.mode = \"aware\"\n        config_file.save()\n\n        result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            Unable to locate project {project}, defaulting to the current directory\n            [Project] - <no project detected>\n            [Location] - {isolation}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    def test_not_a_project(self, hatch, temp_dir, config_file, helpers):\n        project = \"foo\"\n        config_file.model.project = project\n        config_file.model.projects = {project: str(temp_dir)}\n        config_file.model.mode = \"aware\"\n        config_file.save()\n\n        result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            [Project] - {project} (not a project)\n            [Location] - {temp_dir}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    @pytest.mark.parametrize(\"file_name\", [\"pyproject.toml\", \"setup.py\"])\n    def test_found_project(self, hatch, temp_dir, config_file, helpers, file_name):\n        project_file = temp_dir / file_name\n        project_file.touch()\n\n        project = \"foo\"\n        config_file.model.project = project\n        config_file.model.projects = {project: str(temp_dir)}\n        config_file.model.mode = \"aware\"\n        config_file.save()\n\n        result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            [Project] - {project}\n            [Location] - {temp_dir}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n\n    def test_local_override(self, hatch, temp_dir, config_file, helpers):\n        project_file = temp_dir / \"pyproject.toml\"\n        project_file.touch()\n\n        project = \"foo\"\n        config_file.model.project = project\n        config_file.model.projects = {project: str(temp_dir)}\n        config_file.model.mode = \"aware\"\n        config_file.save()\n\n        with temp_chdir() as d:\n            d.joinpath(\"pyproject.toml\").touch()\n            result = hatch(\"status\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            f\"\"\"\n            [Project] - {d.name} (current directory)\n            [Location] - {d}\n            [Config] - {config_file.path}\n            \"\"\"\n        )\n"
  },
  {
    "path": "tests/cli/test/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/test/test_test.py",
    "content": "from __future__ import annotations\n\nimport sys\n\nimport pytest\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.env.utils import get_env_var\nfrom hatch.project.core import Project\nfrom hatch.utils.structures import EnvVars\n\n\n@pytest.fixture(scope=\"module\", autouse=True)\ndef _terminal_width():\n    with EnvVars({\"COLUMNS\": \"100\"}, exclude=[get_env_var(plugin_name=\"virtual\", option=\"uv_path\")]):\n        yield\n\n\nclass TestDefaults:\n    def test_basic(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_arguments(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--\", \"--flag\", \"--\", \"arg1\", \"arg2\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly --flag -- arg1 arg2\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n\nclass TestArguments:\n    def test_default_args(self, hatch, temp_dir, platform, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\"hatch-test\": {\"default-args\": [\"tests1\", \"foo bar\", \"tests2\"]}}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        escape_char = '\"' if platform.windows else \"'\"\n        assert env_run.call_args_list == [\n            mocker.call(f\"pytest -p no:randomly tests1 {escape_char}foo bar{escape_char} tests2\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_args_override(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\"hatch-test\": {\"default-args\": [\"tests1\", \"foo bar\", \"tests2\"]}}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--\", \"--flag\", \"--\", \"arg1\", \"arg2\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly --flag -- arg1 arg2\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_extra_args(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\"hatch-test\": {\"extra-args\": [\"-vv\", \"--print\"]}}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -vv --print -p no:randomly tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n\nclass TestCoverage:\n    def test_flag(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--cover\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"coverage run -m pytest -p no:randomly tests\", shell=True),\n            mocker.call(\"coverage combine\", shell=True),\n            mocker.call(\"coverage report\", shell=True),\n        ]\n\n        root_config_path = data_path / \".config\" / \"coverage\"\n        config_dir = next(root_config_path.iterdir())\n        coverage_config_file = next(config_dir.iterdir())\n\n        assert coverage_config_file.read_text().strip().splitlines()[-2:] == [\n            \"[tool.coverage.run]\",\n            \"parallel = true\",\n        ]\n\n    def test_flag_with_arguments(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--cover\", \"--\", \"--flag\", \"--\", \"arg1\", \"arg2\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"coverage run -m pytest -p no:randomly --flag -- arg1 arg2\", shell=True),\n            mocker.call(\"coverage combine\", shell=True),\n            mocker.call(\"coverage report\", shell=True),\n        ]\n\n        root_config_path = data_path / \".config\" / \"coverage\"\n        config_dir = next(root_config_path.iterdir())\n        coverage_config_file = next(config_dir.iterdir())\n\n        assert coverage_config_file.read_text().strip().splitlines()[-2:] == [\n            \"[tool.coverage.run]\",\n            \"parallel = true\",\n        ]\n\n    def test_quiet_implicitly_enables(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--cover-quiet\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"coverage run -m pytest -p no:randomly tests\", shell=True),\n            mocker.call(\"coverage combine\", shell=True),\n        ]\n\n        root_config_path = data_path / \".config\" / \"coverage\"\n        config_dir = next(root_config_path.iterdir())\n        coverage_config_file = next(config_dir.iterdir())\n\n        assert coverage_config_file.read_text().strip().splitlines()[-2:] == [\n            \"[tool.coverage.run]\",\n            \"parallel = true\",\n        ]\n\n    def test_legacy_config_define_section(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        (project_path / \".coveragerc\").touch()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--cover\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"coverage run -m pytest -p no:randomly tests\", shell=True),\n            mocker.call(\"coverage combine\", shell=True),\n            mocker.call(\"coverage report\", shell=True),\n        ]\n\n        root_config_path = data_path / \".config\" / \"coverage\"\n        config_dir = next(root_config_path.iterdir())\n        coverage_config_file = next(config_dir.iterdir())\n\n        assert coverage_config_file.read_text().strip().splitlines() == [\n            \"[run]\",\n            \"parallel = true\",\n        ]\n\n    def test_legacy_config_enable_parallel(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        (project_path / \".coveragerc\").write_text(\"[run]\\nparallel = false\\nbranch = true\\n\")\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--cover\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"coverage run -m pytest -p no:randomly tests\", shell=True),\n            mocker.call(\"coverage combine\", shell=True),\n            mocker.call(\"coverage report\", shell=True),\n        ]\n\n        root_config_path = data_path / \".config\" / \"coverage\"\n        config_dir = next(root_config_path.iterdir())\n        coverage_config_file = next(config_dir.iterdir())\n\n        assert coverage_config_file.read_text().strip().splitlines() == [\n            \"[run]\",\n            \"parallel = true\",\n            \"branch = true\",\n        ]\n\n\nclass TestRandomize:\n    def test_flag(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--randomize\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_flag_with_arguments(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--randomize\", \"--\", \"--flag\", \"--\", \"arg1\", \"arg2\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest --flag -- arg1 arg2\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_config(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\"hatch-test\": {\"randomize\": True}}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n\nclass TestParallel:\n    def test_flag(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--parallel\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly -n logical tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_flag_with_arguments(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--parallel\", \"--\", \"--flag\", \"--\", \"arg1\", \"arg2\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly -n logical --flag -- arg1 arg2\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_config(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\"hatch-test\": {\"parallel\": True}}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly -n logical tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n\nclass TestRetries:\n    def test_flag(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--retries\", \"2\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly -r aR --reruns 2 tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_flag_with_arguments(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--retries\", \"2\", \"--\", \"--flag\", \"--\", \"arg1\", \"arg2\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly -r aR --reruns 2 --flag -- arg1 arg2\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_config(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\"hatch-test\": {\"retries\": 2}}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly -r aR --reruns 2 tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n\nclass TestRetryDelay:\n    @pytest.mark.usefixtures(\"env_run\")\n    def test_no_retries(self, hatch, temp_dir, config_file):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--retry-delay\", \"3.14\")\n\n        assert result.exit_code == 1, result.output\n        assert result.output == \"The --retry-delay option requires the --retries option to be set as well.\\n\"\n\n    def test_flag(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--retries\", \"2\", \"--retry-delay\", \"3.14\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly -r aR --reruns 2 --reruns-delay 3.14 tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_flag_with_arguments(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--retries\", \"2\", \"--retry-delay\", \"3.14\", \"--\", \"--flag\", \"--\", \"arg1\", \"arg2\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly -r aR --reruns 2 --reruns-delay 3.14 --flag -- arg1 arg2\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_config(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\"hatch-test\": {\"retry-delay\": 1.23}}\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--retries\", \"2\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"pytest -p no:randomly -r aR --reruns 2 --reruns-delay 1.23 tests\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n\nclass TestCustomScripts:\n    def test_basic(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-test\": {\n                \"scripts\": {\n                    \"run\": \"test\",\n                    \"run-cov\": \"test with coverage\",\n                    \"cov-combine\": \"combine coverage\",\n                    \"cov-report\": \"show coverage\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"test\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_coverage(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-test\": {\n                \"scripts\": {\n                    \"run\": \"test\",\n                    \"run-cov\": \"test with coverage\",\n                    \"cov-combine\": \"combine coverage\",\n                    \"cov-report\": \"show coverage\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--cover\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(\"test with coverage\", shell=True),\n            mocker.call(\"combine coverage\", shell=True),\n            mocker.call(\"show coverage\", shell=True),\n        ]\n\n        root_config_path = data_path / \".config\" / \"coverage\"\n        config_dir = next(root_config_path.iterdir())\n        coverage_config_file = next(config_dir.iterdir())\n\n        assert coverage_config_file.read_text().strip().splitlines()[-2:] == [\n            \"[tool.coverage.run]\",\n            \"parallel = true\",\n        ]\n\n    def test_single(self, hatch, temp_dir, config_file, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-test\": {\n                \"scripts\": {\n                    \"run\": \"test {env_name}\",\n                    \"run-cov\": \"test with coverage\",\n                    \"cov-combine\": \"combine coverage\",\n                    \"cov-report\": \"show coverage\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\")\n\n        assert result.exit_code == 0, result.output\n        assert not result.output\n\n        assert env_run.call_args_list == [\n            mocker.call(f\"test hatch-test.py{'.'.join(map(str, sys.version_info[:2]))}\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_matrix(self, hatch, temp_dir, config_file, helpers, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-test\": {\n                \"matrix\": [{\"python\": [\"3.12\", \"3.10\", \"3.8\"]}],\n                \"scripts\": {\n                    \"run\": \"test {env_name}\",\n                    \"run-cov\": \"test with coverage\",\n                    \"cov-combine\": \"combine coverage\",\n                    \"cov-report\": \"show coverage\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--all\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            ──────────────────────────────────────── hatch-test.py3.12 ─────────────────────────────────────────\n            ──────────────────────────────────────── hatch-test.py3.10 ─────────────────────────────────────────\n            ───────────────────────────────────────── hatch-test.py3.8 ─────────────────────────────────────────\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(\"test hatch-test.py3.12\", shell=True),\n            mocker.call(\"test hatch-test.py3.10\", shell=True),\n            mocker.call(\"test hatch-test.py3.8\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n\nclass TestFilters:\n    @pytest.mark.usefixtures(\"env_run\")\n    @pytest.mark.parametrize(\"option\", [\"--include\", \"--exclude\"])\n    def test_usage_with_all(self, hatch, temp_dir, config_file, helpers, option):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--all\", option, \"py=3.10\")\n\n        assert result.exit_code == 1, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            The --all option cannot be used with the --include or --exclude options.\n            \"\"\"\n        )\n\n    def test_include(self, hatch, temp_dir, config_file, helpers, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-test\": {\n                \"matrix\": [{\"python\": [\"3.12\", \"3.10\", \"3.8\"]}],\n                \"scripts\": {\n                    \"run\": \"test {env_name}\",\n                    \"run-cov\": \"test with coverage\",\n                    \"cov-combine\": \"combine coverage\",\n                    \"cov-report\": \"show coverage\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"-i\", \"py=3.10\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            ──────────────────────────────────────── hatch-test.py3.10 ─────────────────────────────────────────\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(\"test hatch-test.py3.10\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_exclude(self, hatch, temp_dir, config_file, helpers, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-test\": {\n                \"matrix\": [{\"python\": [\"3.12\", \"3.10\", \"3.8\"]}],\n                \"scripts\": {\n                    \"run\": \"test {env_name}\",\n                    \"run-cov\": \"test with coverage\",\n                    \"cov-combine\": \"combine coverage\",\n                    \"cov-report\": \"show coverage\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"-x\", \"py=3.10\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            ──────────────────────────────────────── hatch-test.py3.12 ─────────────────────────────────────────\n            ───────────────────────────────────────── hatch-test.py3.8 ─────────────────────────────────────────\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(\"test hatch-test.py3.12\", shell=True),\n            mocker.call(\"test hatch-test.py3.8\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n    def test_python(self, hatch, temp_dir, config_file, helpers, env_run, mocker):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-test\": {\n                \"matrix\": [{\"python\": [\"3.12\", \"3.10\", \"3.8\"]}],\n                \"scripts\": {\n                    \"run\": \"test {env_name}\",\n                    \"run-cov\": \"test with coverage\",\n                    \"cov-combine\": \"combine coverage\",\n                    \"cov-report\": \"show coverage\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"-py\", \"3.10\")\n\n        assert result.exit_code == 0, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            ──────────────────────────────────────── hatch-test.py3.10 ─────────────────────────────────────────\n            \"\"\"\n        )\n\n        assert env_run.call_args_list == [\n            mocker.call(\"test hatch-test.py3.10\", shell=True),\n        ]\n\n        assert not (data_path / \".config\" / \"coverage\").exists()\n\n\nclass TestShow:\n    def test_default_compact(self, hatch, temp_dir, config_file, helpers):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-test\": {\n                \"matrix\": [{\"python\": [\"3.12\", \"3.10\", \"3.8\"]}],\n                \"dependencies\": [\"foo\", \"bar\"],\n                \"scripts\": {\n                    \"run\": \"test {env_name}\",\n                    \"run-cov\": \"test with coverage\",\n                    \"cov-combine\": \"combine coverage\",\n                    \"cov-report\": \"show coverage\",\n                },\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"test\", \"--show\")\n\n        assert result.exit_code == 0, result.output\n        assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n            \"\"\"\n            +------------+---------+-------------------+--------------+-------------+\n            | Name       | Type    | Envs              | Dependencies | Scripts     |\n            +============+=========+===================+==============+=============+\n            | hatch-test | virtual | hatch-test.py3.12 | bar          | cov-combine |\n            |            |         | hatch-test.py3.10 | foo          | cov-report  |\n            |            |         | hatch-test.py3.8  |              | run         |\n            |            |         |                   |              | run-cov     |\n            +------------+---------+-------------------+--------------+-------------+\n            \"\"\"\n        )\n\n    def test_verbose(self, hatch, temp_dir, config_file, helpers):\n        config_file.model.template.plugins[\"default\"][\"tests\"] = False\n        config_file.save()\n\n        project_name = \"My.App\"\n\n        with temp_dir.as_cwd():\n            result = hatch(\"new\", project_name)\n\n        assert result.exit_code == 0, result.output\n\n        project_path = temp_dir / \"my-app\"\n        data_path = temp_dir / \"data\"\n        data_path.mkdir()\n\n        project = Project(project_path)\n        config = dict(project.raw_config)\n        config[\"tool\"][\"hatch\"][\"envs\"] = {\n            \"hatch-test\": {\n                \"matrix\": [{\"python\": [\"3.12\", \"3.10\", \"3.8\"]}],\n                \"dependencies\": [\"foo\", \"bar\"],\n                \"scripts\": {\n                    \"run\": \"test {env_name}\",\n                    \"run-cov\": \"test with coverage\",\n                    \"cov-combine\": \"combine coverage\",\n                    \"cov-report\": \"show coverage\",\n                },\n                \"overrides\": {\"matrix\": {\"python\": {\"description\": {\"value\": \"test 3.10\", \"if\": [\"3.10\"]}}}},\n            }\n        }\n        project.save_config(config)\n\n        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n            result = hatch(\"-v\", \"test\", \"--show\")\n\n        assert result.exit_code == 0, result.output\n        assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(\n            \"\"\"\n            +-------------------+---------+--------------+-------------+-------------+\n            | Name              | Type    | Dependencies | Scripts     | Description |\n            +===================+=========+==============+=============+=============+\n            | hatch-test.py3.12 | virtual | bar          | cov-combine |             |\n            |                   |         | foo          | cov-report  |             |\n            |                   |         |              | run         |             |\n            |                   |         |              | run-cov     |             |\n            +-------------------+---------+--------------+-------------+-------------+\n            | hatch-test.py3.10 | virtual | bar          | cov-combine | test 3.10   |\n            |                   |         | foo          | cov-report  |             |\n            |                   |         |              | run         |             |\n            |                   |         |              | run-cov     |             |\n            +-------------------+---------+--------------+-------------+-------------+\n            | hatch-test.py3.8  | virtual | bar          | cov-combine |             |\n            |                   |         | foo          | cov-report  |             |\n            |                   |         |              | run         |             |\n            |                   |         |              | run-cov     |             |\n            +-------------------+---------+--------------+-------------+-------------+\n            \"\"\"\n        )\n"
  },
  {
    "path": "tests/cli/test_root.py",
    "content": "import os\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.config.user import ConfigFile\nfrom hatch.utils.structures import EnvVars\n\n\nclass TestFreshInstallation:\n    INSTALL_MESSAGE = \"\"\"\\\nNo config file found, creating one with default settings now...\nSuccess! Please see `hatch config`.\n\"\"\"\n\n    def test_config_file_creation_default(self, hatch):\n        with EnvVars():\n            os.environ.pop(ConfigEnvVars.CONFIG, None)\n            with ConfigFile.get_default_location().temp_hide():\n                result = hatch()\n                assert self.INSTALL_MESSAGE not in result.output\n\n    def test_config_file_creation_verbose(self, hatch):\n        with EnvVars():\n            os.environ.pop(ConfigEnvVars.CONFIG, None)\n            with ConfigFile.get_default_location().temp_hide():\n                result = hatch(\"-v\")\n                assert self.INSTALL_MESSAGE in result.output\n\n                result = hatch(\"-v\")\n                assert self.INSTALL_MESSAGE not in result.output\n\n\ndef test_no_subcommand_shows_help(hatch):\n    assert hatch().output == hatch(\"--help\").output\n\n\ndef test_no_config_file(hatch, config_file):\n    config_file.path.remove()\n    result = hatch()\n\n    assert result.exit_code == 1\n    assert result.output == f\"The selected config file `{config_file.path}` does not exist.\\n\"\n"
  },
  {
    "path": "tests/cli/version/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/version/test_version.py",
    "content": "import os\n\nimport pytest\n\nfrom hatch.config.constants import ConfigEnvVars\nfrom hatch.project.core import Project\nfrom hatchling.utils.constants import DEFAULT_CONFIG_FILE\n\n\nclass TestNoProject:\n    def test_random_directory(self, hatch, temp_dir, helpers):\n        with temp_dir.as_cwd():\n            result = hatch(\"version\")\n\n        assert result.exit_code == 1, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            No project detected\n            \"\"\"\n        )\n\n    def test_configured_project(self, hatch, temp_dir, helpers, config_file):\n        project = \"foo\"\n        config_file.model.mode = \"project\"\n        config_file.model.project = project\n        config_file.model.projects = {project: str(temp_dir)}\n        config_file.save()\n\n        with temp_dir.as_cwd():\n            result = hatch(\"version\")\n\n        assert result.exit_code == 1, result.output\n        assert result.output == helpers.dedent(\n            \"\"\"\n            Project foo (not a project)\n            \"\"\"\n        )\n\n\ndef test_other_backend_show(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    (path / \"src\" / \"my_app\" / \"__init__.py\").write_text('__version__ = \"9000.42\"')\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"] = [\"flit-core\"]\n    config[\"build-system\"][\"build-backend\"] = \"flit_core.buildapi\"\n    del config[\"project\"][\"license\"]\n    project.save_config(config)\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        9000.42\n        \"\"\"\n    )\n\n\ndef test_other_backend_set(hatch, temp_dir, helpers):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"] = [\"flit-core\"]\n    config[\"build-system\"][\"build-backend\"] = \"flit_core.buildapi\"\n    del config[\"project\"][\"license\"]\n    project.save_config(config)\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\", \"1.0.0\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        The version can only be set when Hatchling is the build backend\n        \"\"\"\n    )\n\n\ndef test_incompatible_environment(hatch, temp_dir, helpers, build_env_config):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"].append(\"foo\")\n    project.save_config(config)\n    helpers.update_project_environment(project, \"hatch-build\", {\"python\": \"9000\", **build_env_config})\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Environment `hatch-build` is incompatible: cannot locate Python: 9000\n        \"\"\"\n    )\n\n\n@pytest.mark.usefixtures(\"mock_backend_process\")\ndef test_show_dynamic(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        0.0.1\n        \"\"\"\n    )\n\n\n@pytest.mark.usefixtures(\"mock_backend_process\")\ndef test_plugin_dependencies_unmet(hatch, helpers, temp_dir, mock_plugin_installation):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    dependency = os.urandom(16).hex()\n    (path / DEFAULT_CONFIG_FILE).write_text(\n        helpers.dedent(\n            f\"\"\"\n            [env]\n            requires = [\"{dependency}\"]\n            \"\"\"\n        )\n    )\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Syncing environment plugin requirements\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        0.0.1\n        \"\"\"\n    )\n    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])\n\n\n@pytest.mark.requires_internet\n@pytest.mark.usefixtures(\"mock_backend_process\")\ndef test_no_compatibility_check_if_exists(hatch, helpers, temp_dir, mocker):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        hatch(\"new\", project_name)\n\n    project_path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(project_path)\n    config = dict(project.raw_config)\n    config[\"build-system\"][\"requires\"].append(\"binary\")\n    project.save_config(config)\n\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        0.0.1\n        \"\"\"\n    )\n\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment.check_compatibility\", side_effect=Exception(\"incompatible\"))\n    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Inspecting build dependencies\n        0.0.1\n        \"\"\"\n    )\n\n\n@pytest.mark.usefixtures(\"mock_backend_process\")\ndef test_set_dynamic(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\", \"minor,rc\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Creating environment: hatch-build\n        Checking dependencies\n        Syncing dependencies\n        Inspecting build dependencies\n        Old: 0.0.1\n        New: 0.1.0rc0\n        \"\"\"\n    )\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Inspecting build dependencies\n        0.1.0rc0\n        \"\"\"\n    )\n\n\n@pytest.mark.usefixtures(\"mock_backend_process\")\ndef test_set_dynamic_downgrade(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    (path / \"src\" / \"my_app\" / \"__about__.py\").write_text('__version__ = \"21.1.2\"')\n\n    # This one fails, because it's a downgrade without --force\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\", \"21.1.0\", catch_exceptions=True)\n\n    assert result.exit_code == 1, result.output\n    assert str(result.exception) == \"Version `21.1.0` is not higher than the original version `21.1.2`\"\n\n    # Try again, this time with --force\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\", \"--force\", \"21.1.0\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Inspecting build dependencies\n        Old: 21.1.2\n        New: 21.1.0\n        \"\"\"\n    )\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Inspecting build dependencies\n        21.1.0\n        \"\"\"\n    )\n\n\ndef test_show_static(hatch, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"version\"] = \"1.2.3\"\n    config[\"project\"][\"dynamic\"].remove(\"version\")\n    config[\"tool\"][\"hatch\"][\"metadata\"] = {\"hooks\": {\"foo\": {}}}\n    project.save_config(config)\n\n    with path.as_cwd():\n        result = hatch(\"version\")\n\n    assert result.exit_code == 0, result.output\n    assert result.output == \"1.2.3\\n\"\n\n\ndef test_set_static(hatch, helpers, temp_dir):\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    project = Project(path)\n    config = dict(project.raw_config)\n    config[\"project\"][\"version\"] = \"1.2.3\"\n    config[\"project\"][\"dynamic\"].remove(\"version\")\n    project.save_config(config)\n\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"version\", \"minor,rc\")\n\n    assert result.exit_code == 1, result.output\n    assert result.output == helpers.dedent(\n        \"\"\"\n        Cannot set version when it is statically defined by the `project.version` field\n        \"\"\"\n    )\n\n\n@pytest.mark.usefixtures(\"mock_backend_process\")\ndef test_verbose_output_to_stderr(hatch, temp_dir):\n    \"\"\"Test that verbose output (command display and status messages) goes to stderr, not stdout.\"\"\"\n    project_name = \"My.App\"\n\n    with temp_dir.as_cwd():\n        hatch(\"new\", project_name)\n\n    path = temp_dir / \"my-app\"\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    # Run with verbose flag (-v) and separate stderr from stdout\n    with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):\n        result = hatch(\"-v\", \"version\")\n\n    assert result.exit_code == 0, result.output\n\n    # The actual version should be in stdout\n    assert result.stdout == \"0.0.1\\n\"\n\n    # Verbose output should be in stderr\n    assert \"Inspecting build dependencies\" in result.stderr\n    assert \"cmd [1] | python -u -m hatchling version\" in result.stderr\n\n    # These should NOT be in stdout\n    assert \"Inspecting build dependencies\" not in result.stdout\n    assert \"cmd [1]\" not in result.stdout\n"
  },
  {
    "path": "tests/config/__init__.py",
    "content": ""
  },
  {
    "path": "tests/config/test_model.py",
    "content": "import subprocess\n\nimport pytest\n\nfrom hatch.config.model import ConfigurationError, RootConfig\n\n\ndef test_default(default_cache_dir, default_data_dir):\n    config = RootConfig({})\n    config.parse_fields()\n\n    assert config.raw_data == {\n        \"mode\": \"local\",\n        \"project\": \"\",\n        \"shell\": \"\",\n        \"dirs\": {\n            \"project\": [],\n            \"env\": {},\n            \"python\": \"isolated\",\n            \"data\": str(default_data_dir),\n            \"cache\": str(default_cache_dir),\n        },\n        \"projects\": {},\n        \"publish\": {\"index\": {\"repo\": \"main\"}},\n        \"template\": {\n            \"name\": \"Foo Bar\",\n            \"email\": \"foo@bar.baz\",\n            \"licenses\": {\"default\": [\"MIT\"], \"headers\": True},\n            \"plugins\": {\"default\": {\"ci\": False, \"src-layout\": True, \"tests\": True}},\n        },\n        \"terminal\": {\n            \"styles\": {\n                \"info\": \"bold\",\n                \"success\": \"bold cyan\",\n                \"error\": \"bold red\",\n                \"warning\": \"bold yellow\",\n                \"waiting\": \"bold magenta\",\n                \"debug\": \"bold\",\n                \"spinner\": \"simpleDotsScrolling\",\n            },\n        },\n    }\n\n\nclass TestMode:\n    def test_default(self):\n        config = RootConfig({})\n\n        assert config.mode == config.mode == \"local\"\n        assert config.raw_data == {\"mode\": \"local\"}\n\n    def test_defined(self):\n        config = RootConfig({\"mode\": \"aware\"})\n\n        assert config.mode == \"aware\"\n        assert config.raw_data == {\"mode\": \"aware\"}\n\n    def test_not_string(self, helpers):\n        config = RootConfig({\"mode\": 9000})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                mode\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.mode\n\n    def test_unknown(self, helpers):\n        config = RootConfig({\"mode\": \"foo\"})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                mode\n                  must be one of: aware, local, project\"\"\"\n            ),\n        ):\n            _ = config.mode\n\n    def test_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.mode = 9000\n        assert config.raw_data == {\"mode\": 9000}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                mode\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.mode\n\n\nclass TestProject:\n    def test_default(self):\n        config = RootConfig({})\n\n        assert config.project == config.project == \"\"\n        assert config.raw_data == {\"project\": \"\"}\n\n    def test_defined(self):\n        config = RootConfig({\"project\": \"foo\"})\n\n        assert config.project == \"foo\"\n        assert config.raw_data == {\"project\": \"foo\"}\n\n    def test_not_string(self, helpers):\n        config = RootConfig({\"project\": 9000})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                project\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.project\n\n    def test_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.project = 9000\n        assert config.raw_data == {\"project\": 9000}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                project\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.project\n\n\nclass TestShell:\n    def test_default(self):\n        config = RootConfig({})\n\n        assert config.shell.name == config.shell.name == \"\"\n        assert config.shell.path == config.shell.path == \"\"\n        assert config.shell.args == config.shell.args == []\n        assert config.raw_data == {\"shell\": \"\"}\n\n    def test_invalid_type(self, helpers):\n        config = RootConfig({\"shell\": 9000})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell\n                  must be a string or table\"\"\"\n            ),\n        ):\n            _ = config.shell\n\n    def test_string(self):\n        config = RootConfig({\"shell\": \"foo\"})\n\n        assert config.shell.name == \"foo\"\n        assert config.shell.path == \"foo\"\n        assert config.shell.args == []\n        assert config.raw_data == {\"shell\": \"foo\"}\n\n    def test_table(self):\n        config = RootConfig({\"shell\": {\"name\": \"foo\"}})\n\n        assert config.shell.name == \"foo\"\n        assert config.shell.path == \"foo\"\n        assert config.shell.args == []\n        assert config.raw_data == {\"shell\": {\"name\": \"foo\", \"path\": \"foo\", \"args\": []}}\n\n    def test_table_with_path(self):\n        config = RootConfig({\"shell\": {\"name\": \"foo\", \"path\": \"bar\"}})\n\n        assert config.shell.name == \"foo\"\n        assert config.shell.path == \"bar\"\n        assert config.shell.args == []\n        assert config.raw_data == {\"shell\": {\"name\": \"foo\", \"path\": \"bar\", \"args\": []}}\n\n    def test_table_with_path_and_args(self):\n        config = RootConfig({\"shell\": {\"name\": \"foo\", \"path\": \"bar\", \"args\": [\"baz\"]}})\n\n        assert config.shell.name == \"foo\"\n        assert config.shell.path == \"bar\"\n        assert config.shell.args == [\"baz\"]\n        assert config.raw_data == {\"shell\": {\"name\": \"foo\", \"path\": \"bar\", \"args\": [\"baz\"]}}\n\n    def test_table_no_name(self, helpers):\n        config = RootConfig({\"shell\": {}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell -> name\n                  required field\"\"\"\n            ),\n        ):\n            _ = config.shell.name\n\n    def test_table_name_not_string(self, helpers):\n        config = RootConfig({\"shell\": {\"name\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell -> name\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.shell.name\n\n    def test_table_path_not_string(self, helpers):\n        config = RootConfig({\"shell\": {\"path\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell -> path\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.shell.path\n\n    def test_table_args_not_array(self, helpers):\n        config = RootConfig({\"shell\": {\"args\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell -> args\n                  must be an array\"\"\"\n            ),\n        ):\n            _ = config.shell.args\n\n    def test_table_args_entry_not_string(self, helpers):\n        config = RootConfig({\"shell\": {\"args\": [9000]}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell -> args -> 1\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.shell.args\n\n    def test_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.shell = 9000\n        assert config.raw_data == {\"shell\": 9000}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell\n                  must be a string or table\"\"\"\n            ),\n        ):\n            _ = config.shell\n\n    def test_table_name_set_lazy_error(self, helpers):\n        config = RootConfig({\"shell\": {}})\n\n        config.shell.name = 9000\n        assert config.raw_data == {\"shell\": {\"name\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell -> name\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.shell.name\n\n    def test_table_path_set_lazy_error(self, helpers):\n        config = RootConfig({\"shell\": {\"name\": \"foo\"}})\n\n        config.shell.path = 9000\n        assert config.raw_data == {\"shell\": {\"name\": \"foo\", \"path\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell -> path\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.shell.path\n\n    def test_table_args_set_lazy_error(self, helpers):\n        config = RootConfig({\"shell\": {\"name\": \"foo\"}})\n\n        config.shell.args = 9000\n        assert config.raw_data == {\"shell\": {\"name\": \"foo\", \"args\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                shell -> args\n                  must be an array\"\"\"\n            ),\n        ):\n            _ = config.shell.args\n\n\nclass TestDirs:\n    def test_default(self, default_cache_dir, default_data_dir):\n        config = RootConfig({})\n\n        default_cache_directory = str(default_cache_dir)\n        default_data_directory = str(default_data_dir)\n        assert config.dirs.project == config.dirs.project == []\n        assert config.dirs.env == config.dirs.env == {}\n        assert config.dirs.python == config.dirs.python == \"isolated\"\n        assert config.dirs.cache == config.dirs.cache == default_cache_directory\n        assert config.dirs.data == config.dirs.data == default_data_directory\n        assert config.raw_data == {\n            \"dirs\": {\n                \"project\": [],\n                \"env\": {},\n                \"python\": \"isolated\",\n                \"data\": default_data_directory,\n                \"cache\": default_cache_directory,\n            },\n        }\n\n    def test_not_table(self, helpers):\n        config = RootConfig({\"dirs\": 9000})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.dirs\n\n    def test_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.dirs = 9000\n        assert config.raw_data == {\"dirs\": 9000}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.dirs\n\n    def test_project(self):\n        config = RootConfig({\"dirs\": {\"project\": [\"foo\"]}})\n\n        assert config.dirs.project == [\"foo\"]\n        assert config.raw_data == {\"dirs\": {\"project\": [\"foo\"]}}\n\n    def test_project_not_array(self, helpers):\n        config = RootConfig({\"dirs\": {\"project\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> project\n                  must be an array\"\"\"\n            ),\n        ):\n            _ = config.dirs.project\n\n    def test_project_entry_not_string(self, helpers):\n        config = RootConfig({\"dirs\": {\"project\": [9000]}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> project -> 1\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.dirs.project\n\n    def test_project_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.dirs.project = 9000\n        assert config.raw_data == {\"dirs\": {\"project\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> project\n                  must be an array\"\"\"\n            ),\n        ):\n            _ = config.dirs.project\n\n    def test_env(self):\n        config = RootConfig({\"dirs\": {\"env\": {\"foo\": \"bar\"}}})\n\n        assert config.dirs.env == {\"foo\": \"bar\"}\n        assert config.raw_data == {\"dirs\": {\"env\": {\"foo\": \"bar\"}}}\n\n    def test_env_not_table(self, helpers):\n        config = RootConfig({\"dirs\": {\"env\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> env\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.dirs.env\n\n    def test_env_value_not_string(self, helpers):\n        config = RootConfig({\"dirs\": {\"env\": {\"foo\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> env -> foo\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.dirs.env\n\n    def test_env_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.dirs.env = 9000\n        assert config.raw_data == {\"dirs\": {\"env\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> env\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.dirs.env\n\n    def test_python(self):\n        config = RootConfig({\"dirs\": {\"python\": \"foo\"}})\n\n        assert config.dirs.python == \"foo\"\n        assert config.raw_data == {\"dirs\": {\"python\": \"foo\"}}\n\n    def test_python_not_string(self, helpers):\n        config = RootConfig({\"dirs\": {\"python\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> python\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.dirs.python\n\n    def test_python_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.dirs.python = 9000\n        assert config.raw_data == {\"dirs\": {\"python\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> python\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.dirs.python\n\n    def test_data(self):\n        config = RootConfig({\"dirs\": {\"data\": \"foo\"}})\n\n        assert config.dirs.data == \"foo\"\n        assert config.raw_data == {\"dirs\": {\"data\": \"foo\"}}\n\n    def test_data_not_string(self, helpers):\n        config = RootConfig({\"dirs\": {\"data\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> data\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.dirs.data\n\n    def test_data_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.dirs.data = 9000\n        assert config.raw_data == {\"dirs\": {\"data\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> data\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.dirs.data\n\n    def test_cache(self):\n        config = RootConfig({\"dirs\": {\"cache\": \"foo\"}})\n\n        assert config.dirs.cache == \"foo\"\n        assert config.raw_data == {\"dirs\": {\"cache\": \"foo\"}}\n\n    def test_cache_not_string(self, helpers):\n        config = RootConfig({\"dirs\": {\"cache\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> cache\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.dirs.cache\n\n    def test_cache_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.dirs.cache = 9000\n        assert config.raw_data == {\"dirs\": {\"cache\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                dirs -> cache\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.dirs.cache\n\n\nclass TestProjects:\n    def test_default(self):\n        config = RootConfig({})\n\n        assert config.projects == config.projects == {}\n        assert config.raw_data == {\"projects\": {}}\n\n    def test_not_table(self, helpers):\n        config = RootConfig({\"projects\": 9000})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                projects\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.projects\n\n    def test_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.projects = 9000\n        assert config.raw_data == {\"projects\": 9000}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                projects\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.projects\n\n    def test_entry_invalid_type(self, helpers):\n        config = RootConfig({\"projects\": {\"foo\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                projects -> foo\n                  must be a string or table\"\"\"\n            ),\n        ):\n            _ = config.projects\n\n    def test_string(self):\n        config = RootConfig({\"projects\": {\"foo\": \"bar\"}})\n\n        project = config.projects[\"foo\"]\n        assert project.location == project.location == \"bar\"\n        assert config.raw_data == {\"projects\": {\"foo\": \"bar\"}}\n\n    def test_table(self):\n        config = RootConfig({\"projects\": {\"foo\": {\"location\": \"bar\"}}})\n\n        project = config.projects[\"foo\"]\n        assert project.location == project.location == \"bar\"\n        assert config.raw_data == {\"projects\": {\"foo\": {\"location\": \"bar\"}}}\n\n    def test_table_no_location(self, helpers):\n        config = RootConfig({\"projects\": {\"foo\": {}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                projects -> foo -> location\n                  required field\"\"\"\n            ),\n        ):\n            _ = config.projects[\"foo\"].location\n\n    def test_location_not_string(self, helpers):\n        config = RootConfig({\"projects\": {\"foo\": {\"location\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                projects -> foo -> location\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.projects[\"foo\"].location\n\n    def test_location_set_lazy_error(self, helpers):\n        config = RootConfig({\"projects\": {\"foo\": {}}})\n\n        project = config.projects[\"foo\"]\n        project.location = 9000\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                projects -> foo -> location\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = project.location\n\n\nclass TestPublish:\n    def test_default(self):\n        config = RootConfig({})\n\n        assert config.publish == config.publish == {\"index\": {\"repo\": \"main\"}}\n        assert config.raw_data == {\"publish\": {\"index\": {\"repo\": \"main\"}}}\n\n    def test_defined(self):\n        config = RootConfig({\"publish\": {\"foo\": {\"username\": \"\", \"password\": \"\"}}})\n\n        assert config.publish == {\"foo\": {\"username\": \"\", \"password\": \"\"}}\n        assert config.raw_data == {\"publish\": {\"foo\": {\"username\": \"\", \"password\": \"\"}}}\n\n    def test_not_table(self, helpers):\n        config = RootConfig({\"publish\": 9000})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                publish\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.publish\n\n    def test_data_not_table(self, helpers):\n        config = RootConfig({\"publish\": {\"foo\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                publish -> foo\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.publish\n\n    def test_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.publish = 9000\n        assert config.raw_data == {\"publish\": 9000}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                publish\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.publish\n\n\nclass TestTemplate:\n    def test_not_table(self, helpers):\n        config = RootConfig({\"template\": 9000})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.template\n\n    def test_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.template = 9000\n        assert config.raw_data == {\"template\": 9000}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.template\n\n    def test_name(self):\n        config = RootConfig({\"template\": {\"name\": \"foo\"}})\n\n        assert config.template.name == config.template.name == \"foo\"\n        assert config.raw_data == {\"template\": {\"name\": \"foo\"}}\n\n    def test_name_default_env_var(self):\n        config = RootConfig({})\n\n        assert config.template.name == \"Foo Bar\"\n        assert config.raw_data == {\"template\": {\"name\": \"Foo Bar\"}}\n\n    def test_name_default_git(self, temp_dir):\n        config = RootConfig({})\n\n        with temp_dir.as_cwd(exclude=[\"GIT_AUTHOR_NAME\"]):\n            subprocess.check_output([\"git\", \"init\"])\n            subprocess.check_output([\"git\", \"config\", \"--local\", \"user.name\", \"test\"])\n\n            assert config.template.name == \"test\"\n            assert config.raw_data == {\"template\": {\"name\": \"test\"}}\n\n    def test_name_default_no_git(self, temp_dir):\n        config = RootConfig({})\n\n        with temp_dir.as_cwd(exclude=[\"*\"]):\n            assert config.template.name == \"U.N. Owen\"\n            assert config.raw_data == {\"template\": {\"name\": \"U.N. Owen\"}}\n\n    def test_name_not_string(self, helpers):\n        config = RootConfig({\"template\": {\"name\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> name\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.template.name\n\n    def test_name_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.template.name = 9000\n        assert config.raw_data == {\"template\": {\"name\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> name\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.template.name\n\n    def test_email(self):\n        config = RootConfig({\"template\": {\"email\": \"foo\"}})\n\n        assert config.template.email == config.template.email == \"foo\"\n        assert config.raw_data == {\"template\": {\"email\": \"foo\"}}\n\n    def test_email_default_env_var(self):\n        config = RootConfig({})\n\n        assert config.template.email == \"foo@bar.baz\"\n        assert config.raw_data == {\"template\": {\"email\": \"foo@bar.baz\"}}\n\n    def test_email_default_git(self, temp_dir):\n        config = RootConfig({})\n\n        with temp_dir.as_cwd(exclude=[\"GIT_AUTHOR_EMAIL\"]):\n            subprocess.check_output([\"git\", \"init\"])\n            subprocess.check_output([\"git\", \"config\", \"--local\", \"user.email\", \"test\"])\n\n            assert config.template.email == \"test\"\n            assert config.raw_data == {\"template\": {\"email\": \"test\"}}\n\n    def test_email_default_no_git(self, temp_dir):\n        config = RootConfig({})\n\n        with temp_dir.as_cwd(exclude=[\"*\"]):\n            assert config.template.email == \"void@some.where\"\n            assert config.raw_data == {\"template\": {\"email\": \"void@some.where\"}}\n\n    def test_email_not_string(self, helpers):\n        config = RootConfig({\"template\": {\"email\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> email\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.template.email\n\n    def test_email_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.template.email = 9000\n        assert config.raw_data == {\"template\": {\"email\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> email\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.template.email\n\n    def test_licenses_not_table(self, helpers):\n        config = RootConfig({\"template\": {\"licenses\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> licenses\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.template.licenses\n\n    def test_licenses_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.template.licenses = 9000\n        assert config.raw_data == {\"template\": {\"licenses\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> licenses\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.template.licenses\n\n    def test_licenses_headers(self):\n        config = RootConfig({\"template\": {\"licenses\": {\"headers\": False}}})\n\n        assert config.template.licenses.headers is config.template.licenses.headers is False\n        assert config.raw_data == {\"template\": {\"licenses\": {\"headers\": False}}}\n\n    def test_licenses_headers_default(self):\n        config = RootConfig({})\n\n        assert config.template.licenses.headers is config.template.licenses.headers is True\n        assert config.raw_data == {\"template\": {\"licenses\": {\"headers\": True}}}\n\n    def test_licenses_headers_not_boolean(self, helpers):\n        config = RootConfig({\"template\": {\"licenses\": {\"headers\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> licenses -> headers\n                  must be a boolean\"\"\"\n            ),\n        ):\n            _ = config.template.licenses.headers\n\n    def test_licenses_headers_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.template.licenses.headers = 9000\n        assert config.raw_data == {\"template\": {\"licenses\": {\"headers\": 9000}}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> licenses -> headers\n                  must be a boolean\"\"\"\n            ),\n        ):\n            _ = config.template.licenses.headers\n\n    def test_licenses_default(self):\n        config = RootConfig({\"template\": {\"licenses\": {\"default\": [\"Apache-2.0\", \"MIT\"]}}})\n\n        assert config.template.licenses.default == config.template.licenses.default == [\"Apache-2.0\", \"MIT\"]\n        assert config.raw_data == {\"template\": {\"licenses\": {\"default\": [\"Apache-2.0\", \"MIT\"]}}}\n\n    def test_licenses_default_default(self):\n        config = RootConfig({})\n\n        assert config.template.licenses.default == [\"MIT\"]\n        assert config.raw_data == {\"template\": {\"licenses\": {\"default\": [\"MIT\"]}}}\n\n    def test_licenses_default_not_array(self, helpers):\n        config = RootConfig({\"template\": {\"licenses\": {\"default\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> licenses -> default\n                  must be an array\"\"\"\n            ),\n        ):\n            _ = config.template.licenses.default\n\n    def test_licenses_default_entry_not_string(self, helpers):\n        config = RootConfig({\"template\": {\"licenses\": {\"default\": [9000]}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> licenses -> default -> 1\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.template.licenses.default\n\n    def test_licenses_default_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.template.licenses.default = 9000\n        assert config.raw_data == {\"template\": {\"licenses\": {\"default\": 9000}}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> licenses -> default\n                  must be an array\"\"\"\n            ),\n        ):\n            _ = config.template.licenses.default\n\n    def test_plugins(self):\n        config = RootConfig({\"template\": {\"plugins\": {\"foo\": {\"bar\": \"baz\"}}}})\n\n        assert config.template.plugins == config.template.plugins == {\"foo\": {\"bar\": \"baz\"}}\n        assert config.raw_data == {\"template\": {\"plugins\": {\"foo\": {\"bar\": \"baz\"}}}}\n\n    def test_plugins_default(self):\n        config = RootConfig({})\n\n        assert config.template.plugins == {\"default\": {\"ci\": False, \"src-layout\": True, \"tests\": True}}\n        assert config.raw_data == {\n            \"template\": {\"plugins\": {\"default\": {\"ci\": False, \"src-layout\": True, \"tests\": True}}}\n        }\n\n    def test_plugins_not_table(self, helpers):\n        config = RootConfig({\"template\": {\"plugins\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> plugins\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.template.plugins\n\n    def test_plugins_data_not_table(self, helpers):\n        config = RootConfig({\"template\": {\"plugins\": {\"foo\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> plugins -> foo\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.template.plugins\n\n    def test_plugins_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.template.plugins = 9000\n        assert config.raw_data == {\"template\": {\"plugins\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                template -> plugins\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.template.plugins\n\n\nclass TestTerminal:\n    def test_default(self):\n        config = RootConfig({})\n\n        assert config.terminal.styles.info == config.terminal.styles.info == \"bold\"\n        assert config.terminal.styles.success == config.terminal.styles.success == \"bold cyan\"\n        assert config.terminal.styles.error == config.terminal.styles.error == \"bold red\"\n        assert config.terminal.styles.warning == config.terminal.styles.warning == \"bold yellow\"\n        assert config.terminal.styles.waiting == config.terminal.styles.waiting == \"bold magenta\"\n        assert config.terminal.styles.debug == config.terminal.styles.debug == \"bold\"\n        assert config.terminal.styles.spinner == config.terminal.styles.spinner == \"simpleDotsScrolling\"\n        assert config.raw_data == {\n            \"terminal\": {\n                \"styles\": {\n                    \"info\": \"bold\",\n                    \"success\": \"bold cyan\",\n                    \"error\": \"bold red\",\n                    \"warning\": \"bold yellow\",\n                    \"waiting\": \"bold magenta\",\n                    \"debug\": \"bold\",\n                    \"spinner\": \"simpleDotsScrolling\",\n                },\n            },\n        }\n\n    def test_not_table(self, helpers):\n        config = RootConfig({\"terminal\": 9000})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.terminal\n\n    def test_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.terminal = 9000\n        assert config.raw_data == {\"terminal\": 9000}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.terminal\n\n    def test_styles_not_table(self, helpers):\n        config = RootConfig({\"terminal\": {\"styles\": 9000}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles\n\n    def test_styles_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.terminal.styles = 9000\n        assert config.raw_data == {\"terminal\": {\"styles\": 9000}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles\n                  must be a table\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles\n\n    def test_styles_info(self):\n        config = RootConfig({\"terminal\": {\"styles\": {\"info\": \"foo\"}}})\n\n        assert config.terminal.styles.info == \"foo\"\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"info\": \"foo\"}}}\n\n    def test_styles_info_not_string(self, helpers):\n        config = RootConfig({\"terminal\": {\"styles\": {\"info\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> info\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.info\n\n    def test_styles_info_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.terminal.styles.info = 9000\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"info\": 9000}}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> info\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.info\n\n    def test_styles_success(self):\n        config = RootConfig({\"terminal\": {\"styles\": {\"success\": \"foo\"}}})\n\n        assert config.terminal.styles.success == \"foo\"\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"success\": \"foo\"}}}\n\n    def test_styles_success_not_string(self, helpers):\n        config = RootConfig({\"terminal\": {\"styles\": {\"success\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> success\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.success\n\n    def test_styles_success_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.terminal.styles.success = 9000\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"success\": 9000}}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> success\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.success\n\n    def test_styles_error(self):\n        config = RootConfig({\"terminal\": {\"styles\": {\"error\": \"foo\"}}})\n\n        assert config.terminal.styles.error == \"foo\"\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"error\": \"foo\"}}}\n\n    def test_styles_error_not_string(self, helpers):\n        config = RootConfig({\"terminal\": {\"styles\": {\"error\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> error\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.error\n\n    def test_styles_error_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.terminal.styles.error = 9000\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"error\": 9000}}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> error\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.error\n\n    def test_styles_warning(self):\n        config = RootConfig({\"terminal\": {\"styles\": {\"warning\": \"foo\"}}})\n\n        assert config.terminal.styles.warning == \"foo\"\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"warning\": \"foo\"}}}\n\n    def test_styles_warning_not_string(self, helpers):\n        config = RootConfig({\"terminal\": {\"styles\": {\"warning\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> warning\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.warning\n\n    def test_styles_warning_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.terminal.styles.warning = 9000\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"warning\": 9000}}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> warning\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.warning\n\n    def test_styles_waiting(self):\n        config = RootConfig({\"terminal\": {\"styles\": {\"waiting\": \"foo\"}}})\n\n        assert config.terminal.styles.waiting == \"foo\"\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"waiting\": \"foo\"}}}\n\n    def test_styles_waiting_not_string(self, helpers):\n        config = RootConfig({\"terminal\": {\"styles\": {\"waiting\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> waiting\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.waiting\n\n    def test_styles_waiting_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.terminal.styles.waiting = 9000\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"waiting\": 9000}}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> waiting\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.waiting\n\n    def test_styles_debug(self):\n        config = RootConfig({\"terminal\": {\"styles\": {\"debug\": \"foo\"}}})\n\n        assert config.terminal.styles.debug == \"foo\"\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"debug\": \"foo\"}}}\n\n    def test_styles_debug_not_string(self, helpers):\n        config = RootConfig({\"terminal\": {\"styles\": {\"debug\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> debug\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.debug\n\n    def test_styles_debug_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.terminal.styles.debug = 9000\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"debug\": 9000}}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> debug\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.debug\n\n    def test_styles_spinner(self):\n        config = RootConfig({\"terminal\": {\"styles\": {\"spinner\": \"foo\"}}})\n\n        assert config.terminal.styles.spinner == \"foo\"\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"spinner\": \"foo\"}}}\n\n    def test_styles_spinner_not_string(self, helpers):\n        config = RootConfig({\"terminal\": {\"styles\": {\"spinner\": 9000}}})\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> spinner\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.spinner\n\n    def test_styles_spinner_set_lazy_error(self, helpers):\n        config = RootConfig({})\n\n        config.terminal.styles.spinner = 9000\n        assert config.raw_data == {\"terminal\": {\"styles\": {\"spinner\": 9000}}}\n\n        with pytest.raises(\n            ConfigurationError,\n            match=helpers.dedent(\n                \"\"\"\n                Error parsing config:\n                terminal -> styles -> spinner\n                  must be a string\"\"\"\n            ),\n        ):\n            _ = config.terminal.styles.spinner\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "from __future__ import annotations\n\nimport json\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport time\nfrom contextlib import suppress\nfrom functools import lru_cache\nfrom typing import TYPE_CHECKING, NamedTuple\n\nimport pytest\nfrom click.testing import CliRunner as __CliRunner\nfrom filelock import FileLock\nfrom platformdirs import user_cache_dir, user_data_dir\n\nfrom hatch.config.constants import AppEnvVars, ConfigEnvVars, PublishEnvVars\nfrom hatch.config.user import ConfigFile\nfrom hatch.env.internal import get_internal_env_config\nfrom hatch.env.utils import get_env_var\nfrom hatch.utils.ci import running_in_ci\nfrom hatch.utils.fs import Path, temp_directory\nfrom hatch.utils.platform import Platform\nfrom hatch.utils.structures import EnvVars\nfrom hatch.venv.core import TempVirtualEnv\nfrom hatchling.cli import hatchling\n\nfrom .helpers.templates.licenses import MIT, Apache_2_0\n\nif TYPE_CHECKING:\n    from collections.abc import Generator\n    from unittest.mock import MagicMock\n\nPLATFORM = Platform()\n\n\nclass Devpi(NamedTuple):\n    repo: str\n    index_name: str\n    user: str\n    auth: str\n    ca_cert: str\n\n\nclass CliRunner(__CliRunner):\n    def __init__(self, command):\n        super().__init__()\n        self._command = command\n\n    def __call__(self, *args, **kwargs):\n        # Exceptions should always be handled\n        kwargs.setdefault(\"catch_exceptions\", False)\n\n        return self.invoke(self._command, args, **kwargs)\n\n\n@pytest.fixture(scope=\"session\")\ndef hatch(isolation):  # noqa: ARG001\n    from hatch import cli\n\n    return CliRunner(cli.hatch)\n\n\n@pytest.fixture(scope=\"session\")\ndef helpers():\n    # https://docs.pytest.org/en/latest/writing_plugins.html#assertion-rewriting\n    pytest.register_assert_rewrite(\"tests.helpers.helpers\")\n\n    from .helpers import helpers\n\n    return helpers\n\n\n@pytest.fixture(scope=\"session\", autouse=True)\ndef isolation(uv_on_path) -> Generator[Path, None, None]:\n    with temp_directory() as d:\n        data_dir = d / \"data\"\n        data_dir.mkdir()\n        cache_dir = d / \"cache\"\n        cache_dir.mkdir()\n\n        licenses_dir = cache_dir / \"licenses\"\n        licenses_dir.mkdir()\n        licenses_dir.joinpath(\"Apache-2.0.txt\").write_text(Apache_2_0)\n        licenses_dir.joinpath(\"MIT.txt\").write_text(MIT)\n\n        default_env_vars = {\n            AppEnvVars.NO_COLOR: \"1\",\n            ConfigEnvVars.DATA: str(data_dir),\n            ConfigEnvVars.CACHE: str(cache_dir),\n            PublishEnvVars.REPO: \"dev\",\n            \"HATCH_SELF_TESTING\": \"true\",\n            get_env_var(plugin_name=\"virtual\", option=\"uv_path\"): uv_on_path,\n            \"PYAPP_COMMAND_NAME\": os.urandom(4).hex(),\n            \"GIT_AUTHOR_NAME\": \"Foo Bar\",\n            \"GIT_AUTHOR_EMAIL\": \"foo@bar.baz\",\n            \"COLUMNS\": \"80\",\n            \"LINES\": \"24\",\n        }\n        if PLATFORM.windows:\n            default_env_vars[\"COMSPEC\"] = \"cmd.exe\"\n        else:\n            default_env_vars[\"SHELL\"] = \"sh\"\n\n        with d.as_cwd(default_env_vars):\n            os.environ.pop(AppEnvVars.ENV_ACTIVE, None)\n            os.environ.pop(AppEnvVars.FORCE_COLOR, None)\n            yield d\n\n\n@pytest.fixture(scope=\"session\")\ndef isolated_data_dir() -> Path:\n    return Path(os.environ[ConfigEnvVars.DATA])\n\n\n@pytest.fixture(scope=\"session\")\ndef default_data_dir() -> Path:\n    return Path(user_data_dir(\"hatch\", appauthor=False))\n\n\n@pytest.fixture(scope=\"session\")\ndef default_cache_dir() -> Path:\n    return Path(user_cache_dir(\"hatch\", appauthor=False))\n\n\n@pytest.fixture(scope=\"session\")\ndef platform():\n    return PLATFORM\n\n\n@pytest.fixture(scope=\"session\")\ndef current_platform():\n    return PLATFORM.name\n\n\n@pytest.fixture(scope=\"session\")\ndef current_arch():\n    import platform\n\n    return platform.machine().lower()\n\n\n@pytest.fixture(scope=\"session\")\ndef uri_slash_prefix():\n    return \"//\" if os.sep == \"/\" else \"///\"\n\n\n@pytest.fixture\ndef temp_dir() -> Generator[Path, None, None]:\n    with temp_directory() as d:\n        yield d\n\n\n@pytest.fixture\ndef temp_dir_data(temp_dir) -> Generator[Path, None, None]:\n    data_path = temp_dir / \"data\"\n    data_path.mkdir()\n\n    with EnvVars({ConfigEnvVars.DATA: str(data_path)}):\n        yield temp_dir\n\n\n@pytest.fixture\ndef temp_dir_cache(temp_dir) -> Generator[Path, None, None]:\n    cache_path = temp_dir / \"cache\"\n    cache_path.mkdir()\n\n    with EnvVars({ConfigEnvVars.CACHE: str(cache_path)}):\n        yield temp_dir\n\n\n@pytest.fixture(autouse=True)\ndef config_file(tmp_path) -> ConfigFile:\n    path = Path(tmp_path, \"config.toml\")\n    os.environ[ConfigEnvVars.CONFIG] = str(path)\n    config = ConfigFile(path)\n    config.restore()\n    return config\n\n\n@pytest.fixture(scope=\"session\")\ndef default_virtualenv_installed_requirements(helpers):\n    # PyPy installs extra packages by default\n    with TempVirtualEnv(sys.executable, PLATFORM):\n        output = PLATFORM.run_command([\"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\"utf-8\")\n        requirements = helpers.extract_requirements(output.splitlines())\n\n    return frozenset(requirements)\n\n\n@pytest.fixture(scope=\"session\")\ndef extract_installed_requirements(helpers, default_virtualenv_installed_requirements):\n    return lambda lines: [\n        requirement\n        for requirement in helpers.extract_requirements(lines)\n        if requirement not in default_virtualenv_installed_requirements\n    ]\n\n\n@pytest.fixture(scope=\"session\", autouse=True)\ndef python_on_path():\n    return Path(sys.executable).stem\n\n\n@pytest.fixture(scope=\"session\", autouse=True)\ndef uv_on_path():\n    return shutil.which(\"uv\")\n\n\n@pytest.fixture(scope=\"session\")\ndef compatible_python_distributions():\n    from hatch.python.resolve import get_compatible_distributions\n\n    return tuple(get_compatible_distributions())\n\n\n@pytest.fixture(scope=\"session\")\ndef global_application():\n    # This is only required for the EnvironmentInterface constructor and will never be used\n    from hatch.cli.application import Application\n\n    return Application(sys.exit, verbosity=0, enable_color=False, interactive=False)\n\n\n@pytest.fixture\ndef temp_application():\n    # This is only required for the EnvironmentInterface constructor and will never be used\n    from hatch.cli.application import Application\n\n    return Application(sys.exit, verbosity=0, enable_color=False, interactive=False)\n\n\n@pytest.fixture\ndef build_env_config():\n    return get_internal_env_config()[\"hatch-build\"]\n\n\n@pytest.fixture(scope=\"session\")\ndef devpi(tmp_path_factory, worker_id):\n    import platform\n\n    if not shutil.which(\"docker\") or (\n        running_in_ci() and (not PLATFORM.linux or platform.python_implementation() == \"PyPy\")\n    ):\n        pytest.skip(\"Not testing publishing\")\n\n    # This fixture is affected by https://github.com/pytest-dev/pytest-xdist/issues/271\n    root_tmp_dir = Path(tmp_path_factory.getbasetemp().parent)\n\n    devpi_data_file = root_tmp_dir / \"devpi_data.json\"\n    lock_file = f\"{devpi_data_file}.lock\"\n    devpi_started_sessions = root_tmp_dir / \"devpi_started_sessions\"\n    devpi_ended_sessions = root_tmp_dir / \"devpi_ended_sessions\"\n    devpi_data = root_tmp_dir / \"devpi_data\"\n    devpi_docker_data = devpi_data / \"docker\"\n    with FileLock(lock_file):\n        if devpi_data_file.is_file():\n            data = json.loads(devpi_data_file.read_text())\n        else:\n            import trustme\n\n            devpi_started_sessions.mkdir()\n            devpi_ended_sessions.mkdir()\n            devpi_data.mkdir()\n\n            shutil.copytree(Path(__file__).resolve().parent / \"index\" / \"server\", devpi_docker_data)\n\n            # https://github.com/python-trio/trustme/blob/master/trustme/_cli.py\n            # Generate the CA certificate\n            ca = trustme.CA()\n            cert = ca.issue_cert(\"localhost\", \"127.0.0.1\", \"::1\")\n\n            # Write the certificate and private key the server should use\n            server_config_dir = devpi_docker_data / \"nginx\"\n            server_key = str(server_config_dir / \"server.key\")\n            server_cert = str(server_config_dir / \"server.pem\")\n            cert.private_key_pem.write_to_path(path=server_key)\n            with open(server_cert, mode=\"w\", encoding=\"utf-8\") as f:\n                f.truncate()\n            for blob in cert.cert_chain_pems:\n                blob.write_to_path(path=server_cert, append=True)\n\n            # Write the certificate the client should trust\n            client_cert = str(devpi_data / \"client.pem\")\n            ca.cert_pem.write_to_path(path=client_cert)\n\n            data = {\"password\": os.urandom(16).hex(), \"ca_cert\": client_cert}\n            devpi_data_file.write_atomic(json.dumps(data), \"w\", encoding=\"utf-8\")\n\n    dp = Devpi(\"https://localhost:8443/hatch/testing/\", \"testing\", \"hatch\", data[\"password\"], data[\"ca_cert\"])\n    env_vars = {\"DEVPI_INDEX_NAME\": dp.index_name, \"DEVPI_USERNAME\": dp.user, \"DEVPI_PASSWORD\": dp.auth}\n\n    compose_file = str(devpi_docker_data / \"docker-compose.yaml\")\n    with FileLock(lock_file):\n        if not any(devpi_started_sessions.iterdir()):\n            with EnvVars(env_vars):\n                result = subprocess.run(\n                    [\"docker\", \"compose\", \"-f\", compose_file, \"up\", \"--build\", \"-d\", \"--wait\"],\n                    check=False,\n                    capture_output=True,\n                )\n                if result.returncode != 0:\n                    # Debugging info for if devpi fails to start\n                    logs = subprocess.run([\"docker\", \"logs\", \"hatch-devpi\"], check=False, capture_output=True)\n                    pytest.fail(\n                        f\"Failed to start devpi container, see logs:\\n{logs.stdout.decode()}\\n{logs.stderr.decode()}\"\n                    )\n\n            for _ in range(120):\n                output = subprocess.check_output([\"docker\", \"logs\", \"hatch-devpi\"]).decode(\"utf-8\")\n                if f\"Serving index {dp.user}/{dp.index_name}\" in output:\n                    time.sleep(15)\n                    break\n\n                time.sleep(1)\n            else:  # no cov\n                # Add logging here too for timeout case\n                import warnings\n\n                logs = subprocess.run([\"docker\", \"logs\", \"hatch-devpi\"], check=False, capture_output=True)\n                warnings.warn(\n                    f\"devpi container logs (timeout):\\n{logs.stdout.decode()}\\n{logs.stderr.decode()}\", stacklevel=1\n                )\n\n        (devpi_started_sessions / worker_id).touch()\n\n    try:\n        yield dp\n    finally:\n        with FileLock(lock_file):\n            (devpi_ended_sessions / worker_id).touch()\n            if len(list(devpi_started_sessions.iterdir())) == len(list(devpi_ended_sessions.iterdir())):\n                devpi_data_file.unlink()\n                shutil.rmtree(devpi_started_sessions)\n                shutil.rmtree(devpi_ended_sessions)\n\n                with EnvVars(env_vars):\n                    subprocess.run([\"docker\", \"compose\", \"-f\", compose_file, \"down\", \"-t\", \"0\"], capture_output=True)  # noqa: PLW1510\n\n                shutil.rmtree(devpi_data)\n\n\n@pytest.fixture\ndef env_run(mocker) -> Generator[MagicMock, None, None]:\n    run = mocker.patch(\"subprocess.run\", return_value=subprocess.CompletedProcess([], 0, stdout=b\"\"))\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment.exists\", return_value=True)\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment.dependency_hash\", return_value=\"\")\n    mocker.patch(\"hatch.env.virtual.VirtualEnvironment.command_context\")\n    return run\n\n\ndef is_hatchling_command(command: list[str] | str) -> bool:\n    if isinstance(command, str):\n        command = command.split()\n\n    if command[0] != \"python\":\n        return False\n\n    if \"-m\" not in command:\n        return False\n\n    return command[command.index(\"-m\") + 1] == \"hatchling\"\n\n\n@pytest.fixture\ndef mock_backend_process(request, mocker):\n    if \"allow_backend_process\" in request.keywords:\n        yield False\n        return\n\n    def mock_process_api(api):\n        def mock_process(command: list[str] | str, **kwargs):\n            if not is_hatchling_command(command):  # no cov\n                return api(command, **kwargs)\n\n            if isinstance(command, str):\n                command = command.split()\n\n            original_args = sys.argv\n            try:\n                sys.argv = command[3:]\n                mock = mocker.MagicMock()\n\n                try:\n                    # The builder sets process-wide environment variables\n                    with EnvVars():\n                        hatchling()\n                except SystemExit as e:\n                    mock.returncode = e.code\n                else:\n                    mock.returncode = 0\n\n                return mock\n            finally:\n                sys.argv = original_args\n\n        return mock_process\n\n    mocker.patch(\"hatch.utils.platform.Platform.run_command\", side_effect=mock_process_api(PLATFORM.run_command))\n\n    yield True\n\n\n@pytest.fixture\ndef mock_backend_process_output(request, mocker):\n    if \"allow_backend_process\" in request.keywords:\n        yield False\n        return\n\n    output_queue = []\n\n    def mock_process_api(api):\n        def mock_process(command, **kwargs):\n            if not is_hatchling_command(command):  # no cov\n                return api(command, **kwargs)\n\n            if isinstance(command, str):\n                command = command.split()\n\n            output_queue.clear()\n            original_args = sys.argv\n            try:\n                sys.argv = command[3:]\n                mock = mocker.MagicMock()\n\n                try:\n                    # The builder sets process-wide environment variables\n                    with EnvVars():\n                        hatchling()\n                except SystemExit as e:\n                    mock.returncode = e.code\n                else:\n                    mock.returncode = 0\n\n                mock.stdout = mock.stderr = \"\".join(output_queue).encode(\"utf-8\")\n                return mock\n            finally:\n                sys.argv = original_args\n\n        return mock_process\n\n    mocker.patch(\"subprocess.run\", side_effect=mock_process_api(subprocess.run))\n    mocker.patch(\"hatchling.bridge.app._display\", side_effect=lambda cmd, **_: output_queue.append(f\"{cmd}\\n\"))\n\n    yield True\n\n\n@pytest.fixture\ndef mock_plugin_installation(mocker):\n    subprocess_run = subprocess.run\n    mocked_subprocess_run = mocker.MagicMock(returncode=0)\n\n    def _mock(command, **kwargs):\n        if isinstance(command, list):\n            if command[:5] == [sys.executable, \"-u\", \"-m\", \"pip\", \"install\"]:\n                mocked_subprocess_run(command, **kwargs)\n                return mocked_subprocess_run\n\n            if command[:3] == [sys.executable, \"self\", \"python-path\"]:\n                return mocker.MagicMock(returncode=0, stdout=sys.executable.encode())\n\n        return subprocess_run(command, **kwargs)  # no cov\n\n    mocker.patch(\"subprocess.run\", side_effect=_mock)\n\n    return mocked_subprocess_run\n\n\ndef pytest_runtest_setup(item):\n    for marker in item.iter_markers():\n        if marker.name == \"requires_internet\" and not network_connectivity():  # no cov\n            pytest.skip(\"No network connectivity\")\n\n        if marker.name == \"requires_ci\" and not running_in_ci():  # no cov\n            pytest.skip(\"Not running in CI\")\n\n        if marker.name == \"requires_windows\" and not PLATFORM.windows:\n            pytest.skip(\"Not running on Windows\")\n\n        if marker.name == \"requires_macos\" and not PLATFORM.macos:\n            pytest.skip(\"Not running on macOS\")\n\n        if marker.name == \"requires_linux\" and not PLATFORM.linux:\n            pytest.skip(\"Not running on Linux\")\n\n        if marker.name == \"requires_unix\" and PLATFORM.windows:\n            pytest.skip(\"Not running on a Linux-based platform\")\n\n        if marker.name == \"requires_git\" and not git_available():  # no cov\n            pytest.skip(\"Git not present in the environment\")\n\n        if marker.name == \"requires_docker\" and not docker_available():  # no cov\n            pytest.skip(\"Docker not present in the environment\")\n\n        if marker.name == \"requires_cargo\" and not cargo_available():  # no cov\n            pytest.skip(\"Cargo not present in the environment\")\n\n\ndef pytest_configure(config):\n    config.addinivalue_line(\"markers\", \"requires_windows: Tests intended for Windows operating systems\")\n    config.addinivalue_line(\"markers\", \"requires_macos: Tests intended for macOS operating systems\")\n    config.addinivalue_line(\"markers\", \"requires_linux: Tests intended for Linux operating systems\")\n    config.addinivalue_line(\"markers\", \"requires_unix: Tests intended for Linux-based operating systems\")\n    config.addinivalue_line(\"markers\", \"requires_internet: Tests that require access to the internet\")\n    config.addinivalue_line(\"markers\", \"requires_git: Tests that require the git command available in the environment\")\n    config.addinivalue_line(\n        \"markers\", \"requires_docker: Tests that require the docker command available in the environment\"\n    )\n    config.addinivalue_line(\n        \"markers\", \"requires_cargo: Tests that require the cargo command available in the environment\"\n    )\n\n    config.addinivalue_line(\"markers\", \"allow_backend_process: Force the use of backend communication\")\n\n    config.getini(\"norecursedirs\").remove(\"build\")  # /tests/cli/build\n    config.getini(\"norecursedirs\").remove(\"venv\")  # /tests/venv\n\n\n@lru_cache\ndef network_connectivity():  # no cov\n    if running_in_ci():\n        return True\n\n    import socket\n\n    with suppress(Exception):\n        # Test availability of DNS first\n        host = socket.gethostbyname(\"www.google.com\")\n        # Test connection\n        socket.create_connection((host, 80), 2)\n        return True\n\n    return False\n\n\n@lru_cache\ndef git_available():  # no cov\n    if running_in_ci():\n        return True\n\n    return shutil.which(\"git\") is not None\n\n\n@lru_cache\ndef docker_available():  # no cov\n    if running_in_ci():\n        return True\n\n    return shutil.which(\"docker\") is not None\n\n\n@lru_cache\ndef cargo_available():  # no cov\n    if running_in_ci():\n        return True\n\n    return shutil.which(\"cargo\") is not None\n"
  },
  {
    "path": "tests/dep/__init__.py",
    "content": ""
  },
  {
    "path": "tests/dep/test_sync.py",
    "content": "import os\nimport sys\n\nimport pytest\n\nfrom hatch.dep.core import Dependency\nfrom hatch.dep.sync import InstalledDistributions\nfrom hatch.venv.core import TempUVVirtualEnv, TempVirtualEnv\n\n\ndef test_no_dependencies(platform):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([])\n\n\ndef test_dependency_not_found(platform):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert not distributions.dependencies_in_sync([Dependency(\"binary\")])\n\n\n@pytest.mark.requires_internet\ndef test_dependency_found(platform, uv_on_path):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command([uv_on_path, \"pip\", \"install\", \"binary\"], check=True, capture_output=True)\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([Dependency(\"binary\")])\n\n\n@pytest.mark.requires_internet\ndef test_version_unmet(platform, uv_on_path):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command([uv_on_path, \"pip\", \"install\", \"binary\"], check=True, capture_output=True)\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert not distributions.dependencies_in_sync([Dependency(\"binary>9000\")])\n\n\ndef test_marker_met(platform):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([Dependency('binary; python_version < \"1\"')])\n\n\ndef test_marker_unmet(platform):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert not distributions.dependencies_in_sync([Dependency('binary; python_version > \"1\"')])\n\n\n@pytest.mark.requires_internet\ndef test_extra_no_dependencies(platform, uv_on_path):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command([uv_on_path, \"pip\", \"install\", \"binary\"], check=True, capture_output=True)\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert not distributions.dependencies_in_sync([Dependency(\"binary[foo]\")])\n\n\n@pytest.mark.requires_internet\ndef test_unknown_extra(platform, uv_on_path):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command(\n            [uv_on_path, \"pip\", \"install\", \"requests[security]==2.25.1\"], check=True, capture_output=True\n        )\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert not distributions.dependencies_in_sync([Dependency(\"requests[foo]\")])\n\n\n@pytest.mark.requires_internet\ndef test_extra_unmet(platform, uv_on_path):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command([uv_on_path, \"pip\", \"install\", \"requests==2.25.1\"], check=True, capture_output=True)\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert not distributions.dependencies_in_sync([Dependency(\"requests[security]==2.25.1\")])\n\n\n@pytest.mark.requires_internet\ndef test_extra_met(platform, uv_on_path):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command(\n            [uv_on_path, \"pip\", \"install\", \"requests[security]==2.25.1\"], check=True, capture_output=True\n        )\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([Dependency(\"requests[security]==2.25.1\")])\n\n\n@pytest.mark.requires_internet\ndef test_local_dir(hatch, temp_dir, platform, uv_on_path):\n    project_name = os.urandom(10).hex()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / project_name\n    dependency_string = f\"{project_name}@{project_path.as_uri()}\"\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command([uv_on_path, \"pip\", \"install\", str(project_path)], check=True, capture_output=True)\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([Dependency(dependency_string)])\n\n\n@pytest.mark.requires_internet\ndef test_local_dir_editable(hatch, temp_dir, platform, uv_on_path):\n    project_name = os.urandom(10).hex()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / project_name\n    dependency_string = f\"{project_name}@{project_path.as_uri()}\"\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command([uv_on_path, \"pip\", \"install\", \"-e\", str(project_path)], check=True, capture_output=True)\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([Dependency(dependency_string, editable=True)])\n\n\n@pytest.mark.requires_internet\ndef test_local_dir_editable_mismatch(hatch, temp_dir, platform, uv_on_path):\n    project_name = os.urandom(10).hex()\n\n    with temp_dir.as_cwd():\n        result = hatch(\"new\", project_name)\n        assert result.exit_code == 0, result.output\n\n    project_path = temp_dir / project_name\n    dependency_string = f\"{project_name}@{project_path.as_uri()}\"\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command([uv_on_path, \"pip\", \"install\", \"-e\", str(project_path)], check=True, capture_output=True)\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert not distributions.dependencies_in_sync([Dependency(dependency_string)])\n\n\n@pytest.mark.requires_internet\n@pytest.mark.requires_git\ndef test_dependency_git_pip(platform):\n    with TempVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command(\n            [\"pip\", \"install\", \"requests@git+https://github.com/psf/requests\"], check=True, capture_output=True\n        )\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([Dependency(\"requests@git+https://github.com/psf/requests\")])\n\n\n@pytest.mark.requires_internet\n@pytest.mark.requires_git\ndef test_dependency_git_uv(platform, uv_on_path):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command(\n            [uv_on_path, \"pip\", \"install\", \"requests@git+https://github.com/psf/requests\"],\n            check=True,\n            capture_output=True,\n        )\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([Dependency(\"requests@git+https://github.com/psf/requests\")])\n\n\n@pytest.mark.requires_internet\n@pytest.mark.requires_git\ndef test_dependency_git_revision_pip(platform):\n    with TempVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command(\n            [\"pip\", \"install\", \"requests@git+https://github.com/psf/requests@main\"], check=True, capture_output=True\n        )\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([Dependency(\"requests@git+https://github.com/psf/requests@main\")])\n\n\n@pytest.mark.requires_internet\n@pytest.mark.requires_git\ndef test_dependency_git_revision_uv(platform, uv_on_path):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command(\n            [uv_on_path, \"pip\", \"install\", \"requests@git+https://github.com/psf/requests@main\"],\n            check=True,\n            capture_output=True,\n        )\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([Dependency(\"requests@git+https://github.com/psf/requests@main\")])\n\n\n@pytest.mark.requires_internet\n@pytest.mark.requires_git\ndef test_dependency_git_commit(platform, uv_on_path):\n    with TempUVVirtualEnv(sys.executable, platform) as venv:\n        platform.run_command(\n            [\n                uv_on_path,\n                \"pip\",\n                \"install\",\n                \"requests@git+https://github.com/psf/requests@7f694b79e114c06fac5ec06019cada5a61e5570f\",\n            ],\n            check=True,\n            capture_output=True,\n        )\n        distributions = InstalledDistributions(sys_path=venv.sys_path)\n        assert distributions.dependencies_in_sync([\n            Dependency(\"requests@git+https://github.com/psf/requests@7f694b79e114c06fac5ec06019cada5a61e5570f\")\n        ])\n\n\ndef test_dependency_path_with_unresolved_context_variable():\n    \"\"\"\n    Regression test: Dependency.path should raise ValueError for unresolved context variables.\n    Context variables must be resolved before creating Dependency objects.\n    \"\"\"\n    unformatted_dep_string = \"my-package @ {root:parent:uri}/my-package\"\n    dep = Dependency(unformatted_dep_string)\n\n    with pytest.raises(ValueError, match=\"invalid scheme\"):\n        _ = dep.path\n\n\ndef test_dependency_path_with_special_characters():\n    \"\"\"\n    Regression test: Dependency.path should handle URL-encoded special characters.\n    Paths with special characters like '+' get URL-encoded to '%2B' in URIs,\n    and should be decoded back when accessing the path property.\n    \"\"\"\n    # Create a dependency with a path containing URL-encoded special character\n    # Simulating what happens when a path with '+' is converted via .as_uri()\n    dep_string = \"my-package @ file:///tmp/my%2Bproject\"\n    dep = Dependency(dep_string)\n\n    # The path property should decode %2B back to +\n    assert dep.path is not None\n    assert \"my+project\" in str(dep.path)\n"
  },
  {
    "path": "tests/env/__init__.py",
    "content": ""
  },
  {
    "path": "tests/env/collectors/__init__.py",
    "content": ""
  },
  {
    "path": "tests/env/collectors/test_custom.py",
    "content": "import re\n\nimport pytest\n\nfrom hatch.env.collectors.custom import CustomEnvironmentCollector\nfrom hatch.plugin.constants import DEFAULT_CUSTOM_SCRIPT\n\n\ndef test_no_path(isolation):\n    config = {\"path\": \"\"}\n\n    with pytest.raises(\n        ValueError, match=\"Option `path` for environment collector `custom` must not be empty if defined\"\n    ):\n        CustomEnvironmentCollector(str(isolation), config)\n\n\ndef test_path_not_string(isolation):\n    config = {\"path\": 3}\n\n    with pytest.raises(TypeError, match=\"Option `path` for environment collector `custom` must be a string\"):\n        CustomEnvironmentCollector(str(isolation), config)\n\n\ndef test_nonexistent(isolation):\n    config = {\"path\": \"test.py\"}\n\n    with pytest.raises(OSError, match=\"Plugin script does not exist: test.py\"):\n        CustomEnvironmentCollector(str(isolation), config)\n\n\ndef test_default(temp_dir, helpers):\n    config = {}\n\n    file_path = temp_dir / DEFAULT_CUSTOM_SCRIPT\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\n\n            class CustomHook(EnvironmentCollectorInterface):\n                def foo(self):\n                    return self.PLUGIN_NAME, self.root\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        hook = CustomEnvironmentCollector(str(temp_dir), config)\n\n    assert hook.foo() == (\"custom\", str(temp_dir))\n\n\ndef test_explicit_path(temp_dir, helpers):\n    config = {\"path\": f\"foo/{DEFAULT_CUSTOM_SCRIPT}\"}\n\n    file_path = temp_dir / \"foo\" / DEFAULT_CUSTOM_SCRIPT\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\n\n            class CustomHook(EnvironmentCollectorInterface):\n                def foo(self):\n                    return self.PLUGIN_NAME, self.root\n            \"\"\"\n        )\n    )\n\n    with temp_dir.as_cwd():\n        hook = CustomEnvironmentCollector(str(temp_dir), config)\n\n    assert hook.foo() == (\"custom\", str(temp_dir))\n\n\ndef test_no_subclass(temp_dir, helpers):\n    config = {\"path\": f\"foo/{DEFAULT_CUSTOM_SCRIPT}\"}\n\n    file_path = temp_dir / \"foo\" / DEFAULT_CUSTOM_SCRIPT\n    file_path.ensure_parent_dir_exists()\n    file_path.write_text(\n        helpers.dedent(\n            \"\"\"\n            from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\n\n            foo = None\n            bar = 'baz'\n\n            class CustomHook:\n                pass\n            \"\"\"\n        )\n    )\n\n    with (\n        pytest.raises(\n            ValueError,\n            match=re.escape(\n                f\"Unable to find a subclass of `EnvironmentCollectorInterface` in `foo/{DEFAULT_CUSTOM_SCRIPT}`: {temp_dir}\"\n            ),\n        ),\n        temp_dir.as_cwd(),\n    ):\n        CustomEnvironmentCollector(str(temp_dir), config)\n"
  },
  {
    "path": "tests/env/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "tests/env/plugin/test_interface.py",
    "content": "import re\n\nimport pytest\n\nfrom hatch.config.constants import AppEnvVars\nfrom hatch.env.plugin.interface import EnvironmentInterface\nfrom hatch.project.core import Project\nfrom hatch.utils.structures import EnvVars\n\n\nclass MockEnvironment(EnvironmentInterface):  # no cov\n    PLUGIN_NAME = \"mock\"\n\n    def find(self):\n        pass\n\n    def create(self):\n        pass\n\n    def remove(self):\n        pass\n\n    def exists(self):\n        pass\n\n    def install_project(self):\n        pass\n\n    def install_project_dev_mode(self):\n        pass\n\n    def dependencies_in_sync(self):\n        pass\n\n    def sync_dependencies(self):\n        pass\n\n\nclass TestEnvVars:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.env_vars == environment.env_vars == {AppEnvVars.ENV_ACTIVE: \"default\"}\n\n    def test_not_table(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-vars\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.env-vars` must be a mapping\"):\n            _ = environment.env_vars\n\n    def test_value_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-vars\": {\"foo\": 9000}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Environment variable `foo` of field `tool.hatch.envs.default.env-vars` must be a string\"\n        ):\n            _ = environment.env_vars\n\n    def test_correct(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-vars\": {\"foo\": \"bar\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.env_vars == {AppEnvVars.ENV_ACTIVE: \"default\", \"foo\": \"bar\"}\n\n    def test_context_formatting(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-vars\": {\"foo\": \"{env:FOOBAZ}-{matrix:bar}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {\"bar\": \"42\"},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with EnvVars({\"FOOBAZ\": \"baz\"}):\n            assert environment.env_vars == {AppEnvVars.ENV_ACTIVE: \"default\", \"foo\": \"baz-42\"}\n\n\nclass TestEnvInclude:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.env_include == environment.env_include == []\n\n    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-include\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.env-include` must be an array\"):\n            _ = environment.env_include\n\n    def test_pattern_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-include\": [9000]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Pattern #1 of field `tool.hatch.envs.default.env-include` must be a string\"\n        ):\n            _ = environment.env_include\n\n    def test_correct(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-include\": [\"FOO*\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.env_include == [\"HATCH_BUILD_*\", \"FOO*\"]\n\n\nclass TestEnvExclude:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.env_exclude == environment.env_exclude == []\n\n    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-exclude\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.env-exclude` must be an array\"):\n            _ = environment.env_exclude\n\n    def test_pattern_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-exclude\": [9000]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Pattern #1 of field `tool.hatch.envs.default.env-exclude` must be a string\"\n        ):\n            _ = environment.env_exclude\n\n    def test_correct(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"env-exclude\": [\"FOO*\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.env_exclude == [\"FOO*\"]\n\n\nclass TestPlatforms:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.platforms == environment.platforms == []\n\n    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"platforms\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.platforms` must be an array\"):\n            _ = environment.platforms\n\n    def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"platforms\": [9000]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Platform #1 of field `tool.hatch.envs.default.platforms` must be a string\"\n        ):\n            _ = environment.platforms\n\n    def test_correct(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"platforms\": [\"macOS\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.platforms == [\"macos\"]\n\n\nclass TestSkipInstall:\n    def test_default_project(self, temp_dir, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(temp_dir, config=config)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n        (temp_dir / \"pyproject.toml\").touch()\n\n        with temp_dir.as_cwd():\n            assert environment.skip_install is environment.skip_install is False\n\n    def test_default_no_project(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.skip_install is environment.skip_install is True\n\n    def test_not_boolean(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"skip-install\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.skip-install` must be a boolean\"):\n            _ = environment.skip_install\n\n    def test_enable(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"skip-install\": True}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.skip_install is True\n\n\nclass TestDevMode:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.dev_mode is environment.dev_mode is True\n\n    def test_not_boolean(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dev-mode\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.dev-mode` must be a boolean\"):\n            _ = environment.dev_mode\n\n    def test_disable(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dev-mode\": False}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.dev_mode is False\n\n\nclass TestBuilder:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.builder is False\n\n    def test_not_boolean(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"builder\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.builder` must be a boolean\"):\n            _ = environment.builder\n\n    def test_enable(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"builder\": True}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.builder is True\n\n\nclass TestFeatures:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.features == environment.features == []\n\n    def test_invalid_type(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"features\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.features` must be an array of strings\"):\n            _ = environment.features\n\n    def test_correct(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"foo-bar\": [], \"baz\": []}},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"features\": [\"Foo...Bar\", \"Baz\", \"baZ\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.features == [\"baz\", \"foo-bar\"]\n\n    def test_feature_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"foo\": [], \"bar\": []}},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"features\": [9000]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Feature #1 of field `tool.hatch.envs.default.features` must be a string\"):\n            _ = environment.features\n\n    def test_feature_empty_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"foo\": [], \"bar\": []}},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"features\": [\"\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError, match=\"Feature #1 of field `tool.hatch.envs.default.features` cannot be an empty string\"\n        ):\n            _ = environment.features\n\n    def test_feature_undefined(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"optional-dependencies\": {\"foo\": []}},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"features\": [\"foo\", \"bar\", \"\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Feature `bar` of field `tool.hatch.envs.default.features` is not defined in \"\n                \"field `project.optional-dependencies`\"\n            ),\n        ):\n            _ = environment.features\n\n\nclass TestDependencyGroups:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.dependency_groups == []\n\n    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependency-groups\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Field `tool.hatch.envs.default.dependency-groups` must be an array of strings\"\n        ):\n            _ = environment.dependency_groups\n\n    def test_correct(self, isolation, isolated_data_dir, platform, temp_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"dependency-groups\": {\"foo-bar\": [], \"baz\": []},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependency-groups\": [\"Foo...Bar\", \"Baz\", \"baZ\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert environment.dependency_groups == [\"baz\", \"foo-bar\"]\n\n    def test_group_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"dependency-groups\": {\"foo\": [], \"bar\": []},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependency-groups\": [9000]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Group #1 of field `tool.hatch.envs.default.dependency-groups` must be a string\"\n        ):\n            _ = environment.dependency_groups\n\n    def test_group_empty_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"dependency-groups\": {\"foo\": [], \"bar\": []},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependency-groups\": [\"\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError, match=\"Group #1 of field `tool.hatch.envs.default.dependency-groups` cannot be an empty string\"\n        ):\n            _ = environment.dependency_groups\n\n    def test_group_undefined(self, isolation, isolated_data_dir, platform, temp_application):\n        config = {\n            \"project\": {\n                \"name\": \"my_app\",\n                \"version\": \"0.0.1\",\n            },\n            \"dependency-groups\": {\"foo\": []},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependency-groups\": [\"foo\", \"bar\", \"\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Group `bar` of field `tool.hatch.envs.default.dependency-groups` is not \"\n                \"defined in field `dependency-groups`\"\n            ),\n        ):\n            _ = environment.dependency_groups\n\n\nclass TestDescription:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.description == environment.description == \"\"\n\n    def test_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"description\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.description` must be a string\"):\n            _ = environment.description\n\n    def test_correct(self, isolation, isolated_data_dir, platform, global_application):\n        description = \"foo\"\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"description\": description}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.description is description\n\n\nclass TestDependencies:\n    def test_default(self, isolation, isolated_data_dir, platform, temp_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"skip-install\": False}}}},\n        }\n        project = Project(isolation, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert environment.dependencies == environment.dependencies == [\"dep1\"]\n        assert len(environment.dependencies) == len(environment.dependencies_complex)\n\n    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependencies\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.dependencies` must be an array\"):\n            _ = environment.dependencies\n\n    def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependencies\": [9000]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Dependency #1 of field `tool.hatch.envs.default.dependencies` must be a string\"\n        ):\n            _ = environment.dependencies\n\n    def test_invalid(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependencies\": [\"foo^1\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError, match=\"Dependency #1 of field `tool.hatch.envs.default.dependencies` is invalid: .+\"\n        ):\n            _ = environment.dependencies\n\n    def test_extra_not_array(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"extra-dependencies\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.extra-dependencies` must be an array\"):\n            _ = environment.dependencies\n\n    def test_extra_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"extra-dependencies\": [9000]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Dependency #1 of field `tool.hatch.envs.default.extra-dependencies` must be a string\"\n        ):\n            _ = environment.dependencies\n\n    def test_extra_invalid(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"extra-dependencies\": [\"foo^1\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError, match=\"Dependency #1 of field `tool.hatch.envs.default.extra-dependencies` is invalid: .+\"\n        ):\n            _ = environment.dependencies\n\n    def test_full(self, isolation, isolated_data_dir, platform, temp_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"default\": {\"skip-install\": False, \"dependencies\": [\"dep2\"], \"extra-dependencies\": [\"dep3\"]}\n                    }\n                }\n            },\n        }\n        project = Project(isolation, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert environment.dependencies == [\"dep2\", \"dep3\", \"dep1\"]\n\n    def test_context_formatting(self, isolation, isolated_data_dir, platform, temp_application, uri_slash_prefix):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"default\": {\n                            \"skip-install\": False,\n                            \"dependencies\": [\"dep2\"],\n                            \"extra-dependencies\": [\"proj @ {root:uri}\"],\n                        }\n                    }\n                }\n            },\n        }\n        project = Project(isolation, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        normalized_path = str(isolation).replace(\"\\\\\", \"/\")\n        assert environment.dependencies == [\"dep2\", f\"proj @ file:{uri_slash_prefix}{normalized_path}\", \"dep1\"]\n\n    def test_project_dependencies_context_formatting(\n        self, temp_dir, isolated_data_dir, platform, temp_application, uri_slash_prefix\n    ):\n        \"\"\"\n        Regression test for context formatting in project dependencies.\n        Ensures that dependencies in [project] section with context variables\n        like {root:parent:uri} are properly formatted before creating Dependency objects.\n        \"\"\"\n        # Create a sibling project\n        sibling_project = temp_dir.parent / \"sibling-project\"\n        sibling_project.mkdir(exist_ok=True)\n        (sibling_project / \"pyproject.toml\").write_text(\n            \"\"\"\\\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [project]\n    name = \"sibling-project\"\n    version = \"0.0.1\"\n    \"\"\"\n        )\n\n        config = {\n            \"project\": {\n                \"name\": \"my_app\",\n                \"version\": \"0.0.1\",\n                \"dependencies\": [\"sibling-project @ {root:parent:uri}/sibling-project\"],\n            },\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"skip-install\": False}}}},\n        }\n        project = Project(temp_dir, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        normalized_parent_path = str(temp_dir.parent).replace(\"\\\\\", \"/\")\n        expected_dep = f\"sibling-project @ file:{uri_slash_prefix}{normalized_parent_path}/sibling-project\"\n\n        # Verify the dependency was formatted correctly\n        assert expected_dep in environment.dependencies\n\n        # Verify we can access the path property without errors\n        for dep in environment.project_dependencies_complex:\n            if dep.name == \"sibling-project\":\n                assert dep.path is not None\n                assert \"sibling-project\" in str(dep.path)\n\n    def test_full_skip_install(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"default\": {\"dependencies\": [\"dep2\"], \"extra-dependencies\": [\"dep3\"], \"skip-install\": True}\n                    }\n                }\n            },\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.dependencies == [\"dep2\", \"dep3\"]\n\n    def test_full_skip_install_and_features(self, isolation, isolated_data_dir, platform, temp_application):\n        config = {\n            \"project\": {\n                \"name\": \"my_app\",\n                \"version\": \"0.0.1\",\n                \"dependencies\": [\"dep1\"],\n                \"optional-dependencies\": {\"feat\": [\"dep4\"]},\n            },\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"default\": {\n                            \"dependencies\": [\"dep2\"],\n                            \"extra-dependencies\": [\"dep3\"],\n                            \"skip-install\": True,\n                            \"features\": [\"feat\"],\n                        }\n                    }\n                }\n            },\n        }\n        project = Project(isolation, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert environment.dependencies == [\"dep2\", \"dep3\", \"dep4\"]\n\n    def test_full_skip_install_and_dependency_groups(self, isolation, isolated_data_dir, platform, temp_application):\n        config = {\n            \"project\": {\n                \"name\": \"my_app\",\n                \"version\": \"0.0.1\",\n                \"dependencies\": [\"dep1\"],\n            },\n            \"dependency-groups\": {\n                \"foo\": [\"dep5\"],\n                \"bar\": [\"dep4\", {\"include-group\": \"foo\"}],\n            },\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"default\": {\n                            \"dependencies\": [\"dep2\"],\n                            \"extra-dependencies\": [\"dep3\"],\n                            \"skip-install\": True,\n                            \"dependency-groups\": [\"bar\"],\n                        }\n                    }\n                }\n            },\n        }\n        project = Project(isolation, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert environment.dependencies == [\"dep2\", \"dep3\", \"dep4\", \"dep5\"]\n\n    def test_full_no_dev_mode(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\"default\": {\"dependencies\": [\"dep2\"], \"extra-dependencies\": [\"dep3\"], \"dev-mode\": False}}\n                }\n            },\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.dependencies == [\"dep2\", \"dep3\"]\n\n    def test_builder(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"build-system\": {\"requires\": [\"dep2\"]},\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\n                \"hatch\": {\"envs\": {\"default\": {\"skip-install\": False, \"builder\": True, \"dependencies\": [\"dep3\"]}}}\n            },\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.dependencies == [\"dep3\", \"dep2\"]\n\n    def test_workspace(self, temp_dir, isolated_data_dir, platform, temp_application):\n        for i in range(3):\n            project_file = temp_dir / f\"foo{i}\" / \"pyproject.toml\"\n            project_file.parent.mkdir()\n            project_file.write_text(\n                f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"foo{i}\"\nversion = \"0.0.1\"\ndependencies = [\"pkg-{i}\"]\n\n[project.optional-dependencies]\nfeature1 = [\"pkg-feature-1{i}\"]\nfeature2 = [\"pkg-feature-2{i}\"]\nfeature3 = [\"pkg-feature-3{i}\"]\n\"\"\"\n            )\n\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\", \"dependencies\": [\"dep1\"]},\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"default\": {\n                            \"skip-install\": False,\n                            \"dependencies\": [\"dep2\"],\n                            \"extra-dependencies\": [\"dep3\"],\n                            \"workspace\": {\n                                \"members\": [\n                                    {\"path\": \"foo0\", \"features\": [\"feature1\"]},\n                                    {\"path\": \"foo1\", \"features\": [\"feature1\", \"feature2\"]},\n                                    {\"path\": \"foo2\", \"features\": [\"feature1\", \"feature2\", \"feature3\"]},\n                                ],\n                            },\n                        },\n                    },\n                },\n            },\n        }\n        project = Project(temp_dir, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert environment.dependencies == [\n            \"dep2\",\n            \"dep3\",\n            \"pkg-0\",\n            \"pkg-feature-10\",\n            \"pkg-1\",\n            \"pkg-feature-11\",\n            \"pkg-feature-21\",\n            \"pkg-2\",\n            \"pkg-feature-12\",\n            \"pkg-feature-22\",\n            \"pkg-feature-32\",\n            \"dep1\",\n        ]\n\n    def test_self_referencing_dependency_with_extras(self, temp_dir, isolated_data_dir, platform, global_application):\n        \"\"\"Test that self-referencing dependencies with extras include the extra's dependencies.\"\"\"\n        project_dir = temp_dir / \"my-app\"\n        project_dir.mkdir()\n\n        (project_dir / \"my_app\").mkdir()\n        (project_dir / \"my_app\" / \"__init__.py\").write_text(\"\")\n        (project_dir / \"my_app\" / \"__about__.py\").write_text('__version__ = \"0.0.1\"')\n\n        config = {\n            \"project\": {\n                \"name\": \"my-app\",\n                \"version\": \"0.0.1\",\n                \"dependencies\": [],\n                \"optional-dependencies\": {\n                    \"test\": [\"pytest>=7.0\"],\n                },\n            },\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"dev\": {\n                            \"skip-install\": False,\n                            \"dependencies\": [\"my-app[test]\"],\n                        }\n                    }\n                }\n            },\n        }\n\n        project = Project(project_dir, config=config)\n        global_application.project = project\n\n        environment = MockEnvironment(\n            project_dir,\n            project.metadata,\n            \"dev\",\n            project.config.envs[\"dev\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        # Get all dependencies as strings (what would be passed to pip)\n        all_deps_str = [str(d) for d in environment.all_dependencies_complex]\n\n        # Should have the local installation\n        assert any(\"my-app\" in dep and \"file://\" in dep for dep in all_deps_str)\n\n        # Should have my-app[test] which will cause pip to install pytest\n        assert any(\"pytest\" in dep.lower() for dep in all_deps_str)\n\n    def test_dev_mode_true_returns_editable(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify dev-mode=true creates editable local dependency.\"\"\"\n        # Create a pyproject.toml file so skip_install defaults to False\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n\n    [tool.hatch.envs.default]\n    dev-mode = true\n    \"\"\")\n\n        project = Project(temp_dir)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        local_deps = environment.local_dependencies_complex\n\n        assert len(local_deps) == 1\n        assert local_deps[0].editable is True\n\n    def test_dev_mode_false_returns_non_editable(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify dev-mode=false creates non-editable local dependency.\"\"\"\n        # Create a pyproject.toml file so skip_install defaults to False\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n\n    [tool.hatch.envs.default]\n    dev-mode = false\n    \"\"\")\n\n        project = Project(temp_dir)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        local_deps = environment.local_dependencies_complex\n\n        assert len(local_deps) == 1\n        assert local_deps[0].editable is False\n\n    def test_skip_install_returns_empty(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify skip-install=true returns empty local dependencies.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n\n    [tool.hatch.envs.default]\n    skip-install = true\n    \"\"\")\n\n        project = Project(temp_dir)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        local_deps = environment.local_dependencies_complex\n\n        assert len(local_deps) == 0\n\n    def test_workspace_members_always_editable(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify workspace members are always editable regardless of dev-mode.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n\n    [tool.hatch.envs.default]\n    dev-mode = false\n    \"\"\")\n\n        project = Project(temp_dir)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        local_deps = environment.local_dependencies_complex\n\n        # Project itself should respect dev-mode=false\n        assert len(local_deps) == 1\n        assert local_deps[0].editable is False\n\n    def test_dependency_group_resolution(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Test dependency group resolution.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n\n    [dependency-groups]\n    test = [\"pytest\"]\n\n    [tool.hatch.envs.default]\n    dependency-groups = [\"test\"]\n    \"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        deps = environment.project_dependencies_complex\n        assert any(\"pytest\" in str(d) for d in deps)\n\n    def test_dependency_group_resolution_builder_false_dev_mode_false(\n        self, temp_dir, isolated_data_dir, platform, temp_application\n    ):\n        \"\"\"Test dependency group resolution in non-builder non-dev-mode environments.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n\n    [dependency-groups]\n    test = [\"pytest\"]\n\n    [tool.hatch.envs.default]\n    builder = false\n    dev-mode = false\n    dependency-groups = [\"test\"]\n    \"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert any(\"pytest\" in str(d) for d in environment.project_dependencies_complex)\n        assert any(\"pytest\" in str(d) for d in environment.dependencies_complex)\n\n    def test_dependency_group_resolution_builder_true_dev_mode_false(\n        self, temp_dir, isolated_data_dir, platform, temp_application\n    ):\n        \"\"\"Test dependency group resolution in builder non-dev-mode environments.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n\n    [dependency-groups]\n    test = [\"pytest\"]\n\n    [tool.hatch.envs.default]\n    builder = true\n    dev-mode = false\n    dependency-groups = [\"test\"]\n    \"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert any(\"pytest\" in str(d) for d in environment.project_dependencies_complex)\n        assert any(\"pytest\" in str(d) for d in environment.dependencies_complex)\n\n    def test_dependency_group_resolution_builder_true_dev_mode_true(\n        self, temp_dir, isolated_data_dir, platform, temp_application\n    ):\n        \"\"\"Test dependency group resolution in builder dev-mode environments.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n\n    [dependency-groups]\n    test = [\"pytest\"]\n\n    [tool.hatch.envs.default]\n    builder = true\n    dev-mode = true\n    dependency-groups = [\"test\"]\n    \"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert any(\"pytest\" in str(d) for d in environment.project_dependencies_complex)\n        assert any(\"pytest\" in str(d) for d in environment.dependencies_complex)\n\n    def test_additional_dependencies_as_strings(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Test additional_dependencies with string values.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n    \"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        environment.additional_dependencies = [\"extra-dep\"]\n        deps = environment.dependencies_complex\n        assert any(\"extra-dep\" in str(d) for d in deps)\n\n\nclass TestScripts:\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_not_table(self, isolation, isolated_data_dir, platform, global_application, field):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=f\"Field `tool.hatch.envs.default.{field}` must be a table\"):\n            _ = environment.scripts\n\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_name_contains_spaces(self, isolation, isolated_data_dir, platform, global_application, field):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: {\"foo bar\": []}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=f\"Script name `foo bar` in field `tool.hatch.envs.default.{field}` must not contain spaces\",\n        ):\n            _ = environment.scripts\n\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.scripts == environment.scripts == {}\n\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_single_commands(self, isolation, isolated_data_dir, platform, global_application, field):\n        script_config = {\"foo\": \"command1\", \"bar\": \"command2\"}\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: script_config}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.scripts == {\"foo\": [\"command1\"], \"bar\": [\"command2\"]}\n\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_multiple_commands(self, isolation, isolated_data_dir, platform, global_application, field):\n        script_config = {\"foo\": \"command1\", \"bar\": [\"command3\", \"command2\"]}\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: script_config}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.scripts == {\"foo\": [\"command1\"], \"bar\": [\"command3\", \"command2\"]}\n\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_multiple_commands_not_string(self, isolation, isolated_data_dir, platform, global_application, field):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: {\"foo\": [9000]}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=f\"Command #1 in field `tool.hatch.envs.default.{field}.foo` must be a string\"\n        ):\n            _ = environment.scripts\n\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_config_invalid_type(self, isolation, isolated_data_dir, platform, global_application, field):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: {\"foo\": 9000}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=f\"Field `tool.hatch.envs.default.{field}.foo` must be a string or an array of strings\"\n        ):\n            _ = environment.scripts\n\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_command_expansion_basic(self, isolation, isolated_data_dir, platform, global_application, field):\n        script_config = {\"foo\": \"command1\", \"bar\": [\"command3\", \"foo\"]}\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: script_config}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.scripts == {\"foo\": [\"command1\"], \"bar\": [\"command3\", \"command1\"]}\n\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_command_expansion_multiple_nested(self, isolation, isolated_data_dir, platform, global_application, field):\n        script_config = {\n            \"foo\": \"command3\",\n            \"baz\": [\"command5\", \"bar\", \"foo\", \"command1\"],\n            \"bar\": [\"command4\", \"foo\", \"command2\"],\n        }\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: script_config}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.scripts == {\n            \"foo\": [\"command3\"],\n            \"baz\": [\"command5\", \"command4\", \"command3\", \"command2\", \"command3\", \"command1\"],\n            \"bar\": [\"command4\", \"command3\", \"command2\"],\n        }\n\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_command_expansion_multiple_nested_ignore_exit_code(\n        self, isolation, isolated_data_dir, platform, global_application, field\n    ):\n        script_config = {\n            \"foo\": \"command3\",\n            \"baz\": [\"command5\", \"- bar\", \"foo\", \"command1\"],\n            \"bar\": [\"command4\", \"- foo\", \"command2\"],\n        }\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: script_config}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.scripts == {\n            \"foo\": [\"command3\"],\n            \"baz\": [\"command5\", \"- command4\", \"- command3\", \"- command2\", \"command3\", \"command1\"],\n            \"bar\": [\"command4\", \"- command3\", \"command2\"],\n        }\n\n    @pytest.mark.parametrize(\"field\", [\"scripts\", \"extra-scripts\"])\n    def test_command_expansion_modification(self, isolation, isolated_data_dir, platform, global_application, field):\n        script_config = {\n            \"foo\": \"command3\",\n            \"baz\": [\"command5\", \"bar world\", \"foo\", \"command1\"],\n            \"bar\": [\"command4\", \"foo hello\", \"command2\"],\n        }\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {field: script_config}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.scripts == {\n            \"foo\": [\"command3\"],\n            \"baz\": [\"command5\", \"command4 world\", \"command3 hello world\", \"command2 world\", \"command3\", \"command1\"],\n            \"bar\": [\"command4\", \"command3 hello\", \"command2\"],\n        }\n\n    def test_command_expansion_circular_inheritance(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"bar\", \"bar\": \"foo\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=\"Circular expansion detected for field `tool.hatch.envs.default.scripts`: foo -> bar -> foo\",\n        ):\n            _ = environment.scripts\n\n    def test_extra_less_precedence(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"default\": {\n                            \"extra-scripts\": {\"foo\": \"command4\", \"baz\": \"command3\"},\n                            \"scripts\": {\"foo\": \"command1\", \"bar\": \"command2\"},\n                        }\n                    }\n                },\n            },\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.scripts == {\"foo\": [\"command1\"], \"bar\": [\"command2\"], \"baz\": [\"command3\"]}\n\n\nclass TestPreInstallCommands:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.pre_install_commands == environment.pre_install_commands == []\n\n    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"pre-install-commands\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.pre-install-commands` must be an array\"):\n            _ = environment.pre_install_commands\n\n    def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"pre-install-commands\": [9000]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Command #1 of field `tool.hatch.envs.default.pre-install-commands` must be a string\"\n        ):\n            _ = environment.pre_install_commands\n\n    def test_correct(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"pre-install-commands\": [\"baz test\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.pre_install_commands == [\"baz test\"]\n\n\nclass TestPostInstallCommands:\n    def test_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.post_install_commands == environment.post_install_commands == []\n\n    def test_not_array(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"post-install-commands\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.post-install-commands` must be an array\"):\n            _ = environment.post_install_commands\n\n    def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"post-install-commands\": [9000]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError, match=\"Command #1 of field `tool.hatch.envs.default.post-install-commands` must be a string\"\n        ):\n            _ = environment.post_install_commands\n\n    def test_correct(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"post-install-commands\": [\"baz test\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.post_install_commands == [\"baz test\"]\n\n\nclass TestEnvVarOption:\n    def test_unset(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.get_env_var_option(\"foo\") == \"\"\n\n    def test_set(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with EnvVars({\"HATCH_ENV_TYPE_MOCK_FOO\": \"bar\"}):\n            assert environment.get_env_var_option(\"foo\") == \"bar\"\n\n\nclass TestContextFormatting:\n    def test_env_name(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"command {env_name}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert list(environment.expand_command(\"foo\")) == [\"command default\"]\n\n    def test_env_type(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"command {env_type}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert list(environment.expand_command(\"foo\")) == [\"command mock\"]\n\n    def test_verbosity_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"command -v={verbosity}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            9000,\n            global_application,\n        )\n\n        assert list(environment.expand_command(\"foo\")) == [\"command -v=9000\"]\n\n    def test_verbosity_unknown_modifier(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"command {verbosity:bar}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(ValueError, match=\"Unknown verbosity modifier: bar\"):\n            next(environment.expand_command(\"foo\"))\n\n    def test_verbosity_flag_adjustment_not_integer(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"command {verbosity:flag:-1.0}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Verbosity flag adjustment must be an integer: -1.0\"):\n            next(environment.expand_command(\"foo\"))\n\n    @pytest.mark.parametrize(\n        (\"verbosity\", \"command\"),\n        [\n            (-9000, \"command -qqq\"),\n            (-3, \"command -qqq\"),\n            (-2, \"command -qq\"),\n            (-1, \"command -q\"),\n            (0, \"command\"),\n            (1, \"command -v\"),\n            (2, \"command -vv\"),\n            (3, \"command -vvv\"),\n            (9000, \"command -vvv\"),\n        ],\n    )\n    def test_verbosity_flag_default(\n        self, isolation, isolated_data_dir, platform, global_application, verbosity, command\n    ):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"command {verbosity:flag}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            verbosity,\n            global_application,\n        )\n\n        assert list(environment.expand_command(\"foo\")) == [command]\n\n    @pytest.mark.parametrize(\n        (\"adjustment\", \"command\"),\n        [\n            (-9000, \"command -qqq\"),\n            (-3, \"command -qqq\"),\n            (-2, \"command -qq\"),\n            (-1, \"command -q\"),\n            (0, \"command\"),\n            (1, \"command -v\"),\n            (2, \"command -vv\"),\n            (3, \"command -vvv\"),\n            (9000, \"command -vvv\"),\n        ],\n    )\n    def test_verbosity_flag_adjustment(\n        self, isolation, isolated_data_dir, platform, global_application, adjustment, command\n    ):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": f\"command {{verbosity:flag:{adjustment}}}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert list(environment.expand_command(\"foo\")) == [command]\n\n    def test_verbosity_flag_adjustment_invalid(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Test verbosity flag with invalid adjustment.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n    [project]\n    name = \"my-app\"\n    version = \"0.0.1\"\n\n    [tool.hatch.envs.default]\n    scripts.test = \"pytest {verbosity:flag:invalid}\"\n    \"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Verbosity flag adjustment must be an integer\"):\n            list(environment.expand_command(\"test\"))\n\n    def test_args_undefined(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"command {args}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert list(environment.expand_command(\"foo\")) == [\"command\"]\n\n    def test_args_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"command {args: -bar > /dev/null}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert list(environment.expand_command(\"foo\")) == [\"command  -bar > /dev/null\"]\n\n    def test_args_default_override(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"scripts\": {\"foo\": \"command {args: -bar > /dev/null}\"}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert list(environment.expand_command(\"foo baz\")) == [\"command baz\"]\n\n    def test_matrix_no_selection(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependencies\": [\"pkg=={matrix}\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(ValueError, match=\"The `matrix` context formatting field requires a modifier\"):\n            _ = environment.dependencies\n\n    def test_matrix_no_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependencies\": [\"pkg=={matrix:bar}\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(ValueError, match=\"Nonexistent matrix variable must set a default: bar\"):\n            _ = environment.dependencies\n\n    def test_matrix_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependencies\": [\"pkg=={matrix:bar:9000}\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.dependencies == [\"pkg==9000\"]\n\n    def test_matrix_default_override(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"dependencies\": [\"pkg=={matrix:bar:baz}\"]}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {\"bar\": \"42\"},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.dependencies == [\"pkg==42\"]\n\n    def test_env_vars_override(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"default\": {\n                            \"dependencies\": [\"pkg{env:DEP_PIN}\"],\n                            \"env-vars\": {\"DEP_PIN\": \"==0.0.1\"},\n                            \"overrides\": {\"env\": {\"DEP_ANY\": {\"env-vars\": \"DEP_PIN=\"}}},\n                        },\n                    },\n                },\n            },\n        }\n        with EnvVars({\"DEP_ANY\": \"true\"}):\n            project = Project(isolation, config=config)\n            environment = MockEnvironment(\n                isolation,\n                project.metadata,\n                \"default\",\n                project.config.envs[\"default\"],\n                {},\n                isolated_data_dir,\n                isolated_data_dir,\n                platform,\n                0,\n                global_application,\n            )\n\n            assert environment.dependencies == [\"pkg\"]\n\n\nclass TestWorkspaceConfig:\n    def test_not_table(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": 9000}}}},\n        }\n        project = Project(isolation, config=config)\n        with pytest.raises(TypeError, match=\"Field workspace must be a table\"):\n            MockEnvironment(\n                isolation,\n                project.metadata,\n                \"default\",\n                project.config.envs[\"default\"],  # Exception raised here\n                {},\n                isolated_data_dir,\n                isolated_data_dir,\n                platform,\n                0,\n                global_application,\n            )\n\n    def test_parallel_not_boolean(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"parallel\": 9000}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.default.workspace.parallel` must be a boolean\"):\n            _ = environment.workspace.parallel\n\n    def test_parallel_default(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.workspace.parallel is True\n\n    def test_parallel_override(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"parallel\": False}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.workspace.parallel is False\n\n    def test_members_not_table(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": 9000}}}}},\n        }\n        project = Project(isolation, config=config)\n        with pytest.raises(TypeError, match=\"Field workspace.members must be an array\"):\n            MockEnvironment(\n                isolation,\n                project.metadata,\n                \"default\",\n                project.config.envs[\"default\"],  # Exception raised here\n                {},\n                isolated_data_dir,\n                isolated_data_dir,\n                platform,\n                0,\n                global_application,\n            )\n\n    def test_member_invalid_type(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [9000]}}}}},\n        }\n        project = Project(isolation, config=config)\n        with pytest.raises(TypeError, match=\"Member #1 must be a string or table\"):\n            MockEnvironment(\n                isolation,\n                project.metadata,\n                \"default\",\n                project.config.envs[\"default\"],  # Exception raised here\n                {},\n                isolated_data_dir,\n                isolated_data_dir,\n                platform,\n                0,\n                global_application,\n            )\n\n    def test_member_no_path(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{}]}}}}},\n        }\n        project = Project(isolation, config=config)\n        with pytest.raises(TypeError, match=\"Member #1 must define a `path` key\"):\n            MockEnvironment(\n                isolation,\n                project.metadata,\n                \"default\",\n                project.config.envs[\"default\"],  # Exception raised here\n                {},\n                isolated_data_dir,\n                isolated_data_dir,\n                platform,\n                0,\n                global_application,\n            )\n\n    def test_member_path_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": 9000}]}}}}},\n        }\n        project = Project(isolation, config=config)\n        with pytest.raises(TypeError, match=\"Member #1 path must be a string\"):\n            MockEnvironment(\n                isolation,\n                project.metadata,\n                \"default\",\n                project.config.envs[\"default\"],  # Exception raised here\n                {},\n                isolated_data_dir,\n                isolated_data_dir,\n                platform,\n                0,\n                global_application,\n            )\n\n    def test_member_path_empty_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"\"}]}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Option `path` of member #1 of field `tool.hatch.envs.default.workspace.members` \"\n                \"cannot be an empty string\"\n            ),\n        ):\n            _ = environment.workspace.members\n\n    def test_member_features_not_array(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"foo\", \"features\": 9000}]}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError,\n            match=(\n                \"Option `features` of member #1 of field `tool.hatch.envs.default.workspace.members` \"\n                \"must be an array of strings\"\n            ),\n        ):\n            _ = environment.workspace.members\n\n    def test_member_feature_not_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"foo\", \"features\": [9000]}]}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            TypeError,\n            match=(\n                \"Feature #1 of option `features` of member #1 of field `tool.hatch.envs.default.workspace.members` \"\n                \"must be a string\"\n            ),\n        ):\n            _ = environment.workspace.members\n\n    def test_member_feature_empty_string(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"foo\", \"features\": [\"\"]}]}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Feature #1 of option `features` of member #1 of field `tool.hatch.envs.default.workspace.members` \"\n                \"cannot be an empty string\"\n            ),\n        ):\n            _ = environment.workspace.members\n\n    def test_member_feature_duplicate(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"foo\", \"features\": [\"foo\", \"Foo\"]}]}}}\n                }\n            },\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Feature #2 of option `features` of member #1 of field `tool.hatch.envs.default.workspace.members` \"\n                \"is a duplicate\"\n            ),\n        ):\n            _ = environment.workspace.members\n\n    def test_member_does_not_exist(self, isolation, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"foo\"}]}}}}},\n        }\n        project = Project(isolation, config=config)\n        environment = MockEnvironment(\n            isolation,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        with pytest.raises(\n            OSError,\n            match=re.escape(\n                f\"No members could be derived from `foo` of field `tool.hatch.envs.default.workspace.members`: \"\n                f\"{isolation / 'foo'}\"\n            ),\n        ):\n            _ = environment.workspace.members\n\n    def test_member_not_project(self, temp_dir, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"foo\"}]}}}}},\n        }\n        project = Project(temp_dir, config=config)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        member_path = temp_dir / \"foo\"\n        member_path.mkdir()\n\n        with pytest.raises(\n            OSError,\n            match=re.escape(\n                f\"Member derived from `foo` of field `tool.hatch.envs.default.workspace.members` is not a project \"\n                f\"(no `pyproject.toml` file): {member_path}\"\n            ),\n        ):\n            _ = environment.workspace.members\n\n    def test_member_duplicate(self, temp_dir, isolated_data_dir, platform, global_application):\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"foo\"}, {\"path\": \"f*\"}]}}}}},\n        }\n        project = Project(temp_dir, config=config)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        member_path = temp_dir / \"foo\"\n        member_path.mkdir()\n        (member_path / \"pyproject.toml\").touch()\n\n        with pytest.raises(\n            ValueError,\n            match=re.escape(\n                f\"Member derived from `f*` of field \"\n                f\"`tool.hatch.envs.default.workspace.members` is a duplicate: {member_path}\"\n            ),\n        ):\n            _ = environment.workspace.members\n\n    def test_correct(self, hatch, temp_dir, isolated_data_dir, platform, global_application):\n        member1_path = temp_dir / \"foo\"\n        member2_path = temp_dir / \"bar\"\n        member3_path = temp_dir / \"baz\"\n        for member_path in [member1_path, member2_path, member3_path]:\n            with temp_dir.as_cwd():\n                result = hatch(\"new\", member_path.name)\n                assert result.exit_code == 0, result.output\n\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"foo\"}, {\"path\": \"b*\"}]}}}}},\n        }\n        project = Project(temp_dir, config=config)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        members = environment.workspace.members\n        assert len(members) == 3\n        assert members[0].project.location == member1_path\n        assert members[1].project.location == member2_path\n        assert members[2].project.location == member3_path\n\n    def test_member_outside_root_with_shared_prefix(self, temp_dir, isolated_data_dir, platform, global_application):\n        \"\"\"Verify correct workspace member discovery with shared path prefix.\n\n        os.path.commonprefix works character-by-character, so for paths that\n        share a partial directory name (e.g. 'local_app' and 'lib_member' both\n        start with 'l'), it would return an invalid path like '.../l' instead\n        of the true common ancestor directory. os.path.commonpath correctly\n        returns the nearest common directory, which is what we need as the\n        base for the member glob search.\n\n        Example of the mismatch:\n            os.path.commonprefix(['/usr/lib', '/usr/local/lib']) == '/usr/l'\n            os.path.commonpath(['/usr/lib', '/usr/local/lib'])   == '/usr'\n        \"\"\"\n        project_root = temp_dir / \"local_app\"\n        project_root.mkdir()\n\n        member_path = temp_dir / \"lib_member\"\n        member_path.mkdir()\n        (member_path / \"pyproject.toml\").write_text(\n            \"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"lib-member\"\nversion = \"0.1.0\"\n\"\"\"\n        )\n\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"../lib_member\"}]}}}}},\n        }\n        project = Project(project_root, config=config)\n        environment = MockEnvironment(\n            project_root,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        members = environment.workspace.members\n        assert len(members) == 1\n        assert members[0].project.location == member_path\n\n\nclass TestWorkspaceDependencies:\n    def test_basic(self, temp_dir, isolated_data_dir, platform, global_application):\n        for i in range(3):\n            project_file = temp_dir / f\"foo{i}\" / \"pyproject.toml\"\n            project_file.parent.mkdir()\n            project_file.write_text(\n                f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"foo{i}\"\nversion = \"0.0.1\"\ndependencies = [\"pkg-{i}\"]\n\"\"\"\n            )\n\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"workspace\": {\"members\": [{\"path\": \"f*\"}]}}}}},\n        }\n        project = Project(temp_dir, config=config)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.workspace.get_dependencies() == [\"pkg-0\", \"pkg-1\", \"pkg-2\"]\n\n    def test_features(self, temp_dir, isolated_data_dir, platform, global_application):\n        for i in range(3):\n            project_file = temp_dir / f\"foo{i}\" / \"pyproject.toml\"\n            project_file.parent.mkdir()\n            project_file.write_text(\n                f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"foo{i}\"\nversion = \"0.0.1\"\ndependencies = [\"pkg-{i}\"]\n\n[project.optional-dependencies]\nfeature1 = [\"pkg-feature-1{i}\"]\nfeature2 = [\"pkg-feature-2{i}\"]\nfeature3 = [\"pkg-feature-3{i}\"]\n\"\"\"\n            )\n\n        config = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\n                \"hatch\": {\n                    \"envs\": {\n                        \"default\": {\n                            \"workspace\": {\n                                \"members\": [\n                                    {\"path\": \"foo0\", \"features\": [\"feature1\"]},\n                                    {\"path\": \"foo1\", \"features\": [\"feature1\", \"feature2\"]},\n                                    {\"path\": \"foo2\", \"features\": [\"feature1\", \"feature2\", \"feature3\"]},\n                                ],\n                            },\n                        },\n                    },\n                },\n            },\n        }\n        project = Project(temp_dir, config=config)\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            global_application,\n        )\n\n        assert environment.workspace.get_dependencies() == [\n            \"pkg-0\",\n            \"pkg-feature-10\",\n            \"pkg-1\",\n            \"pkg-feature-11\",\n            \"pkg-feature-21\",\n            \"pkg-2\",\n            \"pkg-feature-12\",\n            \"pkg-feature-22\",\n            \"pkg-feature-32\",\n        ]\n\n\nclass TestDependencyHash:\n    def test_hash_includes_local_dependencies(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify dependency hash includes local dependencies.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\n\"\"\")\n\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(temp_dir, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        hash_value = environment.dependency_hash()\n        assert hash_value\n        assert len(hash_value) > 0\n\n    def test_hash_stable_when_dependencies_unchanged(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify dependency hash is stable when dependencies don't change.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\n\"\"\")\n\n        config = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project = Project(temp_dir, config=config)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        hash1 = environment.dependency_hash()\n        hash2 = environment.dependency_hash()\n\n        assert hash1 == hash2\n\n    def test_hash_changes_with_extra_dependencies(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify dependency hash changes when extra-dependencies are added.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\n\"\"\")\n\n        config_no_deps = {\"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"}}\n        project_no_deps = Project(temp_dir, config=config_no_deps)\n        project_no_deps.set_app(temp_application)\n        temp_application.project = project_no_deps\n        env_no_deps = MockEnvironment(\n            temp_dir,\n            project_no_deps.metadata,\n            \"default\",\n            project_no_deps.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n        hash_no_deps = env_no_deps.dependency_hash()\n\n        config_with_deps = {\n            \"project\": {\"name\": \"my_app\", \"version\": \"0.0.1\"},\n            \"tool\": {\"hatch\": {\"envs\": {\"default\": {\"extra-dependencies\": [\"pytest\"]}}}},\n        }\n        project_with_deps = Project(temp_dir, config=config_with_deps)\n        project_with_deps.set_app(temp_application)\n        temp_application.project = project_with_deps\n        env_with_deps = MockEnvironment(\n            temp_dir,\n            project_with_deps.metadata,\n            \"default\",\n            project_with_deps.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n        hash_with_deps = env_with_deps.dependency_hash()\n\n        assert hash_no_deps != hash_with_deps\n\n\nclass TestLocalDependenciesComplex:\n    def test_dev_mode_true_returns_editable(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify dev-mode=true creates editable local dependency.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\n\"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        local_deps = environment.local_dependencies_complex\n        assert len(local_deps) == 1\n        assert local_deps[0].editable is True\n\n    def test_dev_mode_false_returns_non_editable(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify dev-mode=false creates non-editable local dependency.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\n\n[tool.hatch.envs.default]\ndev-mode = false\n\"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        local_deps = environment.local_dependencies_complex\n        assert len(local_deps) == 1\n        assert local_deps[0].editable is False\n\n    def test_workspace_members_always_editable(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify workspace members are always editable regardless of dev-mode.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\n\n[tool.hatch.envs.default]\ndev-mode = false\nworkspace.members = [\"member\"]\n\"\"\")\n\n        member_dir = temp_dir / \"member\"\n        member_dir.mkdir()\n        (member_dir / \"pyproject.toml\").write_text(\"\"\"\n[project]\nname = \"member\"\nversion = \"0.0.1\"\n\"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        local_deps = environment.local_dependencies_complex\n        assert len(local_deps) == 2\n        project_dep = next(d for d in local_deps if d.name == \"my-app\")\n        member_dep = next(d for d in local_deps if d.name == \"member\")\n        assert project_dep.editable is False\n        assert member_dep.editable is True\n\n\nclass TestDynamicDependencies:\n    def test_dynamic_dependencies_resolved(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify dynamic dependencies are resolved correctly.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\ndynamic = [\"dependencies\"]\n\n[tool.hatch.metadata]\nallow-direct-references = true\n\"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        assert \"dependencies\" in environment.metadata.dynamic\n\n\nclass TestBuildSystemIntegration:\n    def test_builder_includes_build_requirements(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify builder environment includes build system requirements.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[build-system]\nrequires = [\"hatchling\", \"build-dep\"]\n\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\n\n[tool.hatch.envs.build]\nbuilder = true\n\"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"build\",\n            project.config.envs[\"build\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        deps = environment.dependencies\n        assert any(\"hatchling\" in d for d in deps)\n        assert any(\"build-dep\" in d for d in deps)\n\n\nclass TestEnvironmentLifecycle:\n    def test_app_status_contexts(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Verify environment lifecycle status contexts work correctly.\"\"\"\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\n\"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        with environment.app_status_creation():\n            pass\n        with environment.app_status_pre_installation():\n            pass\n        with environment.app_status_post_installation():\n            pass\n        with environment.app_status_project_installation():\n            pass\n        with environment.app_status_dependency_state_check():\n            pass\n        with environment.app_status_dependency_installation_check():\n            pass\n        with environment.app_status_dependency_synchronization():\n            pass\n\n\nclass TestFileSystemContext:\n    def test_join_creates_new_context(self, temp_dir, isolated_data_dir, platform, temp_application):\n        \"\"\"Test FileSystemContext.join creates proper paths.\"\"\"\n        from hatch.env.plugin.interface import FileSystemContext\n\n        pyproject = temp_dir / \"pyproject.toml\"\n        pyproject.write_text(\"\"\"\n[project]\nname = \"my-app\"\nversion = \"0.0.1\"\n\"\"\")\n\n        project = Project(temp_dir)\n        project.set_app(temp_application)\n        temp_application.project = project\n        environment = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            isolated_data_dir,\n            isolated_data_dir,\n            platform,\n            0,\n            temp_application,\n        )\n\n        ctx = FileSystemContext(environment, local_path=temp_dir, env_path=\"/env\")\n        new_ctx = ctx.join(\"subdir\")\n        assert \"subdir\" in str(new_ctx.local_path)\n        assert \"subdir\" in new_ctx.env_path\n"
  },
  {
    "path": "tests/helpers/__init__.py",
    "content": ""
  },
  {
    "path": "tests/helpers/helpers.py",
    "content": "from __future__ import annotations\n\nimport importlib\nimport json\nimport os\nimport re\nimport sys\nfrom datetime import datetime, timezone\nfrom functools import lru_cache\nfrom textwrap import dedent as _dedent\nfrom typing import TYPE_CHECKING\nfrom unittest.mock import call\n\nimport tomli_w\n\nfrom hatch.config.user import RootConfig\nfrom hatch.env.utils import add_verbosity_flag\nfrom hatch.python.core import InstalledDistribution\nfrom hatch.python.resolve import get_distribution\nfrom hatch.utils.toml import load_toml_file\n\nif TYPE_CHECKING:\n    from hatch.utils.fs import Path\n\n\ndef dedent(text):\n    return _dedent(text[1:])\n\n\n@lru_cache\ndef tarfile_extraction_compat_options():\n    return {\"filter\": \"data\"} if sys.version_info >= (3, 12) else {}\n\n\ndef remove_trailing_spaces(text):\n    return \"\".join(f\"{line.rstrip()}\\n\" for line in text.splitlines(True))\n\n\ndef extract_requirements(lines):\n    for raw_line in lines:\n        line = raw_line.rstrip()\n        if line and not line.startswith(\"#\"):\n            yield line\n\n\ndef get_current_timestamp():\n    return datetime.now(timezone.utc).timestamp()\n\n\ndef assert_plugin_installation(subprocess_run, dependencies: list[str], *, verbosity=0, count=1):\n    command = [\n        sys.executable,\n        \"-u\",\n        \"-m\",\n        \"pip\",\n        \"install\",\n        \"--disable-pip-version-check\",\n    ]\n    add_verbosity_flag(command, verbosity, adjustment=-1)\n    command.extend(dependencies)\n\n    assert subprocess_run.call_args_list == [call(command, shell=False)] * count\n\n\ndef assert_files(directory, expected_files, *, check_contents=True):\n    start = str(directory)\n    expected_relative_files = {str(f.path): f.contents for f in expected_files}\n    seen_relative_file_paths = set()\n\n    for root, _, files in os.walk(directory):\n        relative_path = os.path.relpath(root, start)\n\n        # First iteration\n        if relative_path == \".\":\n            relative_path = \"\"\n\n        for file_name in files:\n            relative_file_path = os.path.join(relative_path, file_name)\n            seen_relative_file_paths.add(relative_file_path)\n\n            if check_contents and relative_file_path in expected_relative_files:\n                file_path = os.path.join(start, relative_file_path)\n                expected_contents = expected_relative_files[relative_file_path]\n                try:\n                    with open(file_path, encoding=\"utf-8\") as f:\n                        assert f.read() == expected_contents, relative_file_path\n                except UnicodeDecodeError:\n                    with open(file_path, \"rb\") as f:\n                        assert f.read() == expected_contents, (relative_file_path, expected_contents)\n            else:  # no cov\n                pass\n\n    expected_relative_file_paths = set(expected_relative_files)\n\n    missing_files = expected_relative_file_paths - seen_relative_file_paths\n    assert not missing_files, f\"Missing files: {', '.join(sorted(missing_files))}\"\n\n    extra_files = seen_relative_file_paths - expected_relative_file_paths\n    assert not extra_files, f\"Extra files: {', '.join(sorted(extra_files))}\"\n\n\ndef assert_output_match(output: str, pattern: str, *, exact: bool = True):\n    flags = re.MULTILINE if exact else re.MULTILINE | re.DOTALL\n    assert re.search(dedent(pattern), output, flags=flags) is not None, output\n\n\ndef get_template_files(template_name, project_name, **kwargs):\n    kwargs[\"project_name\"] = project_name\n    kwargs[\"project_name_normalized\"] = project_name.lower().replace(\".\", \"-\")\n    kwargs[\"package_name\"] = kwargs[\"project_name_normalized\"].replace(\"-\", \"_\")\n\n    config = RootConfig({})\n    kwargs.setdefault(\"author\", config.template.name)\n    kwargs.setdefault(\"email\", config.template.email)\n    kwargs.setdefault(\"year\", str(datetime.now(timezone.utc).year))\n\n    return __load_template_module(template_name)(**kwargs)\n\n\n@lru_cache\ndef __load_template_module(template_name):\n    template = importlib.import_module(f\"..templates.{template_name}\", __name__)\n    return template.get_files\n\n\ndef update_project_environment(project, name, config):\n    project_file = project.root / \"pyproject.toml\"\n    raw_config = load_toml_file(str(project_file))\n\n    env_config = raw_config.setdefault(\"tool\", {}).setdefault(\"hatch\", {}).setdefault(\"envs\", {}).setdefault(name, {})\n    env_config.update(config)\n\n    project.config.envs[name] = project.config.envs.get(name, project.config.envs[\"default\"]).copy()\n    project.config.envs[name].update(env_config)\n\n    with open(str(project_file), \"w\", encoding=\"utf-8\") as f:\n        f.write(tomli_w.dumps(raw_config))\n\n\ndef write_distribution(directory: Path, name: str):\n    dist = get_distribution(name)\n    path = directory / dist.name\n    path.ensure_dir_exists()\n    python_path = path / dist.python_path\n    python_path.parent.ensure_dir_exists()\n    python_path.touch()\n\n    metadata = {\"source\": dist.source, \"python_path\": dist.python_path}\n    metadata_file = path / InstalledDistribution.metadata_filename()\n    metadata_file.write_text(json.dumps(metadata))\n\n    return InstalledDistribution(path, dist, metadata)\n\n\ndef downgrade_distribution_metadata(dist_dir: Path):\n    metadata_file = dist_dir / InstalledDistribution.metadata_filename()\n    metadata = json.loads(metadata_file.read_text())\n    dist = InstalledDistribution(dist_dir, get_distribution(dist_dir.name), metadata)\n\n    source = metadata[\"source\"]\n    python_path = metadata[\"python_path\"]\n    version = dist.version\n    new_version = downgrade_version(version)\n    new_source = source.replace(version, new_version)\n    metadata[\"source\"] = new_source\n\n    # We also modify the Python path because some directory structures are determined\n    # by the archive name which is itself determined by the source\n    metadata[\"python_path\"] = python_path.replace(version, new_version)\n    if python_path != metadata[\"python_path\"]:\n        new_python_path = dist_dir / metadata[\"python_path\"]\n        new_python_path.parent.ensure_dir_exists()\n        (dist_dir / python_path).rename(new_python_path)\n\n    metadata_file.write_text(json.dumps(metadata))\n    return metadata\n\n\ndef downgrade_version(version: str) -> str:\n    major_version = version.split(\".\")[0]\n    return version.replace(major_version, str(int(major_version) - 1), 1)\n"
  },
  {
    "path": "tests/helpers/templates/__init__.py",
    "content": ""
  },
  {
    "path": "tests/helpers/templates/licenses/__init__.py",
    "content": "Apache_2_0 = \"\"\"\\\nApache License\n\nVersion 2.0, January 2004\n\nhttp://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION,\nAND DISTRIBUTION\n\n   1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and distribution\nas defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct\nor indirect, to cause the direction or management of such entity, whether\nby contract or otherwise, or (ii) ownership of fifty percent (50%) or more\nof the outstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions\ngranted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation\nor translation of a Source form, including but not limited to compiled object\ncode, generated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form,\nmade available under the License, as indicated by a copyright notice that\nis included in or attached to the work (an example is provided in the Appendix\nbelow).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form,\nthat is based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative\nWorks shall not include works that remain separable from, or merely link (or\nbind by name) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative\nWorks thereof, that is intentionally submitted to Licensor for inclusion in\nthe Work by the copyright owner or by an individual or Legal Entity authorized\nto submit on behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication\nsent to the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor\nfor the purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently incorporated\nwithin the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of this\nLicense, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,\nno-charge, royalty-free, irrevocable copyright license to reproduce, prepare\nDerivative Works of, publicly display, publicly perform, sublicense, and distribute\nthe Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of this License,\neach Contributor hereby grants to You a perpetual, worldwide, non-exclusive,\nno-charge, royalty-free, irrevocable (except as stated in this section) patent\nlicense to make, have made, use, offer to sell, sell, import, and otherwise\ntransfer the Work, where such license applies only to those patent claims\nlicensable by such Contributor that are necessarily infringed by their Contribution(s)\nalone or by combination of their Contribution(s) with the Work to which such\nContribution(s) was submitted. If You institute patent litigation against\nany entity (including a cross-claim or counterclaim in a lawsuit) alleging\nthat the Work or a Contribution incorporated within the Work constitutes direct\nor contributory patent infringement, then any patent licenses granted to You\nunder this License for that Work shall terminate as of the date such litigation\nis filed.\n\n4. Redistribution. You may reproduce and distribute copies of the Work or\nDerivative Works thereof in any medium, with or without modifications, and\nin Source or Object form, provided that You meet the following conditions:\n\n(a) You must give any other recipients of the Work or Derivative Works a copy\nof this License; and\n\n(b) You must cause any modified files to carry prominent notices stating that\nYou changed the files; and\n\n(c) You must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source\nform of the Work, excluding those notices that do not pertain to any part\nof the Derivative Works; and\n\n(d) If the Work includes a \"NOTICE\" text file as part of its distribution,\nthen any Derivative Works that You distribute must include a readable copy\nof the attribution notices contained within such NOTICE file, excluding those\nnotices that do not pertain to any part of the Derivative Works, in at least\none of the following places: within a NOTICE text file distributed as part\nof the Derivative Works; within the Source form or documentation, if provided\nalong with the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents\nof the NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works\nthat You distribute, alongside or as an addendum to the NOTICE text from the\nWork, provided that such additional attribution notices cannot be construed\nas modifying the License.\n\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction,\nor distribution of Your modifications, or for any such Derivative Works as\na whole, provided Your use, reproduction, and distribution of the Work otherwise\ncomplies with the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise, any\nContribution intentionally submitted for inclusion in the Work by You to the\nLicensor shall be under the terms and conditions of this License, without\nany additional terms or conditions. Notwithstanding the above, nothing herein\nshall supersede or modify the terms of any separate license agreement you\nmay have executed with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade names,\ntrademarks, service marks, or product names of the Licensor, except as required\nfor reasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or agreed to\nin writing, Licensor provides the Work (and each Contributor provides its\nContributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, either express or implied, including, without limitation, any warranties\nor conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR\nA PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness\nof using or redistributing the Work and assume any risks associated with Your\nexercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory, whether\nin tort (including negligence), contract, or otherwise, unless required by\napplicable law (such as deliberate and grossly negligent acts) or agreed to\nin writing, shall any Contributor be liable to You for damages, including\nany direct, indirect, special, incidental, or consequential damages of any\ncharacter arising as a result of this License or out of the use or inability\nto use the Work (including but not limited to damages for loss of goodwill,\nwork stoppage, computer failure or malfunction, or any and all other commercial\ndamages or losses), even if such Contributor has been advised of the possibility\nof such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing the Work\nor Derivative Works thereof, You may choose to offer, and charge a fee for,\nacceptance of support, warranty, indemnity, or other liability obligations\nand/or rights consistent with this License. However, in accepting such obligations,\nYou may act only on Your own behalf and on Your sole responsibility, not on\nbehalf of any other Contributor, and only if You agree to indemnify, defend,\nand hold each Contributor harmless for any liability incurred by, or claims\nasserted against, such Contributor by reason of your accepting any such warranty\nor additional liability. END OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own identifying\ninformation. (Don't include the brackets!) The text should be enclosed in\nthe appropriate comment syntax for the file format. We also recommend that\na file or class name and description of purpose be included on the same \"printed\npage\" as the copyright notice for easier identification within third-party\narchives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\n\nyou may not use this file except in compliance with the License.\n\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\n\ndistributed under the License is distributed on an \"AS IS\" BASIS,\n\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\nSee the License for the specific language governing permissions and\n\nlimitations under the License.\n\"\"\"\n\nMIT = \"\"\"\\\nMIT License\n\nCopyright (c) <year> <copyright holders>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and\nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the\nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial\nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT\nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO\nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\n"
  },
  {
    "path": "tests/helpers/templates/new/__init__.py",
    "content": ""
  },
  {
    "path": "tests/helpers/templates/new/basic.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\nfrom ..licenses import MIT\n\n\ndef get_files(**kwargs):\n    return [\n        File(\n            Path(\"LICENSE.txt\"),\n            MIT.replace(\"<year>\", f\"{kwargs['year']}-present\", 1).replace(\n                \"<copyright holders>\", f\"{kwargs['author']} <{kwargs['email']}>\", 1\n            ),\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__about__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n__version__ = \"0.0.1\"\n\"\"\",\n        ),\n        File(\n            Path(\"README.md\"),\n            f\"\"\"\\\n# {kwargs[\"project_name\"]}\n\n[![PyPI - Version](https://img.shields.io/pypi/v/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n- [License](#license)\n\n## Installation\n\n```console\npip install {kwargs[\"project_name_normalized\"]}\n```\n\n## License\n\n`{kwargs[\"project_name_normalized\"]}` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n\"\"\",\n        ),\n        File(\n            Path(\"pyproject.toml\"),\n            f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"{kwargs[\"project_name_normalized\"]}\"\ndynamic = [\"version\"]\ndescription = ''\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"MIT\"\nkeywords = []\nauthors = [\n  {{ name = \"{kwargs[\"author\"]}\", email = \"{kwargs[\"email\"]}\" }},\n]\nclassifiers = [\n  \"Development Status :: 4 - Beta\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = []\n\n[project.urls]\nDocumentation = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}#readme\"\nIssues = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}/issues\"\nSource = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}\"\n\n[tool.hatch.version]\npath = \"src/{kwargs[\"package_name\"]}/__about__.py\"\n\"\"\",\n        ),\n    ]\n"
  },
  {
    "path": "tests/helpers/templates/new/default.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\nfrom ..licenses import MIT\n\n\ndef get_files(**kwargs):\n    description = kwargs.get(\"description\", \"\")\n\n    return [\n        File(\n            Path(\"LICENSE.txt\"),\n            MIT.replace(\"<year>\", f\"{kwargs['year']}-present\", 1).replace(\n                \"<copyright holders>\", f\"{kwargs['author']} <{kwargs['email']}>\", 1\n            ),\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__about__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n__version__ = \"0.0.1\"\n\"\"\",\n        ),\n        File(\n            Path(\"tests\", \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"README.md\"),\n            f\"\"\"\\\n# {kwargs[\"project_name\"]}\n\n[![PyPI - Version](https://img.shields.io/pypi/v/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n- [License](#license)\n\n## Installation\n\n```console\npip install {kwargs[\"project_name_normalized\"]}\n```\n\n## License\n\n`{kwargs[\"project_name_normalized\"]}` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n\"\"\",\n        ),\n        File(\n            Path(\"pyproject.toml\"),\n            f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"{kwargs[\"project_name_normalized\"]}\"\ndynamic = [\"version\"]\ndescription = '{description}'\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"MIT\"\nkeywords = []\nauthors = [\n  {{ name = \"{kwargs[\"author\"]}\", email = \"{kwargs[\"email\"]}\" }},\n]\nclassifiers = [\n  \"Development Status :: 4 - Beta\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = []\n\n[project.urls]\nDocumentation = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}#readme\"\nIssues = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}/issues\"\nSource = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}\"\n\n[tool.hatch.version]\npath = \"src/{kwargs[\"package_name\"]}/__about__.py\"\n\n[tool.hatch.envs.types]\nextra-dependencies = [\n  \"mypy>=1.0.0\",\n]\n[tool.hatch.envs.types.scripts]\ncheck = \"mypy --install-types --non-interactive {{args:src/{kwargs[\"package_name\"]} tests}}\"\n\n[tool.coverage.run]\nsource_pkgs = [\"{kwargs[\"package_name\"]}\", \"tests\"]\nbranch = true\nparallel = true\nomit = [\n  \"src/{kwargs[\"package_name\"]}/__about__.py\",\n]\n\n[tool.coverage.paths]\n{kwargs[\"package_name\"]} = [\"src/{kwargs[\"package_name\"]}\", \"*/{kwargs[\"project_name_normalized\"]}/src/{kwargs[\"package_name\"]}\"]\ntests = [\"tests\", \"*/{kwargs[\"project_name_normalized\"]}/tests\"]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"no cov\",\n  \"if __name__ == .__main__.:\",\n  \"if TYPE_CHECKING:\",\n]\n\"\"\",\n        ),\n    ]\n"
  },
  {
    "path": "tests/helpers/templates/new/feature_ci.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\nfrom .default import get_files as get_template_files\n\n\ndef get_files(**kwargs):\n    files = [File(Path(f.path), f.contents) for f in get_template_files(**kwargs)]\n    files.append(\n        File(\n            Path(\".github\", \"workflows\", \"test.yml\"),\n            \"\"\"\\\nname: test\n\non:\n  push:\n    branches: [main, master]\n  pull_request:\n    branches: [main, master]\n\nconcurrency:\n  group: test-${{ github.head_ref }}\n  cancel-in-progress: true\n\nenv:\n  PYTHONUNBUFFERED: \"1\"\n  FORCE_COLOR: \"1\"\n\njobs:\n  run:\n    name: Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v4\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install Hatch\n      run: pip install --upgrade hatch\n\n    - name: Run static analysis\n      run: hatch fmt --check\n\n    - name: Run tests\n      run: hatch test --python ${{ matrix.python-version }} --cover --randomize --parallel --retries 2 --retry-delay 1\n\"\"\",\n        )\n    )\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/new/feature_cli.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\nfrom ..licenses import MIT\n\n\ndef get_files(**kwargs):\n    return [\n        File(\n            Path(\"LICENSE.txt\"),\n            MIT.replace(\"<year>\", f\"{kwargs['year']}-present\", 1).replace(\n                \"<copyright holders>\", f\"{kwargs['author']} <{kwargs['email']}>\", 1\n            ),\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__about__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n__version__ = \"0.0.1\"\n\"\"\",\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__main__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\nimport sys\n\nif __name__ == \"__main__\":\n    from {kwargs[\"package_name\"]}.cli import {kwargs[\"package_name\"]}\n\n    sys.exit({kwargs[\"package_name\"]}())\n\"\"\",\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"cli\", \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\nimport click\n\nfrom {kwargs[\"package_name\"]}.__about__ import __version__\n\n\n@click.group(context_settings={{\"help_option_names\": [\"-h\", \"--help\"]}}, invoke_without_command=True)\n@click.version_option(version=__version__, prog_name=\"{kwargs[\"project_name\"]}\")\ndef {kwargs[\"package_name\"]}():\n    click.echo(\"Hello world!\")\n\"\"\",\n        ),\n        File(\n            Path(\"tests\", \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"README.md\"),\n            f\"\"\"\\\n# {kwargs[\"project_name\"]}\n\n[![PyPI - Version](https://img.shields.io/pypi/v/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n- [License](#license)\n\n## Installation\n\n```console\npip install {kwargs[\"project_name_normalized\"]}\n```\n\n## License\n\n`{kwargs[\"project_name_normalized\"]}` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n\"\"\",\n        ),\n        File(\n            Path(\"pyproject.toml\"),\n            f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"{kwargs[\"project_name_normalized\"]}\"\ndynamic = [\"version\"]\ndescription = ''\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"MIT\"\nkeywords = []\nauthors = [\n  {{ name = \"{kwargs[\"author\"]}\", email = \"{kwargs[\"email\"]}\" }},\n]\nclassifiers = [\n  \"Development Status :: 4 - Beta\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = [\n  \"click\",\n]\n\n[project.urls]\nDocumentation = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}#readme\"\nIssues = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}/issues\"\nSource = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}\"\n\n[project.scripts]\n{kwargs[\"project_name_normalized\"]} = \"{kwargs[\"package_name\"]}.cli:{kwargs[\"package_name\"]}\"\n\n[tool.hatch.version]\npath = \"src/{kwargs[\"package_name\"]}/__about__.py\"\n\n[tool.hatch.envs.types]\nextra-dependencies = [\n  \"mypy>=1.0.0\",\n]\n[tool.hatch.envs.types.scripts]\ncheck = \"mypy --install-types --non-interactive {{args:src/{kwargs[\"package_name\"]} tests}}\"\n\n[tool.coverage.run]\nsource_pkgs = [\"{kwargs[\"package_name\"]}\", \"tests\"]\nbranch = true\nparallel = true\nomit = [\n  \"src/{kwargs[\"package_name\"]}/__about__.py\",\n]\n\n[tool.coverage.paths]\n{kwargs[\"package_name\"]} = [\"src/{kwargs[\"package_name\"]}\", \"*/{kwargs[\"project_name_normalized\"]}/src/{kwargs[\"package_name\"]}\"]\ntests = [\"tests\", \"*/{kwargs[\"project_name_normalized\"]}/tests\"]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"no cov\",\n  \"if __name__ == .__main__.:\",\n  \"if TYPE_CHECKING:\",\n]\n\"\"\",\n        ),\n    ]\n"
  },
  {
    "path": "tests/helpers/templates/new/feature_no_src_layout.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\nfrom ..licenses import MIT\n\n\ndef get_files(**kwargs):\n    description = kwargs.get(\"description\", \"\")\n\n    return [\n        File(\n            Path(\"LICENSE.txt\"),\n            MIT.replace(\"<year>\", f\"{kwargs['year']}-present\", 1).replace(\n                \"<copyright holders>\", f\"{kwargs['author']} <{kwargs['email']}>\", 1\n            ),\n        ),\n        File(\n            Path(kwargs[\"package_name\"], \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(kwargs[\"package_name\"], \"__about__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n__version__ = \"0.0.1\"\n\"\"\",\n        ),\n        File(\n            Path(\"tests\", \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"README.md\"),\n            f\"\"\"\\\n# {kwargs[\"project_name\"]}\n\n[![PyPI - Version](https://img.shields.io/pypi/v/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n- [License](#license)\n\n## Installation\n\n```console\npip install {kwargs[\"project_name_normalized\"]}\n```\n\n## License\n\n`{kwargs[\"project_name_normalized\"]}` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n\"\"\",\n        ),\n        File(\n            Path(\"pyproject.toml\"),\n            f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"{kwargs[\"project_name_normalized\"]}\"\ndynamic = [\"version\"]\ndescription = '{description}'\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"MIT\"\nkeywords = []\nauthors = [\n  {{ name = \"{kwargs[\"author\"]}\", email = \"{kwargs[\"email\"]}\" }},\n]\nclassifiers = [\n  \"Development Status :: 4 - Beta\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = []\n\n[project.urls]\nDocumentation = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}#readme\"\nIssues = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}/issues\"\nSource = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}\"\n\n[tool.hatch.version]\npath = \"{kwargs[\"package_name\"]}/__about__.py\"\n\n[tool.hatch.envs.types]\nextra-dependencies = [\n  \"mypy>=1.0.0\",\n]\n[tool.hatch.envs.types.scripts]\ncheck = \"mypy --install-types --non-interactive {{args:{kwargs[\"package_name\"]} tests}}\"\n\n[tool.coverage.run]\nsource_pkgs = [\"{kwargs[\"package_name\"]}\", \"tests\"]\nbranch = true\nparallel = true\nomit = [\n  \"{kwargs[\"package_name\"]}/__about__.py\",\n]\n\n[tool.coverage.paths]\n{kwargs[\"package_name\"]} = [\"{kwargs[\"package_name\"]}\", \"*/{kwargs[\"project_name_normalized\"]}/{kwargs[\"package_name\"]}\"]\ntests = [\"tests\", \"*/{kwargs[\"project_name_normalized\"]}/tests\"]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"no cov\",\n  \"if __name__ == .__main__.:\",\n  \"if TYPE_CHECKING:\",\n]\n\"\"\",\n        ),\n    ]\n"
  },
  {
    "path": "tests/helpers/templates/new/licenses_empty.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\n\ndef get_files(**kwargs):\n    return [\n        File(Path(\"src\", kwargs[\"package_name\"], \"__init__.py\")),\n        File(Path(\"src\", kwargs[\"package_name\"], \"__about__.py\"), '__version__ = \"0.0.1\"\\n'),\n        File(Path(\"tests\", \"__init__.py\")),\n        File(\n            Path(\"README.md\"),\n            f\"\"\"\\\n# {kwargs[\"project_name\"]}\n\n[![PyPI - Version](https://img.shields.io/pypi/v/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n\n## Installation\n\n```console\npip install {kwargs[\"project_name_normalized\"]}\n```\n\"\"\",\n        ),\n        File(\n            Path(\"pyproject.toml\"),\n            f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"{kwargs[\"project_name_normalized\"]}\"\ndynamic = [\"version\"]\ndescription = ''\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"\"\nkeywords = []\nauthors = [\n  {{ name = \"{kwargs[\"author\"]}\", email = \"{kwargs[\"email\"]}\" }},\n]\nclassifiers = [\n  \"Development Status :: 4 - Beta\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = []\n\n[project.urls]\nDocumentation = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}#readme\"\nIssues = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}/issues\"\nSource = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}\"\n\n[tool.hatch.version]\npath = \"src/{kwargs[\"package_name\"]}/__about__.py\"\n\n[tool.hatch.envs.types]\nextra-dependencies = [\n  \"mypy>=1.0.0\",\n]\n[tool.hatch.envs.types.scripts]\ncheck = \"mypy --install-types --non-interactive {{args:src/{kwargs[\"package_name\"]} tests}}\"\n\n[tool.coverage.run]\nsource_pkgs = [\"{kwargs[\"package_name\"]}\", \"tests\"]\nbranch = true\nparallel = true\nomit = [\n  \"src/{kwargs[\"package_name\"]}/__about__.py\",\n]\n\n[tool.coverage.paths]\n{kwargs[\"package_name\"]} = [\"src/{kwargs[\"package_name\"]}\", \"*/{kwargs[\"project_name_normalized\"]}/src/{kwargs[\"package_name\"]}\"]\ntests = [\"tests\", \"*/{kwargs[\"project_name_normalized\"]}/tests\"]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"no cov\",\n  \"if __name__ == .__main__.:\",\n  \"if TYPE_CHECKING:\",\n]\n\"\"\",\n        ),\n    ]\n"
  },
  {
    "path": "tests/helpers/templates/new/licenses_multiple.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\nfrom ..licenses import MIT, Apache_2_0\n\n\ndef get_files(**kwargs):\n    return [\n        File(Path(\"LICENSES\", \"Apache-2.0.txt\"), Apache_2_0),\n        File(\n            Path(\"LICENSES\", \"MIT.txt\"),\n            MIT.replace(\"<year>\", f\"{kwargs['year']}-present\", 1).replace(\n                \"<copyright holders>\", f\"{kwargs['author']} <{kwargs['email']}>\", 1\n            ),\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: Apache-2.0 OR MIT\n\"\"\",\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__about__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: Apache-2.0 OR MIT\n__version__ = \"0.0.1\"\n\"\"\",\n        ),\n        File(\n            Path(\"tests\", \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: Apache-2.0 OR MIT\n\"\"\",\n        ),\n        File(\n            Path(\"README.md\"),\n            f\"\"\"\\\n# {kwargs[\"project_name\"]}\n\n[![PyPI - Version](https://img.shields.io/pypi/v/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n- [License](#license)\n\n## Installation\n\n```console\npip install {kwargs[\"project_name_normalized\"]}\n```\n\n## License\n\n`{kwargs[\"project_name_normalized\"]}` is distributed under the terms of any of the following licenses:\n\n- [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)\n- [MIT](https://spdx.org/licenses/MIT.html)\n\"\"\",\n        ),\n        File(\n            Path(\"pyproject.toml\"),\n            f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"{kwargs[\"project_name_normalized\"]}\"\ndynamic = [\"version\"]\ndescription = ''\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"Apache-2.0 OR MIT\"\nlicense-files = {{ globs = [\"LICENSES/*\"] }}\nkeywords = []\nauthors = [\n  {{ name = \"{kwargs[\"author\"]}\", email = \"{kwargs[\"email\"]}\" }},\n]\nclassifiers = [\n  \"Development Status :: 4 - Beta\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = []\n\n[project.urls]\nDocumentation = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}#readme\"\nIssues = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}/issues\"\nSource = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}\"\n\n[tool.hatch.version]\npath = \"src/{kwargs[\"package_name\"]}/__about__.py\"\n\n[tool.hatch.envs.types]\nextra-dependencies = [\n  \"mypy>=1.0.0\",\n]\n[tool.hatch.envs.types.scripts]\ncheck = \"mypy --install-types --non-interactive {{args:src/{kwargs[\"package_name\"]} tests}}\"\n\n[tool.coverage.run]\nsource_pkgs = [\"{kwargs[\"package_name\"]}\", \"tests\"]\nbranch = true\nparallel = true\nomit = [\n  \"src/{kwargs[\"package_name\"]}/__about__.py\",\n]\n\n[tool.coverage.paths]\n{kwargs[\"package_name\"]} = [\"src/{kwargs[\"package_name\"]}\", \"*/{kwargs[\"project_name_normalized\"]}/src/{kwargs[\"package_name\"]}\"]\ntests = [\"tests\", \"*/{kwargs[\"project_name_normalized\"]}/tests\"]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"no cov\",\n  \"if __name__ == .__main__.:\",\n  \"if TYPE_CHECKING:\",\n]\n\"\"\",\n        ),\n    ]\n"
  },
  {
    "path": "tests/helpers/templates/new/projects_urls_empty.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\nfrom ..licenses import MIT\n\n\ndef get_files(**kwargs):\n    return [\n        File(\n            Path(\"LICENSE.txt\"),\n            MIT.replace(\"<year>\", f\"{kwargs['year']}-present\", 1).replace(\n                \"<copyright holders>\", f\"{kwargs['author']} <{kwargs['email']}>\", 1\n            ),\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__about__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n__version__ = \"0.0.1\"\n\"\"\",\n        ),\n        File(\n            Path(\"tests\", \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"README.md\"),\n            f\"\"\"\\\n# {kwargs[\"project_name\"]}\n\n[![PyPI - Version](https://img.shields.io/pypi/v/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n- [License](#license)\n\n## Installation\n\n```console\npip install {kwargs[\"project_name_normalized\"]}\n```\n\n## License\n\n`{kwargs[\"project_name_normalized\"]}` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n\"\"\",\n        ),\n        File(\n            Path(\"pyproject.toml\"),\n            f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"{kwargs[\"project_name_normalized\"]}\"\ndynamic = [\"version\"]\ndescription = ''\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"MIT\"\nkeywords = []\nauthors = [\n  {{ name = \"{kwargs[\"author\"]}\", email = \"{kwargs[\"email\"]}\" }},\n]\nclassifiers = [\n  \"Development Status :: 4 - Beta\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = []\n\n[project.urls]\n\n[tool.hatch.version]\npath = \"src/{kwargs[\"package_name\"]}/__about__.py\"\n\n[tool.hatch.envs.types]\nextra-dependencies = [\n  \"mypy>=1.0.0\",\n]\n[tool.hatch.envs.types.scripts]\ncheck = \"mypy --install-types --non-interactive {{args:src/{kwargs[\"package_name\"]} tests}}\"\n\n[tool.coverage.run]\nsource_pkgs = [\"{kwargs[\"package_name\"]}\", \"tests\"]\nbranch = true\nparallel = true\nomit = [\n  \"src/{kwargs[\"package_name\"]}/__about__.py\",\n]\n\n[tool.coverage.paths]\n{kwargs[\"package_name\"]} = [\"src/{kwargs[\"package_name\"]}\", \"*/{kwargs[\"project_name_normalized\"]}/src/{kwargs[\"package_name\"]}\"]\ntests = [\"tests\", \"*/{kwargs[\"project_name_normalized\"]}/tests\"]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"no cov\",\n  \"if __name__ == .__main__.:\",\n  \"if TYPE_CHECKING:\",\n]\n\"\"\",\n        ),\n    ]\n"
  },
  {
    "path": "tests/helpers/templates/new/projects_urls_space_in_label.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\n\nfrom ..licenses import MIT\n\n\ndef get_files(**kwargs):\n    return [\n        File(\n            Path(\"LICENSE.txt\"),\n            MIT.replace(\"<year>\", f\"{kwargs['year']}-present\", 1).replace(\n                \"<copyright holders>\", f\"{kwargs['author']} <{kwargs['email']}>\", 1\n            ),\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"src\", kwargs[\"package_name\"], \"__about__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n__version__ = \"0.0.1\"\n\"\"\",\n        ),\n        File(\n            Path(\"tests\", \"__init__.py\"),\n            f\"\"\"\\\n# SPDX-FileCopyrightText: {kwargs[\"year\"]}-present {kwargs[\"author\"]} <{kwargs[\"email\"]}>\n#\n# SPDX-License-Identifier: MIT\n\"\"\",\n        ),\n        File(\n            Path(\"README.md\"),\n            f\"\"\"\\\n# {kwargs[\"project_name\"]}\n\n[![PyPI - Version](https://img.shields.io/pypi/v/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{kwargs[\"project_name_normalized\"]}.svg)](https://pypi.org/project/{kwargs[\"project_name_normalized\"]})\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n- [License](#license)\n\n## Installation\n\n```console\npip install {kwargs[\"project_name_normalized\"]}\n```\n\n## License\n\n`{kwargs[\"project_name_normalized\"]}` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n\"\"\",\n        ),\n        File(\n            Path(\"pyproject.toml\"),\n            f\"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"{kwargs[\"project_name_normalized\"]}\"\ndynamic = [\"version\"]\ndescription = ''\nreadme = \"README.md\"\nrequires-python = \">=3.8\"\nlicense = \"MIT\"\nkeywords = []\nauthors = [\n  {{ name = \"{kwargs[\"author\"]}\", email = \"{kwargs[\"email\"]}\" }},\n]\nclassifiers = [\n  \"Development Status :: 4 - Beta\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3.8\",\n  \"Programming Language :: Python :: 3.9\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndependencies = []\n\n[project.urls]\nDocumentation = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}#readme\"\nSource = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}\"\n\"Bug Tracker\" = \"https://github.com/{kwargs[\"author\"]}/{kwargs[\"project_name_normalized\"]}/issues\"\n\n[tool.hatch.version]\npath = \"src/{kwargs[\"package_name\"]}/__about__.py\"\n\n[tool.hatch.envs.types]\nextra-dependencies = [\n  \"mypy>=1.0.0\",\n]\n[tool.hatch.envs.types.scripts]\ncheck = \"mypy --install-types --non-interactive {{args:src/{kwargs[\"package_name\"]} tests}}\"\n\n[tool.coverage.run]\nsource_pkgs = [\"{kwargs[\"package_name\"]}\", \"tests\"]\nbranch = true\nparallel = true\nomit = [\n  \"src/{kwargs[\"package_name\"]}/__about__.py\",\n]\n\n[tool.coverage.paths]\n{kwargs[\"package_name\"]} = [\"src/{kwargs[\"package_name\"]}\", \"*/{kwargs[\"project_name_normalized\"]}/src/{kwargs[\"package_name\"]}\"]\ntests = [\"tests\", \"*/{kwargs[\"project_name_normalized\"]}/tests\"]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"no cov\",\n  \"if __name__ == .__main__.:\",\n  \"if TYPE_CHECKING:\",\n]\n\"\"\",\n        ),\n    ]\n"
  },
  {
    "path": "tests/helpers/templates/sdist/__init__.py",
    "content": ""
  },
  {
    "path": "tests/helpers/templates/sdist/standard_default.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\n\n\ndef get_files(**kwargs):\n    relative_root = kwargs.get(\"relative_root\", \"\")\n\n    files = [File(Path(relative_root, f.path), f.contents) for f in get_template_files(**kwargs)]\n    files.append(\n        File(\n            Path(relative_root, \"PKG-INFO\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        )\n    )\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/sdist/standard_default_build_script_artifacts.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\n\n\ndef get_files(**kwargs):\n    relative_root = kwargs.get(\"relative_root\", \"\")\n\n    files = [File(Path(relative_root, f.path), f.contents) for f in get_template_files(**kwargs)]\n    files.extend((\n        File(Path(relative_root, kwargs[\"package_name\"], \"lib.so\"), \"\"),\n        File(\n            Path(relative_root, \".gitignore\"),\n            \"\"\"\\\n*.pyc\n*.so\n*.h\n\"\"\",\n        ),\n        File(\n            Path(relative_root, DEFAULT_BUILD_SCRIPT),\n            \"\"\"\\\nimport pathlib\n\nfrom hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\nclass CustomHook(BuildHookInterface):\n    def initialize(self, version, build_data):\n        pathlib.Path('my_app', 'lib.so').touch()\n        pathlib.Path('my_app', 'lib.h').touch()\n\"\"\",\n        ),\n        File(\n            Path(relative_root, \"PKG-INFO\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/sdist/standard_default_build_script_extra_dependencies.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\nfrom hatchling.utils.constants import DEFAULT_BUILD_SCRIPT\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\n\n\ndef get_files(**kwargs):\n    relative_root = kwargs.get(\"relative_root\", \"\")\n\n    files = [File(Path(relative_root, f.path), f.contents) for f in get_template_files(**kwargs)]\n    files.extend((\n        File(Path(relative_root, kwargs[\"package_name\"], \"lib.so\"), \"\"),\n        File(\n            Path(relative_root, \".gitignore\"),\n            \"\"\"\\\n*.pyc\n*.so\n*.h\n\"\"\",\n        ),\n        File(\n            Path(relative_root, DEFAULT_BUILD_SCRIPT),\n            \"\"\"\\\nimport pathlib\n\nfrom hatchling.builders.hooks.plugin.interface import BuildHookInterface\n\nclass CustomHook(BuildHookInterface):\n    def initialize(self, version, build_data):\n        pathlib.Path('my_app', 'lib.so').touch()\n        pathlib.Path('my_app', 'lib.h').touch()\n        build_data['dependencies'].append('binary')\n\"\"\",\n        ),\n        File(\n            Path(relative_root, \"PKG-INFO\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Dist: binary\n\"\"\",\n        ),\n    ))\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/sdist/standard_default_support_legacy.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\n\n\ndef get_files(**kwargs):\n    relative_root = kwargs.get(\"relative_root\", \"\")\n\n    files = [File(Path(relative_root, f.path), f.contents) for f in get_template_files(**kwargs)]\n    files.extend((\n        File(\n            Path(relative_root, \"PKG-INFO\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n        File(\n            Path(relative_root, \"setup.py\"),\n            f\"\"\"\\\nfrom setuptools import setup\n\nsetup(\n    name='{kwargs[\"project_name_normalized\"]}',\n    version='0.0.1',\n    packages=[\n        '{kwargs[\"package_name\"]}',\n        'tests',\n    ],\n)\n\"\"\",\n        ),\n    ))\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/sdist/standard_default_vcs_git_exclusion_files.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\n\n\ndef get_files(**kwargs):\n    relative_root = kwargs.get(\"relative_root\", \"\")\n\n    files = [File(Path(relative_root, f.path), f.contents) for f in get_template_files(**kwargs)]\n    files.extend((\n        File(Path(relative_root, kwargs[\"package_name\"], \"lib.so\"), \"\"),\n        File(\n            Path(relative_root, \".gitignore\"),\n            \"\"\"\\\n*.pyc\n*.so\n*.h\n\"\"\",\n        ),\n        File(\n            Path(relative_root, \"PKG-INFO\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/sdist/standard_default_vcs_mercurial_exclusion_files.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\n\n\ndef get_files(**kwargs):\n    relative_root = kwargs.get(\"relative_root\", \"\")\n\n    files = [File(Path(relative_root, f.path), f.contents) for f in get_template_files(**kwargs)]\n    files.extend((\n        File(Path(relative_root, kwargs[\"package_name\"], \"lib.so\"), \"\"),\n        File(\n            Path(relative_root, \".hgignore\"),\n            \"\"\"\\\nsyntax: glob\n*.pyc\n\nsyntax: foo\nREADME.md\n\nsyntax: glob\n*.so\n*.h\n\"\"\",\n        ),\n        File(\n            Path(relative_root, \"PKG-INFO\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/sdist/standard_include.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\n\n\ndef get_files(**kwargs):\n    relative_root = kwargs.get(\"relative_root\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        part = f.path.parts[0]\n        if part in {\"my_app\", \"pyproject.toml\", \"README.md\", \"LICENSE.txt\"}:\n            files.append(File(Path(relative_root, f.path), f.contents))\n\n    files.append(\n        File(\n            Path(relative_root, \"PKG-INFO\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nDescription-Content-Type: text/markdown\n\n# My.App\n\n[![PyPI - Version](https://img.shields.io/pypi/v/my-app.svg)](https://pypi.org/project/my-app)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/my-app.svg)](https://pypi.org/project/my-app)\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n- [License](#license)\n\n## Installation\n\n```console\npip install my-app\n```\n\n## License\n\n`my-app` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n\"\"\",\n        )\n    )\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/sdist/standard_include_config_file.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\n\n\ndef get_files(**kwargs):\n    relative_root = kwargs.get(\"relative_root\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        part = f.path.parts[0]\n        if part in {\"my_app\", \"pyproject.toml\", \"README.md\", \"LICENSE.txt\"}:\n            files.append(File(Path(relative_root, f.path), f.contents))\n\n    files.extend((\n        File(Path(relative_root, \"hatch.toml\"), \"\"),\n        File(\n            Path(relative_root, \"PKG-INFO\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nDescription-Content-Type: text/markdown\n\n# My.App\n\n[![PyPI - Version](https://img.shields.io/pypi/v/my-app.svg)](https://pypi.org/project/my-app)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/my-app.svg)](https://pypi.org/project/my-app)\n\n-----\n\n## Table of Contents\n\n- [Installation](#installation)\n- [License](#license)\n\n## Installation\n\n```console\npip install my-app\n```\n\n## License\n\n`my-app` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.\n\"\"\",\n        ),\n    ))\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/__init__.py",
    "content": ""
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_build_script.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: {kwargs.get(\"tag\", \"\")}\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_build_script_artifacts.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(Path(kwargs[\"package_name\"], \"lib.so\"), \"\"),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: false\nTag: {kwargs.get(\"tag\", \"\")}\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_build_script_artifacts_with_src_layout.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.default import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != \"src\":\n            continue\n\n        files.append(File(Path(*f.path.parts[1:]), f.contents))\n\n    files.extend((\n        File(Path(kwargs[\"package_name\"], \"lib.so\"), \"\"),\n        File(Path(\"zlib.pyd\"), \"\"),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: false\nTag: {kwargs.get(\"tag\", \"\")}\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_build_script_configured_build_hooks.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(Path(kwargs[\"package_name\"], \"lib.so\"), \"custom\"),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: false\nTag: {kwargs.get(\"tag\", \"\")}\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_build_script_extra_dependencies.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(Path(kwargs[\"package_name\"], \"lib.so\"), \"\"),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: false\nTag: {kwargs.get(\"tag\", \"\")}\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\nRequires-Dist: binary\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_build_script_force_include.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(Path(kwargs[\"package_name\"], \"lib.so\"), \"\"),\n        File(Path(kwargs[\"package_name\"], \"lib.h\"), \"\"),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: false\nTag: {kwargs.get(\"tag\", \"\")}\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_build_script_force_include_no_duplication.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(Path(kwargs[\"package_name\"], \"z.py\"), 'print(\"hello world\")'),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: false\nTag: {kwargs.get(\"tag\", \"\")}\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_extra_metadata.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(Path(metadata_directory, \"extra_metadata\", \"foo.txt\"), \"\"),\n        File(Path(metadata_directory, \"extra_metadata\", \"nested\", \"bar.txt\"), \"\"),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_license_multiple.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.licenses_multiple import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        first_part = f.path.parts[0]\n\n        if first_part == \"LICENSES\":\n            files.append(File(Path(metadata_directory, \"licenses\", \"LICENSES\", f.path.parts[1]), f.contents))\n\n        if f.path.parts[0] != \"src\":\n            continue\n\n        files.append(File(Path(*f.path.parts[1:]), f.contents))\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSES/Apache-2.0.txt\nLicense-File: LICENSES/MIT.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_license_single.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.default import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != \"src\":\n            continue\n\n        files.append(File(Path(*f.path.parts[1:]), f.contents))\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_namespace_package.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    namespace_package = kwargs[\"namespace\"]\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        f.path = Path(namespace_package, f.path)\n        files.append(f)\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_python_constraint.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_python_constraint_three_components.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: ==3.11.4\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_sbom.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.default import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    sbom_files = kwargs.get(\"sbom_files\", [])\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != \"src\":\n            continue\n\n        files.append(File(Path(*f.path.parts[1:]), f.contents))\n\n    # Add SBOM files\n    for sbom_path, sbom_content in sbom_files:\n        files.append(File(Path(metadata_directory, \"sboms\", sbom_path), sbom_content))\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_shared_data.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    shared_data_directory = kwargs.get(\"shared_data_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(Path(shared_data_directory, \"data\", \"foo.txt\"), \"\"),\n        File(Path(shared_data_directory, \"data\", \"nested\", \"bar.txt\"), \"\"),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_shared_scripts.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    shared_data_directory = kwargs.get(\"shared_data_directory\", \"\")\n    binary_contents = kwargs.get(\"binary_contents\", b\"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(Path(shared_data_directory, \"scripts\", \"binary\"), binary_contents),\n        File(\n            Path(shared_data_directory, \"scripts\", \"other_script.sh\"),\n            \"\"\"\\\n#!/bin/sh arg1 arg2\necho \"Hello, World!\"\n\"\"\",\n        ),\n        File(\n            Path(shared_data_directory, \"scripts\", \"python_script.sh\"),\n            \"\"\"\\\n#!python arg1 arg2\nprint(\"Hello, World!\")\n\"\"\",\n        ),\n        File(\n            Path(shared_data_directory, \"scripts\", \"pythonw_script.sh\"),\n            \"\"\"\\\n#!python arg1 arg2\nprint(\"Hello, World!\")\n\"\"\",\n        ),\n        File(\n            Path(shared_data_directory, \"scripts\", \"pypy_script.sh\"),\n            \"\"\"\\\n#!python\nprint(\"Hello, World!\")\n\"\"\",\n        ),\n        File(\n            Path(shared_data_directory, \"scripts\", \"pypyw_script.sh\"),\n            \"\"\"\\\n#!python arg1 arg2\nprint(\"Hello, World!\")\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_single_module.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = [\n        File(Path(metadata_directory, \"licenses\", f.path), f.contents)\n        for f in get_template_files(**kwargs)\n        if str(f.path) == \"LICENSE.txt\"\n    ]\n\n    files.extend((\n        File(Path(\"my_app.py\"), \"\"),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_default_symlink.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(Path(kwargs[\"package_name\"], \"lib.so\"), \"data\"),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: false\nTag: {kwargs.get(\"tag\", \"\")}\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Python: >3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_editable_exact.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    package_root = kwargs.get(\"package_root\", \"\")\n\n    files = [\n        File(Path(metadata_directory, \"licenses\", f.path), f.contents)\n        for f in get_template_files(**kwargs)\n        if str(f.path) == \"LICENSE.txt\"\n    ]\n\n    pth_file_name = f\"_{kwargs['package_name']}.pth\"\n    loader_file_name = f\"_editable_impl_{kwargs['package_name']}.py\"\n    files.extend((\n        File(Path(pth_file_name), f\"import _editable_impl_{kwargs['package_name']}\"),\n        File(\n            Path(loader_file_name),\n            f\"\"\"\\\nfrom editables.redirector import RedirectingFinder as F\nF.install()\nF.map_module({kwargs[\"package_name\"]!r}, {package_root!r})\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Dist: editables~=0.3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files, generated_files={pth_file_name, loader_file_name})\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_editable_exact_extra_dependencies.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    package_root = kwargs.get(\"package_root\", \"\")\n\n    files = [\n        File(Path(metadata_directory, \"licenses\", f.path), f.contents)\n        for f in get_template_files(**kwargs)\n        if str(f.path) == \"LICENSE.txt\"\n    ]\n\n    pth_file_name = f\"_{kwargs['package_name']}.pth\"\n    loader_file_name = f\"_editable_impl_{kwargs['package_name']}.py\"\n    files.extend((\n        File(Path(pth_file_name), f\"import _editable_impl_{kwargs['package_name']}\"),\n        File(\n            Path(loader_file_name),\n            f\"\"\"\\\nfrom editables.redirector import RedirectingFinder as F\nF.install()\nF.map_module({kwargs[\"package_name\"]!r}, {package_root!r})\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Dist: binary\nRequires-Dist: editables~=0.3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files, generated_files={pth_file_name, loader_file_name})\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_editable_exact_force_include.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    package_root = kwargs.get(\"package_root\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n        elif f.path.parts[-1] == \"__about__.py\":\n            files.append(File(Path(\"zfoo.py\"), f.contents))\n\n    pth_file_name = f\"_{kwargs['package_name']}.pth\"\n    loader_file_name = f\"_editable_impl_{kwargs['package_name']}.py\"\n    files.extend((\n        File(Path(pth_file_name), f\"import _editable_impl_{kwargs['package_name']}\"),\n        File(\n            Path(loader_file_name),\n            f\"\"\"\\\nfrom editables.redirector import RedirectingFinder as F\nF.install()\nF.map_module({kwargs[\"package_name\"]!r}, {package_root!r})\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Dist: editables~=0.3\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files, generated_files={pth_file_name, loader_file_name})\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_editable_pth.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.default import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    package_paths = kwargs.get(\"package_paths\", [])\n\n    files = [\n        File(Path(metadata_directory, \"licenses\", f.path), f.contents)\n        for f in get_template_files(**kwargs)\n        if str(f.path) == \"LICENSE.txt\"\n    ]\n\n    pth_file_name = f\"_{kwargs['package_name']}.pth\"\n    files.extend((\n        File(Path(pth_file_name), \"\\n\".join(package_paths)),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files, generated_files={pth_file_name})\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_editable_pth_extra_dependencies.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.default import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    package_paths = kwargs.get(\"package_paths\", [])\n\n    files = [\n        File(Path(metadata_directory, \"licenses\", f.path), f.contents)\n        for f in get_template_files(**kwargs)\n        if str(f.path) == \"LICENSE.txt\"\n    ]\n\n    pth_file_name = f\"_{kwargs['package_name']}.pth\"\n    files.extend((\n        File(Path(pth_file_name), \"\\n\".join(package_paths)),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\nRequires-Dist: binary\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files, generated_files={pth_file_name})\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_editable_pth_force_include.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.default import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n    package_paths = kwargs.get(\"package_paths\", [])\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n        elif f.path.parts[-1] == \"__about__.py\":\n            files.append(File(Path(\"zfoo.py\"), f.contents))\n\n    pth_file_name = f\"_{kwargs['package_name']}.pth\"\n    files.extend((\n        File(Path(pth_file_name), \"\\n\".join(package_paths)),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files, generated_files={pth_file_name})\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_entry_points.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"entry_points.txt\"),\n            \"\"\"\\\n[console_scripts]\nbar = pkg:foo\nfoo = pkg:bar\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_no_strict_naming.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] != kwargs[\"package_name\"]:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_only_packages_artifact_override.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] not in {kwargs[\"package_name\"], \"tests\"}:\n            continue\n\n        if f.path == Path(\"tests\", \"__init__.py\"):\n            f.path = Path(\"tests\", \"foo.py\")\n\n        files.append(f)\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/standard_tests.py",
    "content": "from hatch.template import File\nfrom hatch.utils.fs import Path\nfrom hatchling.__about__ import __version__\nfrom hatchling.metadata.spec import DEFAULT_METADATA_VERSION\n\nfrom ..new.feature_no_src_layout import get_files as get_template_files\nfrom .utils import update_record_file_contents\n\n\ndef get_files(**kwargs):\n    metadata_directory = kwargs.get(\"metadata_directory\", \"\")\n\n    files = []\n    for f in get_template_files(**kwargs):\n        if str(f.path) == \"LICENSE.txt\":\n            files.append(File(Path(metadata_directory, \"licenses\", f.path), f.contents))\n\n        if f.path.parts[0] not in {kwargs[\"package_name\"], \"tests\"}:\n            continue\n\n        files.append(f)\n\n    files.extend((\n        File(\n            Path(metadata_directory, \"WHEEL\"),\n            f\"\"\"\\\nWheel-Version: 1.0\nGenerator: hatchling {__version__}\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n\"\"\",\n        ),\n        File(\n            Path(metadata_directory, \"METADATA\"),\n            f\"\"\"\\\nMetadata-Version: {DEFAULT_METADATA_VERSION}\nName: {kwargs[\"project_name\"]}\nVersion: 0.0.1\nLicense-File: LICENSE.txt\n\"\"\",\n        ),\n    ))\n\n    record_file = File(Path(metadata_directory, \"RECORD\"), \"\")\n    update_record_file_contents(record_file, files)\n    files.append(record_file)\n\n    return files\n"
  },
  {
    "path": "tests/helpers/templates/wheel/utils.py",
    "content": "import hashlib\nimport os\nimport tempfile\n\nfrom hatchling.builders.utils import format_file_hash, normalize_artifact_permissions\n\n\ndef update_record_file_contents(record_file, files, generated_files=()):\n    for template_file in sorted(\n        files,\n        key=lambda f: (\n            f.path.parts[0].endswith(\".dist-info\"),\n            f.path.parts[0].endswith(\".dist-info\") and f.path.parts[1] == \"extra_metadata\",\n            f.path.parts[0].startswith(\"z\"),\n            len(f.path.parts),\n            f.path.parts,\n        ),\n    ):\n        if isinstance(template_file.contents, bytes):\n            is_binary = True\n            raw_contents = template_file.contents\n        else:\n            is_binary = False\n            raw_contents = template_file.contents.encode(\"utf-8\")\n\n        template_file_path = str(template_file.path)\n        if (\n            not is_binary\n            and os.linesep != \"\\n\"\n            and (\n                \"LICENSE\" in template_file_path\n                or (\n                    not template_file.path.parts[0].endswith(\".dist-info\")\n                    and all(f not in template_file_path for f in generated_files)\n                )\n            )\n        ):\n            raw_contents = raw_contents.replace(b\"\\n\", b\"\\r\\n\")\n\n        hash_obj = hashlib.sha256()\n        hash_obj.update(raw_contents)\n        hash_digest = format_file_hash(hash_obj.digest())\n        record_file.contents += f\"{template_file.path.as_posix()},sha256={hash_digest},{len(raw_contents)}\\n\"\n\n    record_file.contents += f\"{record_file.path.as_posix()},,\\n\"\n\n\ndef test_normalize_artifact_permissions():\n    \"\"\"\n    assert that this func does what we expect on a tmpfile that that starts at 600\n    \"\"\"\n    _, path = tempfile.mkstemp()\n\n    file_stat = os.stat(path)\n    assert file_stat.st_mode == 0o100600\n\n    normalize_artifact_permissions(path)\n\n    file_stat = os.stat(path)\n    assert file_stat.st_mode == 0o100644\n"
  },
  {
    "path": "tests/index/__init__.py",
    "content": ""
  },
  {
    "path": "tests/index/server/devpi/Dockerfile",
    "content": "FROM python:3.11-alpine\n\nRUN apk add --update build-base && \\\n    pip install -U devpi-server devpi-client devpi-web\n\nEXPOSE 3141\n\nCOPY entrypoint.sh /\nENTRYPOINT [\"/bin/ash\", \"/entrypoint.sh\"]\n"
  },
  {
    "path": "tests/index/server/devpi/entrypoint.sh",
    "content": "#!/bin/ash\n# http://redsymbol.net/articles/unofficial-bash-strict-mode/\nIFS=$'\\n\\t'\nset -euo pipefail\n\necho \"==:> Initializing server\"\ndevpi-init --no-root-pypi\n\necho \"==:> Starting server\"\ndevpi-server --host 0.0.0.0 --port 3141 &\n\necho \"==:> Waiting on server\"\nfor i in $(seq 1 30); do\n    if devpi use http://localhost:3141 2>/dev/null; then\n        break\n    fi\n    if [ \"$i\" -eq 30 ]; then\n        echo \"Timed out waiting for devpi-server\"\n        exit 1\n    fi\n    sleep 1\ndone\n\n\necho \"==:> Setting up index\"\ndevpi use http://localhost:3141\ndevpi user -c $DEVPI_USERNAME password=$DEVPI_PASSWORD\ndevpi login $DEVPI_USERNAME --password=$DEVPI_PASSWORD\ndevpi index -c $DEVPI_INDEX_NAME volatile=True mirror_whitelist=\"*\"\ndevpi use $DEVPI_USERNAME/$DEVPI_INDEX_NAME\ndevpi logoff\n\necho \"==:> Serving index $DEVPI_USERNAME/$DEVPI_INDEX_NAME\"\nsleep infinity\n"
  },
  {
    "path": "tests/index/server/docker-compose.yaml",
    "content": "services:\n  devpi:\n    container_name: hatch-devpi\n    build:\n      context: devpi\n    ports:\n    - \"3141:3141\"\n    environment:\n    - DEVPI_INDEX_NAME\n    - DEVPI_USERNAME\n    - DEVPI_PASSWORD\n    healthcheck:\n      test: [ \"CMD\", \"python\", \"-c\", \"import urllib.request; urllib.request.urlopen('http://localhost:3141')\" ]\n      interval: 2s\n      timeout: 5s\n      retries: 30\n      start_period: 10s\n\n  nginx:\n    container_name: hatch-nginx\n    image: nginx:alpine\n    ports:\n    - \"8080:80\"\n    - \"8443:443\"\n    volumes:\n    - ./nginx:/etc/nginx\n    depends_on:\n      devpi:\n        condition: service_healthy"
  },
  {
    "path": "tests/index/server/nginx/nginx.conf",
    "content": "worker_processes 1;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    # Redirect HTTP to HTTPS\n    server {\n        listen 80;\n        listen [::]:80;\n        server_name _;\n        return 301 https://$host$request_uri;\n    }\n\n    server {\n        listen 443 ssl;\n        server_name localhost;\n\n        gzip on;\n        gzip_min_length 2000;\n        gzip_proxied any;\n\n        proxy_read_timeout 60s;\n        client_max_body_size 64M;\n\n        ssl_certificate server.pem;\n        ssl_certificate_key server.key;\n\n        ssl_session_cache builtin:1000  shared:SSL:10m;\n        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n        ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;\n        ssl_prefer_server_ciphers on;\n\n        location / {\n            error_page 418 = @proxy_to_app;\n            return 418;\n        }\n\n        location @proxy_to_app {\n            proxy_pass http://devpi:3141;\n            proxy_pass_header Authorization;\n            proxy_redirect off;\n            proxy_set_header Host $host;\n            proxy_set_header X-Real-IP $remote_addr;\n            proxy_set_header X-Outside-URL $scheme://$host:$server_port;\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n            proxy_set_header X-Forwarded-Host $server_name;\n        }\n    }\n}\n"
  },
  {
    "path": "tests/index/test_core.py",
    "content": "import platform\nimport sys\n\nimport httpx\nimport pytest\n\nfrom hatch._version import __version__\nfrom hatch.index.core import PackageIndex\n\n\nclass TestRepo:\n    def test_normalization(self):\n        index = PackageIndex(\"Https://Foo.Internal/z/../a/b/\")\n\n        assert index.repo == \"https://foo.internal/a/b/\"\n\n\nclass TestURLs:\n    @pytest.mark.parametrize(\n        (\"repo_url\", \"expected_url\"),\n        [\n            pytest.param(\"https://upload.pypi.org/legacy/\", \"https://pypi.org/simple/\", id=\"PyPI main\"),\n            pytest.param(\"https://test.pypi.org/legacy/\", \"https://test.pypi.org/simple/\", id=\"PyPI test\"),\n            pytest.param(\"https://foo.internal/a/b/\", \"https://foo.internal/a/b/%2Bsimple/\", id=\"default\"),\n        ],\n    )\n    def test_simple(self, repo_url, expected_url):\n        index = PackageIndex(repo_url)\n\n        assert str(index.urls.simple) == expected_url\n\n    @pytest.mark.parametrize(\n        (\"repo_url\", \"expected_url\"),\n        [\n            pytest.param(\"https://upload.pypi.org/legacy/\", \"https://pypi.org/project/\", id=\"PyPI main\"),\n            pytest.param(\"https://test.pypi.org/legacy/\", \"https://test.pypi.org/project/\", id=\"PyPI test\"),\n            pytest.param(\"https://foo.internal/a/b/\", \"https://foo.internal/a/b/\", id=\"default\"),\n        ],\n    )\n    def test_project(self, repo_url, expected_url):\n        index = PackageIndex(repo_url)\n\n        assert str(index.urls.project) == expected_url\n\n\nclass TestTLS:\n    def test_default(self, mocker):\n        mock = mocker.patch(\"httpx._transports.default.create_ssl_context\")\n        index = PackageIndex(\"https://foo.internal/a/b/\")\n        _ = index.client\n\n        mock.assert_called_once_with(verify=True, cert=None, trust_env=True)\n\n    def test_ca_cert(self, mocker):\n        mock = mocker.patch(\"httpx._transports.default.create_ssl_context\")\n        index = PackageIndex(\"https://foo.internal/a/b/\", ca_cert=\"foo\")\n        _ = index.client\n\n        mock.assert_called_once_with(verify=\"foo\", cert=None, trust_env=True)\n\n    def test_client_cert(self, mocker):\n        mock = mocker.patch(\"httpx._transports.default.create_ssl_context\")\n        index = PackageIndex(\"https://foo.internal/a/b/\", client_cert=\"foo\")\n        _ = index.client\n\n        mock.assert_called_once_with(verify=True, cert=\"foo\", trust_env=True)\n\n    def test_client_cert_with_key(self, mocker):\n        mock = mocker.patch(\"httpx._transports.default.create_ssl_context\")\n        index = PackageIndex(\"https://foo.internal/a/b/\", client_cert=\"foo\", client_key=\"bar\")\n        _ = index.client\n\n        mock.assert_called_once_with(verify=True, cert=(\"foo\", \"bar\"), trust_env=True)\n\n\nclass TestUserAgent:\n    def test_user_agent_header_format(self):\n        index = PackageIndex(\"https://foo.internal/a/b/\")\n        client = index.client\n\n        user_agent = client.headers[\"User-Agent\"]\n\n        expected = (\n            f\"Hatch/{__version__} {sys.implementation.name}/{platform.python_version()} HTTPX/{httpx.__version__}\"\n        )\n        assert user_agent == expected\n"
  },
  {
    "path": "tests/project/__init__.py",
    "content": ""
  },
  {
    "path": "tests/project/test_config.py",
    "content": "from itertools import product\n\nimport pytest\n\nfrom hatch.plugin.constants import DEFAULT_CUSTOM_SCRIPT\nfrom hatch.plugin.manager import PluginManager\nfrom hatch.project.config import ProjectConfig\nfrom hatch.project.constants import DEFAULT_BUILD_DIRECTORY, BuildEnvVars\nfrom hatch.project.env import RESERVED_OPTIONS\nfrom hatch.utils.structures import EnvVars\n\nARRAY_OPTIONS = [o for o, t in RESERVED_OPTIONS.items() if t is list]\nBOOLEAN_OPTIONS = [o for o, t in RESERVED_OPTIONS.items() if t is bool]\nMAPPING_OPTIONS = [o for o, t in RESERVED_OPTIONS.items() if t is dict and o != \"workspace\"]\nSTRING_OPTIONS = [o for o, t in RESERVED_OPTIONS.items() if t is str and o != \"matrix-name-format\"]\nWORKSPACE_OPTIONS = [\"workspace\"]  # Workspace has nested structure, tested separately\n\n\ndef construct_matrix_data(env_name, config, overrides=None):\n    config = dict(config[env_name])\n    config.pop(\"overrides\", None)\n    matrices = config.pop(\"matrix\")\n    final_matrix_name_format = config.pop(\"matrix-name-format\", \"{value}\")\n\n    # [{'version': ['9000']}, {'feature': ['bar']}]\n    envs = {}\n    for matrix_data in matrices:\n        matrix = dict(matrix_data)\n        variables = {}\n        python_selected = False\n        for variable in (\"py\", \"python\"):\n            if variable in matrix:\n                python_selected = True\n                variables[variable] = matrix.pop(variable)\n                break\n        variables.update(matrix)\n\n        for result in product(*variables.values()):\n            variable_values = dict(zip(variables, result, strict=False))\n            env_name_parts = []\n            for j, (variable, value) in enumerate(variable_values.items()):\n                if j == 0 and python_selected:\n                    env_name_parts.append(value if value.startswith(\"py\") else f\"py{value}\")\n                else:\n                    env_name_parts.append(final_matrix_name_format.format(variable=variable, value=value))\n\n            new_env_name = \"-\".join(env_name_parts)\n            if env_name != \"default\":\n                new_env_name = f\"{env_name}.{new_env_name}\"\n\n            envs[new_env_name] = variable_values\n            if \"py\" in variable_values:\n                envs[new_env_name] = {\"python\": variable_values.pop(\"py\"), **variable_values}\n\n    config.update(overrides or {})\n    config.setdefault(\"type\", \"virtual\")\n    return {\"config\": config, \"envs\": envs}\n\n\nclass TestEnv:\n    def test_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.env` must be a table\"):\n            _ = ProjectConfig(isolation, {\"env\": 9000}).env\n\n    def test_default(self, isolation):\n        project_config = ProjectConfig(isolation, {})\n\n        assert project_config.env == project_config.env == {}\n\n\nclass TestEnvRequires:\n    def test_not_array(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.env.requires` must be an array\"):\n            _ = ProjectConfig(isolation, {\"env\": {\"requires\": 9000}}).env_requires\n\n    def test_requirement_not_string(self, isolation):\n        with pytest.raises(TypeError, match=\"Requirement #1 in `tool.hatch.env.requires` must be a string\"):\n            _ = ProjectConfig(isolation, {\"env\": {\"requires\": [9000]}}).env_requires\n\n    def test_requirement_invalid(self, isolation):\n        with pytest.raises(ValueError, match=\"Requirement #1 in `tool.hatch.env.requires` is invalid: .+\"):\n            _ = ProjectConfig(isolation, {\"env\": {\"requires\": [\"foo^1\"]}}).env_requires\n\n    def test_default(self, isolation):\n        project_config = ProjectConfig(isolation, {})\n\n        assert project_config.env_requires_complex == project_config.env_requires_complex == []\n        assert project_config.env_requires == project_config.env_requires == []\n\n    def test_defined(self, isolation):\n        project_config = ProjectConfig(isolation, {\"env\": {\"requires\": [\"foo\", \"bar\", \"baz\"]}})\n\n        assert project_config.env_requires == [\"foo\", \"bar\", \"baz\"]\n\n\nclass TestEnvCollectors:\n    def test_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.env.collectors` must be a table\"):\n            _ = ProjectConfig(isolation, {\"env\": {\"collectors\": 9000}}).env_collectors\n\n    def test_collector_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.env.collectors.foo` must be a table\"):\n            _ = ProjectConfig(isolation, {\"env\": {\"collectors\": {\"foo\": 9000}}}).env_collectors\n\n    def test_default(self, isolation):\n        project_config = ProjectConfig(isolation, {})\n\n        assert project_config.env_collectors == project_config.env_collectors == {\"default\": {}}\n\n    def test_defined(self, isolation):\n        project_config = ProjectConfig(isolation, {\"env\": {\"collectors\": {\"foo\": {\"bar\": {\"baz\": 9000}}}}})\n\n        assert project_config.env_collectors == {\"default\": {}, \"foo\": {\"bar\": {\"baz\": 9000}}}\n        assert list(project_config.env_collectors) == [\"default\", \"foo\"]\n\n\nclass TestEnvs:\n    def test_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs` must be a table\"):\n            _ = ProjectConfig(isolation, {\"envs\": 9000}, PluginManager()).envs\n\n    def test_config_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo` must be a table\"):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": 9000}}, PluginManager()).envs\n\n    def test_unknown_collector(self, isolation):\n        with pytest.raises(ValueError, match=\"Unknown environment collector: foo\"):\n            _ = ProjectConfig(isolation, {\"env\": {\"collectors\": {\"foo\": {}}}}, PluginManager()).envs\n\n    def test_unknown_template(self, isolation):\n        with pytest.raises(\n            ValueError, match=\"Field `tool.hatch.envs.foo.template` refers to an unknown environment `bar`\"\n        ):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"template\": \"bar\"}}}, PluginManager()).envs\n\n    def test_default_undefined(self, isolation):\n        project_config = ProjectConfig(isolation, {}, PluginManager())\n\n        assert project_config.envs == project_config.envs == {\"default\": {\"type\": \"virtual\"}}\n        assert project_config.matrices == project_config.matrices == {}\n\n    def test_default_partially_defined(self, isolation):\n        env_config = {\"default\": {\"option\": True}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        assert project_config.envs == {\"default\": {\"option\": True, \"type\": \"virtual\"}}\n\n    def test_default_defined(self, isolation):\n        env_config = {\"default\": {\"type\": \"foo\"}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        assert project_config.envs == {\"default\": {\"type\": \"foo\"}}\n\n    def test_basic(self, isolation):\n        env_config = {\"foo\": {\"option\": True}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        assert project_config.envs == {\"default\": {\"type\": \"virtual\"}, \"foo\": {\"option\": True, \"type\": \"virtual\"}}\n\n    def test_basic_override(self, isolation):\n        env_config = {\"foo\": {\"type\": \"baz\"}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        assert project_config.envs == {\"default\": {\"type\": \"virtual\"}, \"foo\": {\"type\": \"baz\"}}\n\n    def test_multiple_inheritance(self, isolation):\n        env_config = {\n            \"foo\": {\"option1\": \"foo\"},\n            \"bar\": {\"template\": \"foo\", \"option2\": \"bar\"},\n            \"baz\": {\"template\": \"bar\", \"option3\": \"baz\"},\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        assert project_config.envs == {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"option1\": \"foo\"},\n            \"bar\": {\"type\": \"virtual\", \"option1\": \"foo\", \"option2\": \"bar\"},\n            \"baz\": {\"type\": \"virtual\", \"option1\": \"foo\", \"option2\": \"bar\", \"option3\": \"baz\"},\n        }\n\n    def test_circular_inheritance(self, isolation):\n        with pytest.raises(\n            ValueError, match=\"Circular inheritance detected for field `tool.hatch.envs.*.template`: foo -> bar -> foo\"\n        ):\n            _ = ProjectConfig(\n                isolation, {\"envs\": {\"foo\": {\"template\": \"bar\"}, \"bar\": {\"template\": \"foo\"}}}, PluginManager()\n            ).envs\n\n    def test_scripts_inheritance(self, isolation):\n        env_config = {\n            \"default\": {\"scripts\": {\"cmd1\": \"bar\", \"cmd2\": \"baz\"}},\n            \"foo\": {\"scripts\": {\"cmd1\": \"foo\"}},\n            \"bar\": {\"template\": \"foo\", \"scripts\": {\"cmd3\": \"bar\"}},\n            \"baz\": {},\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        assert project_config.envs == {\n            \"default\": {\"type\": \"virtual\", \"scripts\": {\"cmd1\": \"bar\", \"cmd2\": \"baz\"}},\n            \"foo\": {\"type\": \"virtual\", \"scripts\": {\"cmd1\": \"foo\", \"cmd2\": \"baz\"}},\n            \"bar\": {\"type\": \"virtual\", \"scripts\": {\"cmd1\": \"foo\", \"cmd2\": \"baz\", \"cmd3\": \"bar\"}},\n            \"baz\": {\"type\": \"virtual\", \"scripts\": {\"cmd1\": \"bar\", \"cmd2\": \"baz\"}},\n        }\n\n    def test_self_referential(self, isolation):\n        env_config = {\"default\": {\"option1\": \"foo\"}, \"bar\": {\"template\": \"bar\"}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        assert project_config.envs == {\n            \"default\": {\"type\": \"virtual\", \"option1\": \"foo\"},\n            \"bar\": {\"type\": \"virtual\"},\n        }\n\n    def test_detached(self, isolation):\n        env_config = {\"default\": {\"option1\": \"foo\"}, \"bar\": {\"detached\": True}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        assert project_config.envs == {\n            \"default\": {\"type\": \"virtual\", \"option1\": \"foo\"},\n            \"bar\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n    def test_matrices_not_array(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.matrix` must be an array\"):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix\": 9000}}}, PluginManager()).envs\n\n    def test_matrix_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Entry #1 in field `tool.hatch.envs.foo.matrix` must be a table\"):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix\": [9000]}}}, PluginManager()).envs\n\n    def test_matrix_empty(self, isolation):\n        with pytest.raises(ValueError, match=\"Matrix #1 in field `tool.hatch.envs.foo.matrix` cannot be empty\"):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix\": [{}]}}}, PluginManager()).envs\n\n    def test_matrix_variable_empty_string(self, isolation):\n        with pytest.raises(\n            ValueError, match=\"Variable #1 in matrix #1 in field `tool.hatch.envs.foo.matrix` cannot be an empty string\"\n        ):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix\": [{\"\": []}]}}}, PluginManager()).envs\n\n    def test_matrix_variable_not_array(self, isolation):\n        with pytest.raises(\n            TypeError, match=\"Variable `bar` in matrix #1 in field `tool.hatch.envs.foo.matrix` must be an array\"\n        ):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix\": [{\"bar\": 9000}]}}}, PluginManager()).envs\n\n    def test_matrix_variable_array_empty(self, isolation):\n        with pytest.raises(\n            ValueError, match=\"Variable `bar` in matrix #1 in field `tool.hatch.envs.foo.matrix` cannot be empty\"\n        ):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix\": [{\"bar\": []}]}}}, PluginManager()).envs\n\n    def test_matrix_variable_entry_not_string(self, isolation):\n        with pytest.raises(\n            TypeError,\n            match=\"Value #1 of variable `bar` in matrix #1 in field `tool.hatch.envs.foo.matrix` must be a string\",\n        ):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix\": [{\"bar\": [9000]}]}}}, PluginManager()).envs\n\n    def test_matrix_variable_entry_empty_string(self, isolation):\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Value #1 of variable `bar` in matrix #1 in field `tool.hatch.envs.foo.matrix` \"\n                \"cannot be an empty string\"\n            ),\n        ):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix\": [{\"bar\": [\"\"]}]}}}, PluginManager()).envs\n\n    def test_matrix_variable_entry_duplicate(self, isolation):\n        with pytest.raises(\n            ValueError,\n            match=\"Value #2 of variable `bar` in matrix #1 in field `tool.hatch.envs.foo.matrix` is a duplicate\",\n        ):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix\": [{\"bar\": [\"1\", \"1\"]}]}}}, PluginManager()).envs\n\n    def test_matrix_multiple_python_variables(self, isolation):\n        with pytest.raises(\n            ValueError,\n            match=\"Matrix #1 in field `tool.hatch.envs.foo.matrix` cannot contain both `py` and `python` variables\",\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\"envs\": {\"foo\": {\"matrix\": [{\"py\": [\"39\", \"310\"], \"python\": [\"39\", \"311\"]}]}}},\n                PluginManager(),\n            ).envs\n\n    def test_matrix_name_format_not_string(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.matrix-name-format` must be a string\"):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix-name-format\": 9000}}}, PluginManager()).envs\n\n    def test_matrix_name_format_invalid(self, isolation):\n        with pytest.raises(\n            ValueError,\n            match=\"Field `tool.hatch.envs.foo.matrix-name-format` must contain at least the `{value}` placeholder\",\n        ):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"matrix-name-format\": \"bar\"}}}, PluginManager()).envs\n\n    def test_overrides_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.overrides` must be a table\"):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"overrides\": 9000}}}, PluginManager()).envs\n\n    def test_overrides_platform_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.overrides.platform` must be a table\"):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"overrides\": {\"platform\": 9000}}}}, PluginManager()).envs\n\n    def test_overrides_env_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.overrides.env` must be a table\"):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"overrides\": {\"env\": 9000}}}}, PluginManager()).envs\n\n    def test_overrides_matrix_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.overrides.matrix` must be a table\"):\n            _ = ProjectConfig(\n                isolation,\n                {\"envs\": {\"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": 9000}}}},\n                PluginManager(),\n            ).envs\n\n    def test_overrides_name_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.overrides.name` must be a table\"):\n            _ = ProjectConfig(\n                isolation,\n                {\"envs\": {\"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"name\": 9000}}}},\n                PluginManager(),\n            ).envs\n\n    def test_overrides_platform_entry_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.overrides.platform.bar` must be a table\"):\n            _ = ProjectConfig(\n                isolation, {\"envs\": {\"foo\": {\"overrides\": {\"platform\": {\"bar\": 9000}}}}}, PluginManager()\n            ).envs\n\n    def test_overrides_env_entry_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.overrides.env.bar` must be a table\"):\n            _ = ProjectConfig(isolation, {\"envs\": {\"foo\": {\"overrides\": {\"env\": {\"bar\": 9000}}}}}, PluginManager()).envs\n\n    def test_overrides_matrix_entry_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.overrides.matrix.bar` must be a table\"):\n            _ = ProjectConfig(\n                isolation,\n                {\"envs\": {\"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"bar\": 9000}}}}},\n                PluginManager(),\n            ).envs\n\n    def test_overrides_name_entry_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.envs.foo.overrides.name.bar` must be a table\"):\n            _ = ProjectConfig(\n                isolation,\n                {\"envs\": {\"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"name\": {\"bar\": 9000}}}}},\n                PluginManager(),\n            ).envs\n\n    def test_matrix_simple_no_python(self, isolation):\n        env_config = {\"foo\": {\"option\": True, \"matrix\": [{\"version\": [\"9000\", \"3.14\"]}]}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", \"option\": True},\n            \"foo.3.14\": {\"type\": \"virtual\", \"option\": True},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    def test_matrix_simple_no_python_custom_name_format(self, isolation):\n        env_config = {\n            \"foo\": {\n                \"option\": True,\n                \"matrix-name-format\": \"{variable}_{value}\",\n                \"matrix\": [{\"version\": [\"9000\", \"3.14\"]}],\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.version_9000\": {\"type\": \"virtual\", \"option\": True},\n            \"foo.version_3.14\": {\"type\": \"virtual\", \"option\": True},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"indicator\", [\"py\", \"python\"])\n    def test_matrix_simple_only_python(self, isolation, indicator):\n        env_config = {\"foo\": {\"option\": True, \"matrix\": [{indicator: [\"39\", \"310\"]}]}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.py39\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py310\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"indicator\", [\"py\", \"python\"])\n    def test_matrix_simple(self, isolation, indicator):\n        env_config = {\"foo\": {\"option\": True, \"matrix\": [{\"version\": [\"9000\", \"3.14\"], indicator: [\"39\", \"310\"]}]}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.py39-9000\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py39-3.14\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py310-9000\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-3.14\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"indicator\", [\"py\", \"python\"])\n    def test_matrix_simple_custom_name_format(self, isolation, indicator):\n        env_config = {\n            \"foo\": {\n                \"option\": True,\n                \"matrix-name-format\": \"{variable}_{value}\",\n                \"matrix\": [{\"version\": [\"9000\", \"3.14\"], indicator: [\"39\", \"310\"]}],\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.py39-version_9000\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py39-version_3.14\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py310-version_9000\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-version_3.14\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    def test_matrix_multiple_non_python(self, isolation):\n        env_config = {\n            \"foo\": {\n                \"option\": True,\n                \"matrix\": [{\"version\": [\"9000\", \"3.14\"], \"py\": [\"39\", \"310\"], \"foo\": [\"baz\", \"bar\"]}],\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.py39-9000-baz\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py39-9000-bar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py39-3.14-baz\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py39-3.14-bar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py310-9000-baz\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-9000-bar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-3.14-baz\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-3.14-bar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    def test_matrix_series(self, isolation):\n        env_config = {\n            \"foo\": {\n                \"option\": True,\n                \"matrix\": [\n                    {\"version\": [\"9000\", \"3.14\"], \"py\": [\"39\", \"310\"], \"foo\": [\"baz\", \"bar\"]},\n                    {\"version\": [\"9000\"], \"py\": [\"310\"], \"baz\": [\"foo\", \"test\"], \"bar\": [\"foobar\"]},\n                ],\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.py39-9000-baz\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py39-9000-bar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py39-3.14-baz\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py39-3.14-bar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"39\"},\n            \"foo.py310-9000-baz\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-9000-bar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-3.14-baz\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-3.14-bar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-9000-foo-foobar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n            \"foo.py310-9000-test-foobar\": {\"type\": \"virtual\", \"option\": True, \"python\": \"310\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    def test_matrices_not_inherited(self, isolation):\n        env_config = {\n            \"foo\": {\"option1\": True, \"matrix\": [{\"py\": [\"39\"]}]},\n            \"bar\": {\"template\": \"foo\", \"option2\": False},\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.py39\": {\"type\": \"virtual\", \"option1\": True, \"python\": \"39\"},\n            \"bar\": {\"type\": \"virtual\", \"option1\": True, \"option2\": False},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    def test_matrix_default_naming(self, isolation):\n        env_config = {\"default\": {\"option\": True, \"matrix\": [{\"version\": [\"9000\", \"3.14\"]}]}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"9000\": {\"type\": \"virtual\", \"option\": True},\n            \"3.14\": {\"type\": \"virtual\", \"option\": True},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"default\"] == construct_matrix_data(\"default\", env_config)\n\n    def test_matrix_pypy_naming(self, isolation):\n        env_config = {\"foo\": {\"option\": True, \"matrix\": [{\"py\": [\"python3.9\", \"pypy3\"]}]}}\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.python3.9\": {\"type\": \"virtual\", \"option\": True, \"python\": \"python3.9\"},\n            \"foo.pypy3\": {\"type\": \"virtual\", \"option\": True, \"python\": \"pypy3\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_invalid_type(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=f\"Field `tool.hatch.envs.foo.overrides.matrix.version.{option}` must be a string or an array\",\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: 9000}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_array_entry_invalid_type(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a string or an inline table\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [9000]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_table_entry_no_key(self, isolation, option):\n        with pytest.raises(\n            ValueError,\n            match=(\n                f\"Entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must have an option named `key`\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: [{}]}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_table_entry_key_not_string(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `key` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a string\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"key\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_table_entry_key_empty_string(self, isolation, option):\n        with pytest.raises(\n            ValueError,\n            match=(\n                f\"Option `key` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"cannot be an empty string\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"key\": \"\"}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_table_entry_value_not_string(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `value` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a string\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"key\": \"foo\", \"value\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_table_entry_if_not_array(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `if` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be an array\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\n                                \"matrix\": {\"version\": {option: [{\"key\": \"foo\", \"value\": \"bar\", \"if\": 9000}]}}\n                            },\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_invalid_type(self, isolation, option):\n        with pytest.raises(\n            TypeError, match=f\"Field `tool.hatch.envs.foo.overrides.matrix.version.{option}` must be an array\"\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: 9000}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_entry_no_value(self, isolation, option):\n        with pytest.raises(\n            ValueError,\n            match=(\n                f\"Entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must have an option named `value`\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: [{}]}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_entry_value_not_string(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `value` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a string\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_entry_value_empty_string(self, isolation, option):\n        with pytest.raises(\n            ValueError,\n            match=(\n                f\"Option `value` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"cannot be an empty string\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"\"}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_entry_if_not_array(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `if` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be an array\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"foo\", \"if\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_entry_invalid_type(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a string or an inline table\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [9000]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_invalid_type(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a string, inline table, or an array\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: 9000}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_table_no_value(self, isolation, option):\n        with pytest.raises(\n            ValueError,\n            match=f\"Field `tool.hatch.envs.foo.overrides.matrix.version.{option}` must have an option named `value`\",\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: {}}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_table_value_not_string(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=f\"Option `value` in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` must be a string\",\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: {\"value\": 9000}}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_array_entry_invalid_type(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a string or an inline table\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [9000]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_array_table_no_value(self, isolation, option):\n        with pytest.raises(\n            ValueError,\n            match=(\n                f\"Entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must have an option named `value`\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: [{}]}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_array_table_value_not_string(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `value` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a string\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_array_table_if_not_array(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `if` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be an array\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"foo\", \"if\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_invalid_type(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a boolean, inline table, or an array\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: 9000}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_table_no_value(self, isolation, option):\n        with pytest.raises(\n            ValueError,\n            match=f\"Field `tool.hatch.envs.foo.overrides.matrix.version.{option}` must have an option named `value`\",\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: {}}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_table_value_not_boolean(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=f\"Option `value` in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` must be a boolean\",\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: {\"value\": 9000}}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_entry_invalid_type(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a boolean or an inline table\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [9000]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_no_value(self, isolation, option):\n        with pytest.raises(\n            ValueError,\n            match=(\n                f\"Entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must have an option named `value`\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: [{}]}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_value_not_boolean(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `value` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be a boolean\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_if_not_array(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `if` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be an array\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": True, \"if\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_platform_not_array(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `platform` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be an array\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": True, \"platform\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_platform_item_not_string(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Item #1 in option `platform` in entry #1 in field \"\n                f\"`tool.hatch.envs.foo.overrides.matrix.version.{option}` must be a string\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": True, \"platform\": [9000]}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_env_not_array(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Option `env` in entry #1 in field `tool.hatch.envs.foo.overrides.matrix.version.{option}` \"\n                f\"must be an array\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": True, \"env\": 9000}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_env_item_not_string(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=(\n                f\"Item #1 in option `env` in entry #1 in field \"\n                f\"`tool.hatch.envs.foo.overrides.matrix.version.{option}` must be a string\"\n            ),\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\n                            \"matrix\": [{\"version\": [\"9000\"]}],\n                            \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": True, \"env\": [9000]}]}}},\n                        }\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_string_with_value(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: \"FOO=ok\"}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"FOO\": \"ok\"}},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_string_without_value(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: \"FOO\"}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"FOO\": \"9000\"}},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_string_override(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: {\"TEST\": \"baz\"},\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: \"TEST\"}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"TEST\": \"9000\"}},\n            \"foo.bar\": {\"type\": \"virtual\", option: {\"TEST\": \"baz\"}},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_array_string_with_value(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [\"FOO=ok\"]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"FOO\": \"ok\"}},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_array_string_without_value(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [\"FOO\"]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"FOO\": \"9000\"}},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_array_string_override(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: {\"TEST\": \"baz\"},\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [\"TEST\"]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"TEST\": \"9000\"}},\n            \"foo.bar\": {\"type\": \"virtual\", option: {\"TEST\": \"baz\"}},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_array_table_key_with_value(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"key\": \"FOO\", \"value\": \"ok\"}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"FOO\": \"ok\"}},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_array_table_key_without_value(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"key\": \"FOO\"}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"FOO\": \"9000\"}},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_array_table_override(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: {\"TEST\": \"baz\"},\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"key\": \"TEST\"}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"TEST\": \"9000\"}},\n            \"foo.bar\": {\"type\": \"virtual\", option: {\"TEST\": \"baz\"}},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_array_table_conditional(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: {\"TEST\": \"baz\"},\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"key\": \"TEST\", \"if\": [\"42\"]}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"TEST\": \"baz\"}},\n            \"foo.42\": {\"type\": \"virtual\", option: {\"TEST\": \"42\"}},\n            \"foo.bar\": {\"type\": \"virtual\", option: {\"TEST\": \"baz\"}},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", MAPPING_OPTIONS)\n    def test_overrides_matrix_mapping_overwrite(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: {\"TEST\": \"baz\"},\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {f\"set-{option}\": [\"FOO=bar\", {\"key\": \"BAZ\"}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"FOO\": \"bar\", \"BAZ\": \"9000\"}},\n            \"foo.bar\": {\"type\": \"virtual\", option: {\"TEST\": \"baz\"}},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_string(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [\"run foo\"]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run foo\"]},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_string_existing_append(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [\"run foo\"]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\", \"run foo\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"run foo\"}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run foo\"]},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_existing_append(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"run foo\"}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\", \"run foo\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_conditional(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"run foo\", \"if\": [\"42\"]}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.42\": {\"type\": \"virtual\", option: [\"run baz\", \"run foo\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_conditional_with_platform(self, isolation, option, current_platform):\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"matrix\": {\n                        \"version\": {option: [{\"value\": \"run foo\", \"if\": [\"42\"], \"platform\": [current_platform]}]}\n                    },\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.42\": {\"type\": \"virtual\", option: [\"run baz\", \"run foo\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_conditional_with_wrong_platform(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"matrix\": {\"version\": {option: [{\"value\": \"run foo\", \"if\": [\"42\"], \"platform\": [\"bar\"]}]}},\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.42\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_conditional_with_env_var_match(self, isolation, option):\n        env_var = \"OVERRIDES_ENV_FOO\"\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"matrix\": {\"version\": {option: [{\"value\": \"run foo\", \"if\": [\"42\"], \"env\": [f\"{env_var}=bar\"]}]}}\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.42\": {\"type\": \"virtual\", option: [\"run baz\", \"run foo\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        with EnvVars({env_var: \"bar\"}):\n            assert project_config.envs == expected_envs\n            assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_conditional_with_env_var_match_empty_string(self, isolation, option):\n        env_var = \"OVERRIDES_ENV_FOO\"\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"matrix\": {\"version\": {option: [{\"value\": \"run foo\", \"if\": [\"42\"], \"env\": [f\"{env_var}=\"]}]}}\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.42\": {\"type\": \"virtual\", option: [\"run baz\", \"run foo\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        with EnvVars({env_var: \"\"}):\n            assert project_config.envs == expected_envs\n            assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_conditional_with_env_var_present(self, isolation, option):\n        env_var = \"OVERRIDES_ENV_FOO\"\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"run foo\", \"if\": [\"42\"], \"env\": [env_var]}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.42\": {\"type\": \"virtual\", option: [\"run baz\", \"run foo\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        with EnvVars({env_var: \"any\"}):\n            assert project_config.envs == expected_envs\n            assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_conditional_with_env_var_no_match(self, isolation, option):\n        env_var = \"OVERRIDES_ENV_FOO\"\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"matrix\": {\"version\": {option: [{\"value\": \"run foo\", \"if\": [\"42\"], \"env\": [f\"{env_var}=bar\"]}]}}\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.42\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        with EnvVars({env_var: \"baz\"}):\n            assert project_config.envs == expected_envs\n            assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_table_conditional_with_env_var_missing(self, isolation, option):\n        env_var = \"OVERRIDES_ENV_FOO\"\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"matrix\": {\"version\": {option: [{\"value\": \"run foo\", \"if\": [\"42\"], \"env\": [f\"{env_var}=bar\"]}]}}\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.42\": {\"type\": \"virtual\", option: [\"run baz\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    def test_overrides_matrix_set_with_no_type_information(self, isolation):\n        env_var = \"OVERRIDES_ENV_FOO\"\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"matrix\": {\"version\": {\"bar\": {\"value\": [\"baz\"], \"if\": [\"42\"], \"env\": [f\"{env_var}=bar\"]}}}\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\"},\n            \"foo.42\": {\"type\": \"virtual\", \"bar\": [\"baz\"]},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        with EnvVars({env_var: \"bar\"}):\n            assert project_config.envs == expected_envs\n            assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    def test_overrides_matrix_set_with_no_type_information_not_table(self, isolation):\n        project_config = ProjectConfig(\n            isolation,\n            {\n                \"envs\": {\n                    \"foo\": {\n                        \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                        \"overrides\": {\"matrix\": {\"version\": {\"bar\": 9000}}},\n                    }\n                }\n            },\n            PluginManager(),\n        )\n        _ = project_config.envs\n\n        with pytest.raises(\n            ValueError,\n            match=(\n                \"Untyped option `tool.hatch.envs.foo.9000.overrides.matrix.version.bar` \"\n                \"must be defined as a table with a `value` key\"\n            ),\n        ):\n            project_config.finalize_env_overrides({})\n\n    @pytest.mark.parametrize(\"option\", ARRAY_OPTIONS)\n    def test_overrides_matrix_array_overwrite(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: [\"run baz\"],\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {f\"set-{option}\": [\"run foo\", {\"value\": \"run bar\"}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: [\"run foo\", \"run bar\"]},\n            \"foo.bar\": {\"type\": \"virtual\", option: [\"run baz\"]},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_string_create(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: \"baz\"}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_string_overwrite(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: \"test\",\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: \"baz\"}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\", option: \"test\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_table_create(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: {\"value\": \"baz\"}}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_table_override(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: \"test\",\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: {\"value\": \"baz\"}}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\", option: \"test\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_table_conditional(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: \"test\",\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: {\"value\": \"baz\", \"if\": [\"42\"]}}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"test\"},\n            \"foo.42\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\", option: \"test\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_array_table_create(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"baz\"}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_array_table_override(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: \"test\",\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"baz\"}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\", option: \"test\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_array_table_conditional(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: \"test\",\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"baz\", \"if\": [\"42\"]}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"test\"},\n            \"foo.42\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\", option: \"test\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_array_table_conditional_eager_string(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: \"test\",\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [\"baz\", {\"value\": \"foo\", \"if\": [\"42\"]}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.42\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\", option: \"test\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", STRING_OPTIONS)\n    def test_overrides_matrix_string_array_table_conditional_eager_table(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: \"test\",\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": \"baz\", \"if\": [\"42\"]}, \"foo\"]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: \"foo\"},\n            \"foo.42\": {\"type\": \"virtual\", option: \"baz\"},\n            \"foo.bar\": {\"type\": \"virtual\", option: \"test\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_boolean_create(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: True}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_boolean_overwrite(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: False,\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: True}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\", option: False},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_table_create(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: {\"value\": True}}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_table_override(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: False,\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: {\"value\": True}}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\", option: False},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_table_conditional(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: False,\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: {\"value\": True, \"if\": [\"42\"]}}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: False},\n            \"foo.42\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\", option: False},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_create(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": True}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_override(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: False,\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": True}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\", option: False},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_conditional(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: False,\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": True, \"if\": [\"42\"]}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: False},\n            \"foo.42\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\", option: False},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_conditional_eager_boolean(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: False,\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [True, {\"value\": False, \"if\": [\"42\"]}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: True},\n            \"foo.42\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\", option: False},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", BOOLEAN_OPTIONS)\n    def test_overrides_matrix_boolean_array_table_conditional_eager_table(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: False,\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: [{\"value\": True, \"if\": [\"42\"]}, False]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: False},\n            \"foo.42\": {\"type\": \"virtual\", option: True},\n            \"foo.bar\": {\"type\": \"virtual\", option: False},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    # We assert type coverage using matrix variable overrides, for the others just test one type\n    def test_overrides_platform_boolean_boolean_create(self, isolation, current_platform):\n        env_config = {\n            \"foo\": {\n                \"overrides\": {\"platform\": {\"bar\": {\"dependencies\": [\"baz\"]}, current_platform: {\"skip-install\": True}}}\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n        assert project_config.envs == expected_envs\n\n    def test_overrides_platform_boolean_boolean_overwrite(self, isolation, current_platform):\n        env_config = {\n            \"foo\": {\n                \"skip-install\": True,\n                \"overrides\": {\n                    \"platform\": {\"bar\": {\"dependencies\": [\"baz\"]}, current_platform: {\"skip-install\": False}}\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": False},\n        }\n\n        assert project_config.envs == expected_envs\n\n    def test_overrides_platform_boolean_table_create(self, isolation, current_platform):\n        env_config = {\n            \"foo\": {\n                \"overrides\": {\n                    \"platform\": {\n                        \"bar\": {\"dependencies\": [\"baz\"]},\n                        current_platform: {\"skip-install\": [{\"value\": True}]},\n                    }\n                }\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n        assert project_config.envs == expected_envs\n\n    def test_overrides_platform_boolean_table_overwrite(self, isolation, current_platform):\n        env_config = {\n            \"foo\": {\n                \"skip-install\": True,\n                \"overrides\": {\n                    \"platform\": {\n                        \"bar\": {\"dependencies\": [\"baz\"]},\n                        current_platform: {\"skip-install\": [{\"value\": False}]},\n                    }\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": False},\n        }\n\n        assert project_config.envs == expected_envs\n\n    def test_overrides_env_boolean_boolean_create(self, isolation):\n        env_var_exists = \"OVERRIDES_ENV_FOO\"\n        env_var_missing = \"OVERRIDES_ENV_BAR\"\n        env_config = {\n            \"foo\": {\n                \"overrides\": {\n                    \"env\": {env_var_missing: {\"dependencies\": [\"baz\"]}, env_var_exists: {\"skip-install\": True}}\n                }\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n        with EnvVars({env_var_exists: \"any\"}):\n            assert project_config.envs == expected_envs\n\n    def test_overrides_env_boolean_boolean_overwrite(self, isolation):\n        env_var_exists = \"OVERRIDES_ENV_FOO\"\n        env_var_missing = \"OVERRIDES_ENV_BAR\"\n        env_config = {\n            \"foo\": {\n                \"skip-install\": True,\n                \"overrides\": {\n                    \"env\": {env_var_missing: {\"dependencies\": [\"baz\"]}, env_var_exists: {\"skip-install\": False}}\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": False},\n        }\n\n        with EnvVars({env_var_exists: \"any\"}):\n            assert project_config.envs == expected_envs\n\n    def test_overrides_env_boolean_table_create(self, isolation):\n        env_var_exists = \"OVERRIDES_ENV_FOO\"\n        env_var_missing = \"OVERRIDES_ENV_BAR\"\n        env_config = {\n            \"foo\": {\n                \"overrides\": {\n                    \"env\": {\n                        env_var_missing: {\"dependencies\": [\"baz\"]},\n                        env_var_exists: {\"skip-install\": [{\"value\": True}]},\n                    }\n                }\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n        with EnvVars({env_var_exists: \"any\"}):\n            assert project_config.envs == expected_envs\n\n    def test_overrides_env_boolean_table_overwrite(self, isolation):\n        env_var_exists = \"OVERRIDES_ENV_FOO\"\n        env_var_missing = \"OVERRIDES_ENV_BAR\"\n        env_config = {\n            \"foo\": {\n                \"skip-install\": True,\n                \"overrides\": {\n                    \"env\": {\n                        env_var_missing: {\"dependencies\": [\"baz\"]},\n                        env_var_exists: {\"skip-install\": [{\"value\": False}]},\n                    }\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": False},\n        }\n\n        with EnvVars({env_var_exists: \"any\"}):\n            assert project_config.envs == expected_envs\n\n    def test_overrides_env_boolean_conditional(self, isolation):\n        env_var_exists = \"OVERRIDES_ENV_FOO\"\n        env_var_missing = \"OVERRIDES_ENV_BAR\"\n        env_config = {\n            \"foo\": {\n                \"overrides\": {\n                    \"env\": {\n                        env_var_missing: {\"dependencies\": [\"baz\"]},\n                        env_var_exists: {\"skip-install\": [{\"value\": True, \"if\": [\"foo\"]}]},\n                    }\n                }\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n        with EnvVars({env_var_exists: \"foo\"}):\n            assert project_config.envs == expected_envs\n\n    def test_overrides_name_boolean_boolean_create(self, isolation):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"name\": {\"bar$\": {\"skip-install\": True}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\"},\n            \"foo.bar\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n        assert project_config.envs == expected_envs\n\n    def test_overrides_name_boolean_boolean_overwrite(self, isolation):\n        env_config = {\n            \"foo\": {\n                \"skip-install\": True,\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"name\": {\"bar$\": {\"skip-install\": False}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", \"skip-install\": True},\n            \"foo.bar\": {\"type\": \"virtual\", \"skip-install\": False},\n        }\n\n        assert project_config.envs == expected_envs\n\n    def test_overrides_name_boolean_table_create(self, isolation):\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"name\": {\"bar$\": {\"skip-install\": [{\"value\": True}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\"},\n            \"foo.bar\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n        assert project_config.envs == expected_envs\n\n    def test_overrides_name_boolean_table_overwrite(self, isolation):\n        env_config = {\n            \"foo\": {\n                \"skip-install\": True,\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"name\": {\"bar$\": {\"skip-install\": [{\"value\": False}]}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", \"skip-install\": True},\n            \"foo.bar\": {\"type\": \"virtual\", \"skip-install\": False},\n        }\n\n        assert project_config.envs == expected_envs\n\n    # Tests for source precedence\n    def test_overrides_name_precedence_over_matrix(self, isolation):\n        env_config = {\n            \"foo\": {\n                \"skip-install\": False,\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"name\": {\"42$\": {\"skip-install\": False}},\n                    \"matrix\": {\"version\": {\"skip-install\": [{\"value\": True, \"if\": [\"42\"]}]}},\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", \"skip-install\": False},\n            \"foo.42\": {\"type\": \"virtual\", \"skip-install\": False},\n            \"foo.bar\": {\"type\": \"virtual\", \"skip-install\": False},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config, {\"skip-install\": False})\n\n    def test_overrides_matrix_precedence_over_platform(self, isolation, current_platform):\n        env_config = {\n            \"foo\": {\n                \"skip-install\": False,\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"platform\": {current_platform: {\"skip-install\": True}},\n                    \"matrix\": {\"version\": {\"skip-install\": [{\"value\": False, \"if\": [\"42\"]}]}},\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", \"skip-install\": True},\n            \"foo.42\": {\"type\": \"virtual\", \"skip-install\": False},\n            \"foo.bar\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config, {\"skip-install\": True})\n\n    def test_overrides_matrix_precedence_over_env(self, isolation):\n        env_var = \"OVERRIDES_ENV_FOO\"\n        env_config = {\n            \"foo\": {\n                \"skip-install\": False,\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"env\": {env_var: {\"skip-install\": True}},\n                    \"matrix\": {\"version\": {\"skip-install\": [{\"value\": False, \"if\": [\"42\"]}]}},\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", \"skip-install\": True},\n            \"foo.42\": {\"type\": \"virtual\", \"skip-install\": False},\n            \"foo.bar\": {\"type\": \"virtual\", \"skip-install\": True},\n        }\n\n        with EnvVars({env_var: \"any\"}):\n            assert project_config.envs == expected_envs\n            assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config, {\"skip-install\": True})\n\n    def test_overrides_env_precedence_over_platform(self, isolation, current_platform):\n        env_var = \"OVERRIDES_ENV_FOO\"\n        env_config = {\n            \"foo\": {\n                \"overrides\": {\n                    \"platform\": {current_platform: {\"skip-install\": True}},\n                    \"env\": {env_var: {\"skip-install\": [{\"value\": False, \"if\": [\"foo\"]}]}},\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo\": {\"type\": \"virtual\", \"skip-install\": False},\n        }\n\n        with EnvVars({env_var: \"foo\"}):\n            assert project_config.envs == expected_envs\n\n    # Test for options defined by environment plugins\n    def test_overrides_for_environment_plugins(self, isolation, current_platform):\n        env_var = \"OVERRIDES_ENV_FOO\"\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\n                    \"platform\": {current_platform: {\"foo\": True}},\n                    \"env\": {env_var: {\"bar\": [{\"value\": \"foobar\", \"if\": [\"foo\"]}]}},\n                    \"matrix\": {\"version\": {\"baz\": \"BAR=ok\"}},\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\"},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        with EnvVars({env_var: \"foo\"}):\n            assert project_config.envs == expected_envs\n            assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n        project_config.finalize_env_overrides({\"foo\": bool, \"bar\": str, \"baz\": dict})\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", \"foo\": True, \"bar\": \"foobar\", \"baz\": {\"BAR\": \"ok\"}},\n            \"foo.bar\": {\"type\": \"virtual\", \"foo\": True, \"bar\": \"foobar\"},\n        }\n\n        assert project_config.envs == expected_envs\n        assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    # Test environment collectors\n    def test_environment_collector_finalize_config(self, helpers, temp_dir):\n        file_path = temp_dir / DEFAULT_CUSTOM_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\n\n                class CustomHook(EnvironmentCollectorInterface):\n                    def finalize_config(self, config):\n                        config['default']['type'] = 'foo'\n                \"\"\"\n            )\n        )\n\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {\"type\": {\"value\": \"baz\", \"if\": [\"42\"]}}}},\n            }\n        }\n        project_config = ProjectConfig(\n            temp_dir, {\"envs\": env_config, \"env\": {\"collectors\": {\"custom\": {}}}}, PluginManager()\n        )\n\n        expected_envs = {\n            \"default\": {\"type\": \"foo\"},\n            \"foo.9000\": {\"type\": \"foo\"},\n            \"foo.42\": {\"type\": \"baz\"},\n            \"foo.bar\": {\"type\": \"foo\"},\n        }\n\n        with temp_dir.as_cwd():\n            assert project_config.envs == expected_envs\n            assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config, {\"type\": \"foo\"})\n\n    def test_environment_collector_finalize_environments(self, helpers, temp_dir):\n        file_path = temp_dir / DEFAULT_CUSTOM_SCRIPT\n        file_path.write_text(\n            helpers.dedent(\n                \"\"\"\n                from hatch.env.collectors.plugin.interface import EnvironmentCollectorInterface\n\n                class CustomHook(EnvironmentCollectorInterface):\n                    def finalize_environments(self, config):\n                        config['foo.42']['type'] = 'foo'\n                \"\"\"\n            )\n        )\n\n        env_config = {\n            \"foo\": {\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {\"type\": {\"value\": \"baz\", \"if\": [\"42\"]}}}},\n            }\n        }\n        project_config = ProjectConfig(\n            temp_dir, {\"envs\": env_config, \"env\": {\"collectors\": {\"custom\": {}}}}, PluginManager()\n        )\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\"},\n            \"foo.42\": {\"type\": \"foo\"},\n            \"foo.bar\": {\"type\": \"virtual\"},\n        }\n\n        with temp_dir.as_cwd():\n            assert project_config.envs == expected_envs\n            assert project_config.matrices[\"foo\"] == construct_matrix_data(\"foo\", env_config)\n\n    @pytest.mark.parametrize(\"option\", WORKSPACE_OPTIONS)\n    def test_overrides_matrix_workspace_invalid_type(self, isolation, option):\n        with pytest.raises(\n            TypeError,\n            match=f\"Field `tool.hatch.envs.foo.overrides.matrix.version.{option}` must be a table\",\n        ):\n            _ = ProjectConfig(\n                isolation,\n                {\n                    \"envs\": {\n                        \"foo\": {\"matrix\": [{\"version\": [\"9000\"]}], \"overrides\": {\"matrix\": {\"version\": {option: 9000}}}}\n                    }\n                },\n                PluginManager(),\n            ).envs\n\n    @pytest.mark.parametrize(\"option\", WORKSPACE_OPTIONS)\n    def test_overrides_matrix_workspace_members_append(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: {\"members\": [\"packages/core\"]},\n                \"matrix\": [{\"version\": [\"9000\"]}, {\"feature\": [\"bar\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: {\"members\": [\"packages/extra\"]}}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"members\": [\"packages/core\", \"packages/extra\"]}},\n            \"foo.bar\": {\"type\": \"virtual\", option: {\"members\": [\"packages/core\"]}},\n        }\n\n        assert project_config.envs == expected_envs\n\n    @pytest.mark.parametrize(\"option\", WORKSPACE_OPTIONS)\n    def test_overrides_matrix_workspace_members_conditional(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: {\"members\": [\"packages/core\"]},\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}],\n                \"overrides\": {\n                    \"matrix\": {\"version\": {option: {\"members\": [{\"value\": \"packages/special\", \"if\": [\"42\"]}]}}}\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"members\": [\"packages/core\"]}},\n            \"foo.42\": {\"type\": \"virtual\", option: {\"members\": [\"packages/core\", \"packages/special\"]}},\n        }\n\n        assert project_config.envs == expected_envs\n\n    @pytest.mark.parametrize(\"option\", WORKSPACE_OPTIONS)\n    def test_overrides_matrix_workspace_parallel(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: {\"members\": [\"packages/*\"], \"parallel\": True},\n                \"matrix\": [{\"version\": [\"9000\", \"42\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {option: {\"parallel\": {\"value\": False, \"if\": [\"42\"]}}}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"members\": [\"packages/*\"], \"parallel\": True}},\n            \"foo.42\": {\"type\": \"virtual\", option: {\"members\": [\"packages/*\"], \"parallel\": False}},\n        }\n\n        assert project_config.envs == expected_envs\n\n    @pytest.mark.parametrize(\"option\", WORKSPACE_OPTIONS)\n    def test_overrides_matrix_workspace_overwrite(self, isolation, option):\n        env_config = {\n            \"foo\": {\n                option: {\"members\": [\"packages/core\"], \"parallel\": True},\n                \"matrix\": [{\"version\": [\"9000\"]}],\n                \"overrides\": {\"matrix\": {\"version\": {f\"set-{option}\": {\"members\": [\"packages/new\"]}}}},\n            }\n        }\n        project_config = ProjectConfig(isolation, {\"envs\": env_config}, PluginManager())\n\n        expected_envs = {\n            \"default\": {\"type\": \"virtual\"},\n            \"foo.9000\": {\"type\": \"virtual\", option: {\"members\": [\"packages/new\"]}},\n        }\n\n        assert project_config.envs == expected_envs\n\n\nclass TestPublish:\n    def test_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.publish` must be a table\"):\n            _ = ProjectConfig(isolation, {\"publish\": 9000}).publish\n\n    def test_config_not_table(self, isolation):\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.publish.foo` must be a table\"):\n            _ = ProjectConfig(isolation, {\"publish\": {\"foo\": 9000}}).publish\n\n    def test_default(self, isolation):\n        project_config = ProjectConfig(isolation, {})\n\n        assert project_config.publish == project_config.publish == {}\n\n    def test_defined(self, isolation):\n        project_config = ProjectConfig(isolation, {\"publish\": {\"foo\": {\"bar\": \"baz\"}}})\n\n        assert project_config.publish == {\"foo\": {\"bar\": \"baz\"}}\n\n\nclass TestScripts:\n    def test_not_table(self, isolation):\n        config = {\"scripts\": 9000}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.scripts` must be a table\"):\n            _ = project_config.scripts\n\n    def test_name_contains_spaces(self, isolation):\n        config = {\"scripts\": {\"foo bar\": []}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(\n            ValueError, match=\"Script name `foo bar` in field `tool.hatch.scripts` must not contain spaces\"\n        ):\n            _ = project_config.scripts\n\n    def test_default(self, isolation):\n        project_config = ProjectConfig(isolation, {})\n\n        assert project_config.scripts == project_config.scripts == {}\n\n    def test_single_commands(self, isolation):\n        config = {\"scripts\": {\"foo\": \"command1\", \"bar\": \"command2\"}}\n        project_config = ProjectConfig(isolation, config)\n\n        assert project_config.scripts == {\"foo\": [\"command1\"], \"bar\": [\"command2\"]}\n\n    def test_multiple_commands(self, isolation):\n        config = {\"scripts\": {\"foo\": \"command1\", \"bar\": [\"command3\", \"command2\"]}}\n        project_config = ProjectConfig(isolation, config)\n\n        assert project_config.scripts == {\"foo\": [\"command1\"], \"bar\": [\"command3\", \"command2\"]}\n\n    def test_multiple_commands_not_string(self, isolation):\n        config = {\"scripts\": {\"foo\": [9000]}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Command #1 in field `tool.hatch.scripts.foo` must be a string\"):\n            _ = project_config.scripts\n\n    def test_config_invalid_type(self, isolation):\n        config = {\"scripts\": {\"foo\": 9000}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.scripts.foo` must be a string or an array of strings\"):\n            _ = project_config.scripts\n\n    def test_command_expansion_basic(self, isolation):\n        config = {\"scripts\": {\"foo\": \"command1\", \"bar\": [\"command3\", \"foo\"]}}\n        project_config = ProjectConfig(isolation, config)\n\n        assert project_config.scripts == {\"foo\": [\"command1\"], \"bar\": [\"command3\", \"command1\"]}\n\n    def test_command_expansion_multiple_nested(self, isolation):\n        config = {\n            \"scripts\": {\n                \"foo\": \"command3\",\n                \"baz\": [\"command5\", \"bar\", \"foo\", \"command1\"],\n                \"bar\": [\"command4\", \"foo\", \"command2\"],\n            }\n        }\n        project_config = ProjectConfig(isolation, config)\n\n        assert project_config.scripts == {\n            \"foo\": [\"command3\"],\n            \"baz\": [\"command5\", \"command4\", \"command3\", \"command2\", \"command3\", \"command1\"],\n            \"bar\": [\"command4\", \"command3\", \"command2\"],\n        }\n\n    def test_command_expansion_multiple_nested_ignore_exit_code(self, isolation):\n        config = {\n            \"scripts\": {\n                \"foo\": \"command3\",\n                \"baz\": [\"command5\", \"- bar\", \"foo\", \"command1\"],\n                \"bar\": [\"command4\", \"- foo\", \"command2\"],\n            }\n        }\n        project_config = ProjectConfig(isolation, config)\n\n        assert project_config.scripts == {\n            \"foo\": [\"command3\"],\n            \"baz\": [\"command5\", \"- command4\", \"- command3\", \"- command2\", \"command3\", \"command1\"],\n            \"bar\": [\"command4\", \"- command3\", \"command2\"],\n        }\n\n    def test_command_expansion_modification(self, isolation):\n        config = {\n            \"scripts\": {\n                \"foo\": \"command3\",\n                \"baz\": [\"command5\", \"bar world\", \"foo\", \"command1\"],\n                \"bar\": [\"command4\", \"foo hello\", \"command2\"],\n            }\n        }\n        project_config = ProjectConfig(isolation, config)\n\n        assert project_config.scripts == {\n            \"foo\": [\"command3\"],\n            \"baz\": [\"command5\", \"command4 world\", \"command3 hello world\", \"command2 world\", \"command3\", \"command1\"],\n            \"bar\": [\"command4\", \"command3 hello\", \"command2\"],\n        }\n\n    def test_command_expansion_circular_inheritance(self, isolation):\n        config = {\"scripts\": {\"foo\": \"bar\", \"bar\": \"foo\"}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(\n            ValueError, match=\"Circular expansion detected for field `tool.hatch.scripts`: foo -> bar -> foo\"\n        ):\n            _ = project_config.scripts\n\n\nclass TestBuild:\n    def test_not_table(self, isolation):\n        config = {\"build\": 9000}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build` must be a table\"):\n            _ = project_config.build\n\n    def test_targets_not_table(self, isolation):\n        config = {\"build\": {\"targets\": 9000}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets` must be a table\"):\n            _ = project_config.build.target(\"foo\")\n\n    def test_target_not_table(self, isolation):\n        config = {\"build\": {\"targets\": {\"foo\": 9000}}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo` must be a table\"):\n            _ = project_config.build.target(\"foo\")\n\n    def test_directory_global_not_table(self, isolation):\n        config = {\"build\": {\"directory\": 9000}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.directory` must be a string\"):\n            _ = project_config.build.target(\"foo\").directory\n\n    def test_directory_not_table(self, isolation):\n        config = {\"build\": {\"targets\": {\"foo\": {\"directory\": 9000}}}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.directory` must be a string\"):\n            _ = project_config.build.target(\"foo\").directory\n\n    def test_directory_default(self, isolation):\n        project_config = ProjectConfig(isolation, {})\n\n        assert project_config.build.target(\"foo\").directory == DEFAULT_BUILD_DIRECTORY\n\n    def test_directory_global_correct(self, isolation):\n        config = {\"build\": {\"directory\": \"bar\"}}\n        project_config = ProjectConfig(isolation, config)\n\n        assert project_config.build.target(\"foo\").directory == \"bar\"\n\n    def test_directory_target_override(self, isolation):\n        config = {\"build\": {\"directory\": \"bar\", \"targets\": {\"foo\": {\"directory\": \"baz\"}}}}\n        project_config = ProjectConfig(isolation, config)\n\n        assert project_config.build.target(\"foo\").directory == \"baz\"\n\n    def test_dependencies_global_not_array(self, isolation):\n        config = {\"build\": {\"dependencies\": 9000}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.dependencies` must be an array\"):\n            _ = project_config.build.target(\"foo\").dependencies\n\n    def test_dependencies_global_entry_not_string(self, isolation):\n        config = {\"build\": {\"dependencies\": [9000]}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Dependency #1 in field `tool.hatch.build.dependencies` must be a string\"):\n            _ = project_config.build.target(\"foo\").dependencies\n\n    def test_dependencies_not_array(self, isolation):\n        config = {\"build\": {\"targets\": {\"foo\": {\"dependencies\": 9000}}}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.dependencies` must be an array\"):\n            _ = project_config.build.target(\"foo\").dependencies\n\n    def test_dependencies_entry_not_string(self, isolation):\n        config = {\"build\": {\"targets\": {\"foo\": {\"dependencies\": [9000]}}}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(\n            TypeError, match=\"Dependency #1 in field `tool.hatch.build.targets.foo.dependencies` must be a string\"\n        ):\n            _ = project_config.build.target(\"foo\").dependencies\n\n    def test_dependencies_target_merge(self, isolation):\n        config = {\"build\": {\"dependencies\": [\"baz\"], \"targets\": {\"foo\": {\"dependencies\": [\"bar\"]}}}}\n        project_config = ProjectConfig(isolation, config)\n\n        assert project_config.build.target(\"foo\").dependencies == [\"baz\", \"bar\"]\n\n    def test_hooks_global_not_table(self, isolation):\n        config = {\"build\": {\"hooks\": 9000}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.hooks` must be a table\"):\n            _ = project_config.build.target(\"foo\").hook_config\n\n    def test_hook_config_global_not_table(self, isolation):\n        config = {\"build\": {\"hooks\": {\"hook1\": 9000}}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.hooks.hook1` must be a table\"):\n            _ = project_config.build.target(\"foo\").hook_config\n\n    def test_hooks_not_table(self, isolation):\n        config = {\"build\": {\"targets\": {\"foo\": {\"hooks\": 9000}}}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.hooks` must be a table\"):\n            _ = project_config.build.target(\"foo\").hook_config\n\n    def test_hook_config_not_table(self, isolation):\n        config = {\"build\": {\"targets\": {\"foo\": {\"hooks\": {\"hook1\": 9000}}}}}\n        project_config = ProjectConfig(isolation, config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.build.targets.foo.hooks.hook1` must be a table\"):\n            _ = project_config.build.target(\"foo\").hook_config\n\n    def test_hook_config_target_override(self, isolation):\n        config = {\n            \"build\": {\n                \"hooks\": {\n                    \"hook1\": {\"foo\": \"bar\", \"enable-by-default\": False},\n                    \"hook2\": {\"foo\": \"bar\"},\n                    \"hook3\": {\"foo\": \"bar\"},\n                    \"hook4\": {\"foo\": \"bar\"},\n                },\n                \"targets\": {\n                    \"foo\": {\n                        \"hooks\": {\n                            \"hook3\": {\"bar\": \"foo\"},\n                            \"hook4\": {\"bar\": \"foo\", \"enable-by-default\": False},\n                            \"hook5\": {\"bar\": \"foo\"},\n                            \"hook6\": {\"bar\": \"foo\", \"enable-by-default\": False},\n                        },\n                    },\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, config)\n\n        hook_config = project_config.build.target(\"foo\").hook_config\n        assert hook_config == {\n            \"hook2\": {\"foo\": \"bar\"},\n            \"hook3\": {\"bar\": \"foo\"},\n            \"hook5\": {\"bar\": \"foo\"},\n        }\n\n    def test_hook_config_all_enabled(self, isolation):\n        config = {\n            \"build\": {\n                \"hooks\": {\n                    \"hook1\": {\"foo\": \"bar\", \"enable-by-default\": False},\n                    \"hook2\": {\"foo\": \"bar\"},\n                    \"hook3\": {\"foo\": \"bar\"},\n                    \"hook4\": {\"foo\": \"bar\"},\n                },\n                \"targets\": {\n                    \"foo\": {\n                        \"hooks\": {\n                            \"hook3\": {\"bar\": \"foo\"},\n                            \"hook4\": {\"bar\": \"foo\", \"enable-by-default\": False},\n                            \"hook5\": {\"bar\": \"foo\"},\n                            \"hook6\": {\"bar\": \"foo\", \"enable-by-default\": False},\n                        },\n                    },\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, config)\n\n        with EnvVars({BuildEnvVars.HOOKS_ENABLE: \"true\"}):\n            hook_config = project_config.build.target(\"foo\").hook_config\n\n        assert hook_config == {\n            \"hook1\": {\"foo\": \"bar\", \"enable-by-default\": False},\n            \"hook2\": {\"foo\": \"bar\"},\n            \"hook3\": {\"bar\": \"foo\"},\n            \"hook4\": {\"bar\": \"foo\", \"enable-by-default\": False},\n            \"hook5\": {\"bar\": \"foo\"},\n            \"hook6\": {\"bar\": \"foo\", \"enable-by-default\": False},\n        }\n\n    def test_hook_config_all_disabled(self, isolation):\n        config = {\n            \"build\": {\n                \"hooks\": {\n                    \"hook1\": {\"foo\": \"bar\", \"enable-by-default\": False},\n                    \"hook2\": {\"foo\": \"bar\"},\n                    \"hook3\": {\"foo\": \"bar\"},\n                    \"hook4\": {\"foo\": \"bar\"},\n                },\n                \"targets\": {\n                    \"foo\": {\n                        \"hooks\": {\n                            \"hook3\": {\"bar\": \"foo\"},\n                            \"hook4\": {\"bar\": \"foo\", \"enable-by-default\": False},\n                            \"hook5\": {\"bar\": \"foo\"},\n                            \"hook6\": {\"bar\": \"foo\", \"enable-by-default\": False},\n                        },\n                    },\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, config)\n\n        with EnvVars({BuildEnvVars.NO_HOOKS: \"true\"}):\n            hook_config = project_config.build.target(\"foo\").hook_config\n\n        assert not hook_config\n\n    def test_hook_config_specific_enabled(self, isolation):\n        config = {\n            \"build\": {\n                \"hooks\": {\n                    \"hook1\": {\"foo\": \"bar\", \"enable-by-default\": False},\n                    \"hook2\": {\"foo\": \"bar\"},\n                    \"hook3\": {\"foo\": \"bar\"},\n                    \"hook4\": {\"foo\": \"bar\"},\n                },\n                \"targets\": {\n                    \"foo\": {\n                        \"hooks\": {\n                            \"hook3\": {\"bar\": \"foo\"},\n                            \"hook4\": {\"bar\": \"foo\", \"enable-by-default\": False},\n                            \"hook5\": {\"bar\": \"foo\"},\n                            \"hook6\": {\"bar\": \"foo\", \"enable-by-default\": False},\n                        },\n                    },\n                },\n            }\n        }\n        project_config = ProjectConfig(isolation, config)\n\n        with EnvVars({f\"{BuildEnvVars.HOOK_ENABLE_PREFIX}HOOK6\": \"true\"}):\n            hook_config = project_config.build.target(\"foo\").hook_config\n\n        assert hook_config == {\n            \"hook2\": {\"foo\": \"bar\"},\n            \"hook3\": {\"bar\": \"foo\"},\n            \"hook5\": {\"bar\": \"foo\"},\n            \"hook6\": {\"bar\": \"foo\", \"enable-by-default\": False},\n        }\n"
  },
  {
    "path": "tests/project/test_core.py",
    "content": "import pytest\n\nfrom hatch.project.core import Project\n\n\nclass TestFindProjectRoot:\n    def test_no_project(self, temp_dir):\n        project = Project(temp_dir)\n        assert project.find_project_root() is None\n\n    @pytest.mark.parametrize(\"file_name\", [\"pyproject.toml\", \"setup.py\"])\n    def test_direct(self, temp_dir, file_name):\n        project = Project(temp_dir)\n\n        project_file = temp_dir / file_name\n        project_file.touch()\n\n        assert project.find_project_root() == temp_dir\n\n    @pytest.mark.parametrize(\"file_name\", [\"pyproject.toml\", \"setup.py\"])\n    def test_recurse(self, temp_dir, file_name):\n        project = Project(temp_dir)\n\n        project_file = temp_dir / file_name\n        project_file.touch()\n\n        path = temp_dir / \"test\"\n        path.mkdir()\n\n        assert project.find_project_root() == temp_dir\n\n    @pytest.mark.parametrize(\"file_name\", [\"pyproject.toml\", \"setup.py\"])\n    def test_no_path(self, temp_dir, file_name):\n        project_file = temp_dir / file_name\n        project_file.touch()\n\n        path = temp_dir / \"test\"\n        project = Project(path)\n\n        assert project.find_project_root() == temp_dir\n\n\nclass TestLoadProjectFromConfig:\n    def test_no_project_no_project_dirs(self, config_file):\n        assert Project.from_config(config_file.model, \"foo\") is None\n\n    def test_project_empty_string(self, config_file, temp_dir):\n        config_file.model.projects[\"\"] = str(temp_dir)\n        assert Project.from_config(config_file.model, \"\") is None\n\n    def test_project_basic_string(self, config_file, temp_dir):\n        config_file.model.projects = {\"foo\": str(temp_dir)}\n        project = Project.from_config(config_file.model, \"foo\")\n        assert project.chosen_name == \"foo\"\n        assert project.location == temp_dir\n\n    def test_project_complex(self, config_file, temp_dir):\n        config_file.model.projects = {\"foo\": {\"location\": str(temp_dir)}}\n        project = Project.from_config(config_file.model, \"foo\")\n        assert project.chosen_name == \"foo\"\n        assert project.location == temp_dir\n\n    def test_project_complex_null_location(self, config_file):\n        config_file.model.projects = {\"foo\": {\"location\": \"\"}}\n        assert Project.from_config(config_file.model, \"foo\") is None\n\n    def test_project_dirs(self, config_file, temp_dir):\n        path = temp_dir / \"foo\"\n        path.mkdir()\n        config_file.model.dirs.project = [str(temp_dir)]\n        project = Project.from_config(config_file.model, \"foo\")\n        assert project.chosen_name == \"foo\"\n        assert project.location == path\n\n    def test_project_dirs_null_dir(self, config_file):\n        config_file.model.dirs.project = [\"\"]\n        assert Project.from_config(config_file.model, \"foo\") is None\n\n    def test_project_dirs_not_directory(self, config_file, temp_dir):\n        path = temp_dir / \"foo\"\n        path.touch()\n        config_file.model.dirs.project = [str(temp_dir)]\n        assert Project.from_config(config_file.model, \"foo\") is None\n\n\nclass TestChosenName:\n    def test_selected(self, temp_dir):\n        project = Project(temp_dir, name=\"foo\")\n        assert project.chosen_name == \"foo\"\n\n    def test_cwd(self, temp_dir):\n        project = Project(temp_dir)\n        assert project.chosen_name is None\n\n\nclass TestLocation:\n    def test_no_project(self, temp_dir):\n        project = Project(temp_dir)\n        assert project.location == temp_dir\n        assert project.root is None\n\n    @pytest.mark.parametrize(\"file_name\", [\"pyproject.toml\", \"setup.py\"])\n    def test_project(self, temp_dir, file_name):\n        project_file = temp_dir / file_name\n        project_file.touch()\n\n        project = Project(temp_dir)\n        assert project.location == temp_dir\n        assert project.root == temp_dir\n\n\nclass TestRawConfig:\n    def test_missing(self, temp_dir):\n        project = Project(temp_dir)\n        project.find_project_root()\n\n        assert project.raw_config == {\"project\": {\"name\": temp_dir.name}}\n\n    def test_exists(self, temp_dir):\n        project_file = temp_dir / \"pyproject.toml\"\n        project_file.touch()\n        project = Project(temp_dir)\n        project.find_project_root()\n\n        config = {\"project\": {\"name\": \"foo\"}, \"bar\": \"baz\"}\n        project.save_config(config)\n\n        assert project.raw_config == config\n\n    def test_exists_without_project_table(self, temp_dir):\n        project_file = temp_dir / \"pyproject.toml\"\n        project_file.touch()\n        project = Project(temp_dir)\n        project.find_project_root()\n\n        assert project.raw_config == {\"project\": {\"name\": temp_dir.name}}\n\n\nclass TestEnsureCWD:\n    def test_location_is_file(self, temp_dir, mocker):\n        script_path = temp_dir / \"script.py\"\n        script_path.touch()\n        project = Project(script_path)\n        project.find_project_root()\n\n        with temp_dir.as_cwd():\n            mocker.patch(\"hatch.utils.fs.Path.as_cwd\", side_effect=Exception)\n            with project.ensure_cwd() as cwd:\n                assert cwd == temp_dir\n\n    def test_cwd_is_location(self, temp_dir, mocker):\n        project_file = temp_dir / \"pyproject.toml\"\n        project_file.touch()\n        project = Project(temp_dir)\n        project.find_project_root()\n\n        with temp_dir.as_cwd():\n            mocker.patch(\"hatch.utils.fs.Path.as_cwd\", side_effect=Exception)\n            with project.ensure_cwd() as cwd:\n                assert cwd == temp_dir\n\n    def test_cwd_inside_location(self, temp_dir, mocker):\n        project_file = temp_dir / \"pyproject.toml\"\n        project_file.touch()\n        project = Project(temp_dir)\n        project.find_project_root()\n\n        subdir = temp_dir / \"subdir\"\n        subdir.mkdir()\n\n        with subdir.as_cwd():\n            mocker.patch(\"hatch.utils.fs.Path.as_cwd\", side_effect=Exception)\n            with project.ensure_cwd() as cwd:\n                assert cwd == subdir\n\n    def test_cwd_outside_location(self, temp_dir):\n        subdir = temp_dir / \"subdir\"\n        subdir.mkdir()\n        project_file = subdir / \"pyproject.toml\"\n        project_file.touch()\n        project = Project(subdir)\n        project.find_project_root()\n\n        with temp_dir.as_cwd(), project.ensure_cwd() as cwd:\n            assert cwd == subdir\n"
  },
  {
    "path": "tests/project/test_frontend.py",
    "content": "import json\nimport sys\n\nimport pytest\n\nfrom hatch.env.plugin.interface import EnvironmentInterface\nfrom hatch.project.core import Project\nfrom hatchling.builders.constants import EDITABLES_REQUIREMENT\nfrom hatchling.metadata.spec import project_metadata_from_core_metadata\n\nBACKENDS = [(\"hatchling\", \"hatchling.build\"), (\"flit-core\", \"flit_core.buildapi\")]\n\n\nclass MockEnvironment(EnvironmentInterface):  # no cov\n    PLUGIN_NAME = \"mock\"\n\n    def find(self):\n        pass\n\n    def create(self):\n        pass\n\n    def remove(self):\n        pass\n\n    def exists(self):\n        pass\n\n    def install_project(self):\n        pass\n\n    def install_project_dev_mode(self):\n        pass\n\n    def dependencies_in_sync(self):\n        pass\n\n    def sync_dependencies(self):\n        pass\n\n\nclass TestPrepareMetadata:\n    @pytest.mark.parametrize(\n        (\"backend_pkg\", \"backend_api\"),\n        [pytest.param(backend_pkg, backend_api, id=backend_pkg) for backend_pkg, backend_api in BACKENDS],\n    )\n    def test_wheel(self, temp_dir, temp_dir_data, platform, global_application, backend_pkg, backend_api):\n        project_dir = temp_dir / \"project\"\n        project_dir.mkdir()\n        (project_dir / \"pyproject.toml\").write_text(\n            f\"\"\"\\\n[build-system]\nrequires = [\"{backend_pkg}\"]\nbuild-backend = \"{backend_api}\"\n\n[project]\nname = \"foo\"\nversion = \"9000.42\"\ndescription = \"text\"\n\"\"\"\n        )\n\n        package_dir = project_dir / \"foo\"\n        package_dir.mkdir()\n        (package_dir / \"__init__.py\").touch()\n\n        project = Project(project_dir)\n        project.build_env = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            temp_dir_data,\n            temp_dir_data,\n            platform,\n            0,\n            global_application,\n        )\n\n        output_dir = temp_dir / \"output\"\n        output_dir.mkdir()\n        script = project.build_frontend.scripts.prepare_metadata(\n            output_dir=str(output_dir), project_root=str(project_dir)\n        )\n        platform.check_command([sys.executable, \"-c\", script])\n        work_dir = output_dir / \"work\"\n        output = json.loads((output_dir / \"output.json\").read_text())\n        metadata_file = work_dir / output[\"return_val\"] / \"METADATA\"\n\n        assert project_metadata_from_core_metadata(metadata_file.read_text()) == {\n            \"name\": \"foo\",\n            \"version\": \"9000.42\",\n            \"description\": \"text\",\n        }\n\n    @pytest.mark.parametrize(\n        (\"backend_pkg\", \"backend_api\"),\n        [pytest.param(backend_pkg, backend_api, id=backend_pkg) for backend_pkg, backend_api in BACKENDS],\n    )\n    def test_editable(self, temp_dir, temp_dir_data, platform, global_application, backend_pkg, backend_api):\n        project_dir = temp_dir / \"project\"\n        project_dir.mkdir()\n        (project_dir / \"pyproject.toml\").write_text(\n            f\"\"\"\\\n[build-system]\nrequires = [\"{backend_pkg}\"]\nbuild-backend = \"{backend_api}\"\n\n[project]\nname = \"foo\"\nversion = \"9000.42\"\ndescription = \"text\"\n\"\"\"\n        )\n\n        package_dir = project_dir / \"foo\"\n        package_dir.mkdir()\n        (package_dir / \"__init__.py\").touch()\n\n        project = Project(project_dir)\n        project.build_env = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            temp_dir_data,\n            temp_dir_data,\n            platform,\n            0,\n            global_application,\n        )\n\n        output_dir = temp_dir / \"output\"\n        output_dir.mkdir()\n        script = project.build_frontend.scripts.prepare_metadata(\n            output_dir=str(output_dir), project_root=str(project_dir), editable=True\n        )\n        platform.check_command([sys.executable, \"-c\", script])\n        work_dir = output_dir / \"work\"\n        output = json.loads((output_dir / \"output.json\").read_text())\n        metadata_file = work_dir / output[\"return_val\"] / \"METADATA\"\n\n        assert project_metadata_from_core_metadata(metadata_file.read_text()) == {\n            \"name\": \"foo\",\n            \"version\": \"9000.42\",\n            \"description\": \"text\",\n        }\n\n\nclass TestBuildWheel:\n    @pytest.mark.parametrize(\n        (\"backend_pkg\", \"backend_api\"),\n        [pytest.param(backend_pkg, backend_api, id=backend_pkg) for backend_pkg, backend_api in BACKENDS],\n    )\n    def test_standard(self, temp_dir, temp_dir_data, platform, global_application, backend_pkg, backend_api):\n        project_dir = temp_dir / \"project\"\n        project_dir.mkdir()\n        (project_dir / \"pyproject.toml\").write_text(\n            f\"\"\"\\\n[build-system]\nrequires = [\"{backend_pkg}\"]\nbuild-backend = \"{backend_api}\"\n\n[project]\nname = \"foo\"\nversion = \"9000.42\"\ndescription = \"text\"\n\"\"\"\n        )\n\n        package_dir = project_dir / \"foo\"\n        package_dir.mkdir()\n        (package_dir / \"__init__.py\").touch()\n\n        project = Project(project_dir)\n        project.build_env = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            temp_dir_data,\n            temp_dir_data,\n            platform,\n            0,\n            global_application,\n        )\n\n        output_dir = temp_dir / \"output\"\n        output_dir.mkdir()\n        script = project.build_frontend.scripts.build_wheel(output_dir=str(output_dir), project_root=str(project_dir))\n        platform.check_command([sys.executable, \"-c\", script])\n        work_dir = output_dir / \"work\"\n        output = json.loads((output_dir / \"output.json\").read_text())\n        wheel_path = work_dir / output[\"return_val\"]\n\n        assert wheel_path.is_file()\n        assert wheel_path.name.startswith(\"foo-9000.42-\")\n        assert wheel_path.name.endswith(\".whl\")\n\n    @pytest.mark.parametrize(\n        (\"backend_pkg\", \"backend_api\"),\n        [pytest.param(backend_pkg, backend_api, id=backend_pkg) for backend_pkg, backend_api in BACKENDS],\n    )\n    def test_editable(self, temp_dir, temp_dir_data, platform, global_application, backend_pkg, backend_api):\n        project_dir = temp_dir / \"project\"\n        project_dir.mkdir()\n        (project_dir / \"pyproject.toml\").write_text(\n            f\"\"\"\\\n[build-system]\nrequires = [\"{backend_pkg}\"]\nbuild-backend = \"{backend_api}\"\n\n[project]\nname = \"foo\"\nversion = \"9000.42\"\ndescription = \"text\"\n\"\"\"\n        )\n\n        package_dir = project_dir / \"foo\"\n        package_dir.mkdir()\n        (package_dir / \"__init__.py\").touch()\n\n        project = Project(project_dir)\n        project.build_env = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            temp_dir_data,\n            temp_dir_data,\n            platform,\n            0,\n            global_application,\n        )\n\n        output_dir = temp_dir / \"output\"\n        output_dir.mkdir()\n        script = project.build_frontend.scripts.build_wheel(\n            output_dir=str(output_dir), project_root=str(project_dir), editable=True\n        )\n        platform.check_command([sys.executable, \"-c\", script])\n        work_dir = output_dir / \"work\"\n        output = json.loads((output_dir / \"output.json\").read_text())\n        wheel_path = work_dir / output[\"return_val\"]\n\n        assert wheel_path.is_file()\n        assert wheel_path.name.startswith(\"foo-9000.42-\")\n        assert wheel_path.name.endswith(\".whl\")\n\n\nclass TestSourceDistribution:\n    @pytest.mark.parametrize(\n        (\"backend_pkg\", \"backend_api\"),\n        [pytest.param(backend_pkg, backend_api, id=backend_pkg) for backend_pkg, backend_api in BACKENDS],\n    )\n    def test_standard(self, temp_dir, temp_dir_data, platform, global_application, backend_pkg, backend_api):\n        project_dir = temp_dir / \"project\"\n        project_dir.mkdir()\n        (project_dir / \"pyproject.toml\").write_text(\n            f\"\"\"\\\n[build-system]\nrequires = [\"{backend_pkg}\"]\nbuild-backend = \"{backend_api}\"\n\n[project]\nname = \"foo\"\nversion = \"9000.42\"\ndescription = \"text\"\n\"\"\"\n        )\n\n        package_dir = project_dir / \"foo\"\n        package_dir.mkdir()\n        (package_dir / \"__init__.py\").touch()\n\n        project = Project(project_dir)\n        project.build_env = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            temp_dir_data,\n            temp_dir_data,\n            platform,\n            0,\n            global_application,\n        )\n\n        output_dir = temp_dir / \"output\"\n        output_dir.mkdir()\n        script = project.build_frontend.scripts.build_sdist(output_dir=str(output_dir), project_root=str(project_dir))\n        platform.check_command([sys.executable, \"-c\", script])\n        work_dir = output_dir / \"work\"\n        output = json.loads((output_dir / \"output.json\").read_text())\n        sdist_path = work_dir / output[\"return_val\"]\n\n        assert sdist_path.is_file()\n        assert sdist_path.name == \"foo-9000.42.tar.gz\"\n\n\nclass TestGetRequires:\n    @pytest.mark.parametrize(\n        (\"backend_pkg\", \"backend_api\"),\n        [pytest.param(backend_pkg, backend_api, id=backend_pkg) for backend_pkg, backend_api in BACKENDS],\n    )\n    @pytest.mark.parametrize(\"build\", [\"sdist\", \"wheel\", \"editable\"])\n    def test_default(self, temp_dir, temp_dir_data, platform, global_application, backend_pkg, backend_api, build):\n        project_dir = temp_dir / \"project\"\n        project_dir.mkdir()\n        (project_dir / \"pyproject.toml\").write_text(\n            f\"\"\"\\\n[build-system]\nrequires = [\"{backend_pkg}\"]\nbuild-backend = \"{backend_api}\"\n\n[project]\nname = \"foo\"\nversion = \"9000.42\"\ndescription = \"text\"\n\"\"\"\n        )\n\n        package_dir = project_dir / \"foo\"\n        package_dir.mkdir()\n        (package_dir / \"__init__.py\").touch()\n\n        project = Project(project_dir)\n        project.build_env = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            temp_dir_data,\n            temp_dir_data,\n            platform,\n            0,\n            global_application,\n        )\n\n        output_dir = temp_dir / \"output\"\n        output_dir.mkdir()\n        script = project.build_frontend.scripts.get_requires(\n            output_dir=str(output_dir), project_root=str(project_dir), build=build\n        )\n        platform.check_command([sys.executable, \"-c\", script])\n        output = json.loads((output_dir / \"output.json\").read_text())\n\n        assert output[\"return_val\"] == (\n            [EDITABLES_REQUIREMENT] if backend_pkg == \"hatchling\" and build == \"editable\" else []\n        )\n\n\nclass TestHatchGetBuildDeps:\n    def test_default(self, temp_dir, temp_dir_data, platform, global_application):\n        project_dir = temp_dir / \"project\"\n        project_dir.mkdir()\n        (project_dir / \"pyproject.toml\").write_text(\n            \"\"\"\\\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"foo\"\nversion = \"9000.42\"\n\"\"\"\n        )\n\n        package_dir = project_dir / \"foo\"\n        package_dir.mkdir()\n        (package_dir / \"__init__.py\").touch()\n\n        project = Project(project_dir)\n        project.build_env = MockEnvironment(\n            temp_dir,\n            project.metadata,\n            \"default\",\n            project.config.envs[\"default\"],\n            {},\n            temp_dir_data,\n            temp_dir_data,\n            platform,\n            0,\n            global_application,\n        )\n\n        output_dir = temp_dir / \"output\"\n        output_dir.mkdir()\n        script = project.build_frontend.hatch.scripts.get_build_deps(\n            output_dir=str(output_dir), project_root=str(project_dir), targets=[\"sdist\", \"wheel\"]\n        )\n        platform.check_command([sys.executable, \"-c\", script])\n        output = json.loads((output_dir / \"output.json\").read_text())\n\n        assert output == []\n"
  },
  {
    "path": "tests/project/test_utils.py",
    "content": "import pytest\n\nfrom hatch.project.utils import parse_inline_script_metadata\n\n\nclass TestParseInlineScriptMetadata:\n    def test_no_metadata(self):\n        assert parse_inline_script_metadata(\"\") is None\n\n    def test_too_many_blocks(self, helpers):\n        script = helpers.dedent(\n            \"\"\"\n            # /// script\n            # dependencies = [\"foo\"]\n            # ///\n\n            # /// script\n            # dependencies = [\"foo\"]\n            # ///\n            \"\"\"\n        )\n        with pytest.raises(ValueError, match=\"^Multiple inline metadata blocks found for type: script$\"):\n            parse_inline_script_metadata(script)\n\n    def test_correct(self, helpers):\n        script = helpers.dedent(\n            \"\"\"\n            # /// script\n            # embedded-csharp = '''\n            # /// <summary>\n            # /// text\n            # ///\n            # /// </summary>\n            # public class MyClass { }\n            # '''\n            # ///\n            \"\"\"\n        )\n        assert parse_inline_script_metadata(script) == {\n            \"embedded-csharp\": helpers.dedent(\n                \"\"\"\n                /// <summary>\n                /// text\n                ///\n                /// </summary>\n                public class MyClass { }\n                \"\"\"\n            ),\n        }\n"
  },
  {
    "path": "tests/publish/__init__.py",
    "content": ""
  },
  {
    "path": "tests/publish/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "tests/publish/plugin/test_interface.py",
    "content": "import pytest\n\nfrom hatch.publish.plugin.interface import PublisherInterface\n\n\nclass MockPublisher(PublisherInterface):  # no cov\n    PLUGIN_NAME = \"mock\"\n\n    def publish(self, artifacts, options):\n        pass\n\n\nclass TestDisable:\n    def test_default(self, isolation):\n        project_config = {}\n        plugin_config = {}\n        publisher = MockPublisher(None, isolation, None, project_config, plugin_config)\n\n        assert publisher.disable is publisher.disable is False\n\n    def test_project_config(self, isolation):\n        project_config = {\"disable\": True}\n        plugin_config = {}\n        publisher = MockPublisher(None, isolation, None, project_config, plugin_config)\n\n        assert publisher.disable is True\n\n    def test_project_config_not_boolean(self, isolation):\n        project_config = {\"disable\": 9000}\n        plugin_config = {}\n        publisher = MockPublisher(None, isolation, None, project_config, plugin_config)\n\n        with pytest.raises(TypeError, match=\"Field `tool.hatch.publish.mock.disable` must be a boolean\"):\n            _ = publisher.disable\n\n    def test_plugin_config(self, isolation):\n        project_config = {}\n        plugin_config = {\"disable\": True}\n        publisher = MockPublisher(None, isolation, None, project_config, plugin_config)\n\n        assert publisher.disable is True\n\n    def test_plugin_config_not_boolean(self, isolation):\n        project_config = {}\n        plugin_config = {\"disable\": 9000}\n        publisher = MockPublisher(None, isolation, None, project_config, plugin_config)\n\n        with pytest.raises(TypeError, match=\"Global plugin configuration `publish.mock.disable` must be a boolean\"):\n            _ = publisher.disable\n\n    def test_project_config_overrides_plugin_config(self, isolation):\n        project_config = {\"disable\": False}\n        plugin_config = {\"disable\": True}\n        publisher = MockPublisher(None, isolation, None, project_config, plugin_config)\n\n        assert publisher.disable is False\n"
  },
  {
    "path": "tests/python/__init__.py",
    "content": ""
  },
  {
    "path": "tests/python/test_core.py",
    "content": "import json\n\nimport pytest\n\nfrom hatch.config.constants import PythonEnvVars\nfrom hatch.python.core import InstalledDistribution, PythonManager\nfrom hatch.python.distributions import ORDERED_DISTRIBUTIONS\nfrom hatch.python.resolve import custom_env_var, get_distribution\nfrom hatch.utils.structures import EnvVars\n\n\n@pytest.mark.parametrize(\"name\", ORDERED_DISTRIBUTIONS)\ndef test_custom_source(platform, current_arch, name):\n    if platform.name == \"macos\" and current_arch == \"arm64\" and name == \"3.7\":\n        pytest.skip(\"No macOS 3.7 distribution for ARM\")\n\n    dist = get_distribution(name)\n    with EnvVars({custom_env_var(PythonEnvVars.CUSTOM_SOURCE_PREFIX, name): \"foo\"}):\n        assert dist.source == \"foo\"\n\n\n@pytest.mark.requires_internet\n@pytest.mark.parametrize(\"name\", ORDERED_DISTRIBUTIONS)\ndef test_installation(temp_dir, platform, current_arch, name):\n    if platform.name == \"macos\" and current_arch == \"arm64\" and name == \"3.7\":\n        pytest.skip(\"No macOS 3.7 distribution for ARM\")\n\n    # Ensure the source and any parent directories get created\n    manager = PythonManager(temp_dir / \"foo\" / \"bar\")\n    dist = manager.install(name)\n\n    python_path = dist.python_path\n    assert python_path.is_file()\n\n    output = platform.check_command_output([python_path, \"-c\", \"import sys;print(sys.executable)\"]).strip()\n    assert output == str(python_path)\n\n    major_minor = name.replace(\"pypy\", \"\")\n\n    output = platform.check_command_output([python_path, \"--version\"]).strip()\n\n    assert output.startswith(f\"Python {major_minor}.\")\n    if name.startswith(\"pypy\"):\n        assert \"PyPy\" in output\n\n\nclass TestGetInstalled:\n    def test_source_does_not_exist(self, temp_dir):\n        manager = PythonManager(temp_dir / \"foo\")\n\n        assert manager.get_installed() == {}\n\n    def test_not_a_directory(self, temp_dir):\n        manager = PythonManager(temp_dir)\n\n        dist = get_distribution(\"3.10\")\n        path = temp_dir / dist.name\n        path.touch()\n\n        assert manager.get_installed() == {}\n\n    def test_no_metadata_file(self, temp_dir):\n        manager = PythonManager(temp_dir)\n\n        dist = get_distribution(\"3.10\")\n        path = temp_dir / dist.name\n        path.mkdir()\n\n        assert manager.get_installed() == {}\n\n    def test_no_python_path(self, temp_dir):\n        manager = PythonManager(temp_dir)\n\n        dist = get_distribution(\"3.10\")\n        path = temp_dir / dist.name\n        path.mkdir()\n        metadata_file = path / InstalledDistribution.metadata_filename()\n        metadata_file.write_text(json.dumps({\"source\": dist.source}))\n\n        assert manager.get_installed() == {}\n\n    def test_order(self, temp_dir, compatible_python_distributions):\n        manager = PythonManager(temp_dir)\n\n        for name in compatible_python_distributions:\n            dist = get_distribution(name)\n            path = temp_dir / dist.name\n            path.mkdir()\n            metadata_file = path / InstalledDistribution.metadata_filename()\n            metadata_file.write_text(json.dumps({\"source\": dist.source}))\n            python_path = path / dist.python_path\n            python_path.parent.ensure_dir_exists()\n            python_path.touch()\n\n        assert tuple(manager.get_installed()) == compatible_python_distributions\n"
  },
  {
    "path": "tests/python/test_resolve.py",
    "content": "import sys\nfrom platform import machine\n\nimport pytest\n\nfrom hatch.config.constants import PythonEnvVars\nfrom hatch.errors import PythonDistributionResolutionError, PythonDistributionUnknownError\nfrom hatch.python.resolve import custom_env_var, get_distribution\nfrom hatch.utils.structures import EnvVars\n\n\nclass TestErrors:\n    def test_unknown_distribution(self):\n        with pytest.raises(PythonDistributionUnknownError, match=\"Unknown distribution: foo\"):\n            get_distribution(\"foo\")\n\n    @pytest.mark.skipif(\n        not (sys.platform == \"linux\" and machine().lower() == \"x86_64\"),\n        reason=\"No variants for this platform and architecture combination\",\n    )\n    def test_resolution_error(self, platform):\n        with (\n            EnvVars({\"HATCH_PYTHON_VARIANT_CPU\": \"foo\"}),\n            pytest.raises(\n                PythonDistributionResolutionError,\n                match=f\"Could not find a default source for name='3.11' system='{platform.name}' arch=\",\n            ),\n        ):\n            get_distribution(\"3.11\")\n\n\nclass TestDistributionVersions:\n    def test_cpython_standalone(self):\n        url = \"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz\"\n        dist = get_distribution(\"3.11\", url)\n        version = dist.version\n\n        assert version.epoch == 0\n        assert version.base_version == \"3.11.3\"\n\n    def test_cpython_standalone_custom(self):\n        name = \"3.11\"\n        dist = get_distribution(name)\n        with EnvVars({custom_env_var(PythonEnvVars.CUSTOM_VERSION_PREFIX, name): \"9000.42\"}):\n            version = dist.version\n\n        assert version.epoch == 100\n        assert \".\".join(map(str, version.release)) == \"9000.42\"\n\n    def test_pypy(self):\n        url = \"https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2\"\n        dist = get_distribution(\"pypy3.10\", url)\n        version = dist.version\n\n        assert version.epoch == 0\n        assert version.base_version == \"7.3.12\"\n\n    def test_pypy_custom(self):\n        name = \"pypy3.10\"\n        dist = get_distribution(name)\n        with EnvVars({custom_env_var(PythonEnvVars.CUSTOM_VERSION_PREFIX, name): \"9000.42\"}):\n            version = dist.version\n\n        assert version.epoch == 100\n        assert \".\".join(map(str, version.release)) == \"9000.42\"\n\n\nclass TestDistributionPaths:\n    def test_cpython_standalone_custom(self):\n        name = \"3.11\"\n        dist = get_distribution(name)\n        with EnvVars({custom_env_var(PythonEnvVars.CUSTOM_PATH_PREFIX, name): \"foo/bar/python\"}):\n            assert dist.python_path == \"foo/bar/python\"\n\n    def test_pypy_custom(self):\n        name = \"pypy3.10\"\n        dist = get_distribution(name)\n        with EnvVars({custom_env_var(PythonEnvVars.CUSTOM_PATH_PREFIX, name): \"foo/bar/python\"}):\n            assert dist.python_path == \"foo/bar/python\"\n\n\n@pytest.mark.requires_linux\nclass TestVariantCPU:\n    def test_legacy_option(self, current_arch):\n        variant = \"v4\"\n        with EnvVars({\"HATCH_PYTHON_VARIANT_LINUX\": variant}):\n            dist = get_distribution(\"3.12\")\n\n        if current_arch != \"x86_64\":\n            assert variant not in dist.source\n        else:\n            assert variant in dist.source\n\n    @pytest.mark.parametrize(\"variant\", [\"v1\", \"v2\", \"v3\", \"v4\"])\n    def test_compatibility(self, variant, current_arch):\n        with EnvVars({\"HATCH_PYTHON_VARIANT_CPU\": variant}):\n            dist = get_distribution(\"3.12\")\n\n        if current_arch != \"x86_64\" or variant == \"v1\":\n            assert variant not in dist.source\n        else:\n            assert variant in dist.source\n\n    @pytest.mark.skipif(\n        machine().lower() != \"x86_64\",\n        reason=\"No variants for this platform and architecture combination\",\n    )\n    @pytest.mark.parametrize(\n        (\"variant\", \"flags\"),\n        [\n            pytest.param(\n                \"v1\",\n                # Just guessing here...\n                \"flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni dtes64 monitor ds_cpl smx est tm2\",\n                id=\"v1\",\n            ),\n            pytest.param(\n                \"v2\",\n                # Intel Core i7-860\n                \"flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni dtes64 monitor ds_cpl smx est tm2 ssse3 cx16 xtpr pdcm sse4_1 sse4_2 popcnt lahf_lm pti ssbd ibrs ibpb stibp dtherm ida flush_l1d\",\n                id=\"v2\",\n            ),\n            pytest.param(\n                \"v3\",\n                # Intel Core i5-5300U\n                \"flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap intel_pt xsaveopt dtherm ida arat pln pts vnmi md_clear flush_l1d\",\n                id=\"v3\",\n            ),\n            pytest.param(\n                \"v4\",\n                # Just guessing here...\n                \"flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap intel_pt xsaveopt dtherm ida arat pln pts vnmi md_clear flush_l1d avx512f avx512bw avx512cd avx512dq avx512vl\",\n                id=\"v4\",\n            ),\n        ],\n    )\n    def test_guess_variant(self, tmp_path, mocker, variant, flags):\n        cpuinfo = tmp_path / \"cpuinfo\"\n        cpuinfo.write_text(flags)\n\n        original_open = open\n\n        def mock_open(path, *args, **kwargs):\n            if str(path) == \"/proc/cpuinfo\":\n                return original_open(str(cpuinfo), *args, **kwargs)\n            return original_open(path, *args, **kwargs)\n\n        mocker.patch(\"builtins.open\", side_effect=mock_open)\n\n        with EnvVars({\"HATCH_PYTHON_VARIANT_CPU\": \"\"}):\n            dist = get_distribution(\"3.12\")\n            if variant == \"v1\":\n                for v in (\"v1\", \"v2\", \"v3\", \"v4\"):\n                    assert v not in dist.source\n            else:\n                assert variant in dist.source\n\n\nclass TestVariantGIL:\n    def test_compatible(self):\n        with EnvVars({\"HATCH_PYTHON_VARIANT_GIL\": \"freethreaded\"}):\n            dist = get_distribution(\"3.13\")\n\n        assert \"freethreaded\" in dist.source\n\n    def test_incompatible(self, platform):\n        with (\n            EnvVars({\"HATCH_PYTHON_VARIANT_GIL\": \"freethreaded\"}),\n            pytest.raises(\n                PythonDistributionResolutionError,\n                match=f\"Could not find a default source for name='3.12' system='{platform.name}' arch=\",\n            ),\n        ):\n            get_distribution(\"3.12\")\n"
  },
  {
    "path": "tests/utils/__init__.py",
    "content": ""
  },
  {
    "path": "tests/utils/test_auth.py",
    "content": "from hatch.publish.auth import AuthenticationCredentials\nfrom hatch.utils.fs import Path\n\n\ndef test_pypirc(tmp_path, mocker):\n    # Create a fake home directory\n    fake_home = tmp_path / \"home\"\n    fake_home.mkdir()\n\n    # Create .pypirc in the fake home\n    pypirc = fake_home / \".pypirc\"\n    pypirc.write_text(\"\"\"\\\n[other]\nusername: guido\npassword: gat\nrepository: https://kaashandel.nl/\n\n[pypi]\nusername: guido\npassword: sprscrt\n\"\"\")\n\n    mocker.patch.object(Path, \"home\", return_value=fake_home)\n    credentials = AuthenticationCredentials(\n        app=None, cache_dir=Path(\"/none\"), options={}, repo=\"\", repo_config={\"url\": \"\"}\n    )\n    assert credentials.username == \"guido\"\n    assert credentials.password == \"sprscrt\"\n\n    credentials = AuthenticationCredentials(\n        app=None,\n        cache_dir=Path(\"/none\"),\n        options={},\n        repo=\"other\",\n        repo_config={\"url\": \"\"},\n    )\n    assert credentials.username == \"guido\"\n    assert credentials.password == \"gat\"\n\n    credentials = AuthenticationCredentials(\n        app=None,\n        cache_dir=Path(\"/none\"),\n        options={},\n        repo=\"arbitrary\",\n        repo_config={\"url\": \"https://kaashandel.nl/\"},\n    )\n    assert credentials.username == \"guido\"\n    assert credentials.password == \"gat\"\n"
  },
  {
    "path": "tests/utils/test_fs.py",
    "content": "import os\nimport pathlib\n\nfrom hatch.utils.fs import Path, temp_chdir, temp_directory\n\n\nclass TestPath:\n    def test_type(self):\n        expected_type = type(pathlib.Path())\n\n        assert isinstance(Path(), expected_type)\n        assert issubclass(Path, expected_type)\n\n    def test_resolve_relative_non_existent(self, tmp_path):\n        origin = os.getcwd()\n        os.chdir(tmp_path)\n        try:\n            expected_representation = os.path.join(tmp_path, \"foo\")\n            assert str(Path(\"foo\").resolve()) == expected_representation\n            assert str(Path(\".\", \"foo\").resolve()) == expected_representation\n        finally:\n            os.chdir(origin)\n\n    def test_ensure_dir_exists(self, tmp_path):\n        path = Path(tmp_path, \"foo\")\n        path.ensure_dir_exists()\n\n        assert path.is_dir()\n\n    def test_ensure_parent_dir_exists(self, tmp_path):\n        path = Path(tmp_path, \"foo\", \"bar\")\n        path.ensure_parent_dir_exists()\n\n        assert path.parent.is_dir()\n        assert not path.is_dir()\n\n    def test_as_cwd(self, tmp_path):\n        origin = os.getcwd()\n\n        with Path(tmp_path).as_cwd():\n            assert os.getcwd() == str(tmp_path)\n\n        assert os.getcwd() == origin\n\n    def test_as_cwd_env_vars(self, tmp_path):\n        env_var = str(self).encode().hex().upper()\n        origin = os.getcwd()\n\n        with Path(tmp_path).as_cwd(env_vars={env_var: \"foo\"}):\n            assert os.getcwd() == str(tmp_path)\n            assert os.environ.get(env_var) == \"foo\"\n\n        assert os.getcwd() == origin\n        assert env_var not in os.environ\n\n    def test_remove_file(self, tmp_path):\n        path = Path(tmp_path, \"foo\")\n        path.touch()\n\n        assert path.is_file()\n        path.remove()\n        assert not path.exists()\n\n    def test_remove_directory(self, tmp_path):\n        path = Path(tmp_path, \"foo\")\n        path.mkdir()\n\n        assert path.is_dir()\n        path.remove()\n        assert not path.exists()\n\n    def test_remove_non_existent(self, tmp_path):\n        path = Path(tmp_path, \"foo\")\n\n        assert not path.exists()\n        path.remove()\n        assert not path.exists()\n\n    def test_temp_hide_file(self, tmp_path):\n        path = Path(tmp_path, \"foo\")\n        path.touch()\n\n        with path.temp_hide() as temp_path:\n            assert not path.exists()\n            assert temp_path.is_file()\n\n        assert path.is_file()\n        assert not temp_path.exists()\n\n    def test_temp_hide_dir(self, tmp_path):\n        path = Path(tmp_path, \"foo\")\n        path.mkdir()\n\n        with path.temp_hide() as temp_path:\n            assert not path.exists()\n            assert temp_path.is_dir()\n\n        assert path.is_dir()\n        assert not temp_path.exists()\n\n    def test_temp_hide_non_existent(self, tmp_path):\n        path = Path(tmp_path, \"foo\")\n\n        with path.temp_hide() as temp_path:\n            assert not path.exists()\n            assert not temp_path.exists()\n\n        assert not path.exists()\n        assert not temp_path.exists()\n\n\ndef test_temp_directory():\n    with temp_directory() as temp_dir:\n        assert isinstance(temp_dir, Path)\n        assert temp_dir.is_dir()\n\n    assert not temp_dir.exists()\n\n\ndef test_temp_chdir():\n    origin = os.getcwd()\n\n    with temp_chdir() as temp_dir:\n        assert isinstance(temp_dir, Path)\n        assert temp_dir.is_dir()\n        assert os.getcwd() == str(temp_dir)\n\n    assert os.getcwd() == origin\n    assert not temp_dir.exists()\n"
  },
  {
    "path": "tests/utils/test_platform.py",
    "content": "import os\nimport stat\n\nimport pytest\n\nfrom hatch.utils.fs import Path\nfrom hatch.utils.platform import Platform\nfrom hatch.utils.structures import EnvVars\n\n\n@pytest.mark.requires_windows\nclass TestWindows:\n    def test_tag(self):\n        assert Platform().windows is True\n\n    def test_default_shell(self):\n        assert Platform().default_shell == os.environ.get(\"COMSPEC\", \"cmd\")\n\n    def test_format_for_subprocess_list(self):\n        assert Platform().format_for_subprocess([\"foo\", \"bar\"], shell=False) == [\"foo\", \"bar\"]\n\n    def test_format_for_subprocess_list_shell(self):\n        assert Platform().format_for_subprocess([\"foo\", \"bar\"], shell=True) == [\"foo\", \"bar\"]\n\n    def test_format_for_subprocess_string(self):\n        assert Platform().format_for_subprocess(\"foo bar\", shell=False) == \"foo bar\"\n\n    def test_format_for_subprocess_string_shell(self):\n        assert Platform().format_for_subprocess(\"foo bar\", shell=True) == \"foo bar\"\n\n    def test_home(self):\n        platform = Platform()\n\n        assert platform.home == platform.home == Path(os.path.expanduser(\"~\"))\n\n    def test_populate_default_popen_kwargs_executable(self):\n        platform = Platform()\n\n        kwargs = {}\n        platform.populate_default_popen_kwargs(kwargs, shell=True)\n        assert not kwargs\n\n        kwargs[\"executable\"] = \"foo\"\n        platform.populate_default_popen_kwargs(kwargs, shell=True)\n        assert kwargs[\"executable\"] == \"foo\"\n\n\n@pytest.mark.requires_macos\nclass TestMacOS:\n    def test_tag(self):\n        assert Platform().macos is True\n\n    def test_default_shell(self):\n        assert Platform().default_shell == os.environ.get(\"SHELL\", \"bash\")\n\n    def test_format_for_subprocess_list(self):\n        assert Platform().format_for_subprocess([\"foo\", \"bar\"], shell=False) == [\"foo\", \"bar\"]\n\n    def test_format_for_subprocess_list_shell(self):\n        assert Platform().format_for_subprocess([\"foo\", \"bar\"], shell=True) == [\"foo\", \"bar\"]\n\n    def test_format_for_subprocess_string(self):\n        assert Platform().format_for_subprocess(\"foo bar\", shell=False) == [\"foo\", \"bar\"]\n\n    def test_format_for_subprocess_string_shell(self):\n        assert Platform().format_for_subprocess(\"foo bar\", shell=True) == \"foo bar\"\n\n    def test_home(self):\n        platform = Platform()\n\n        assert platform.home == platform.home == Path(os.path.expanduser(\"~\"))\n\n    def test_populate_default_popen_kwargs_executable(self, temp_dir):\n        new_path = f\"{os.environ.get('PATH', '')}{os.pathsep}{temp_dir}\".strip(os.pathsep)\n        executable = temp_dir / \"sh\"\n        executable.touch()\n        executable.chmod(executable.stat().st_mode | stat.S_IEXEC)\n\n        kwargs = {}\n\n        platform = Platform()\n        with EnvVars({\"DYLD_FOO\": \"bar\", \"PATH\": new_path}):\n            platform.populate_default_popen_kwargs(kwargs, shell=True)\n\n        assert kwargs[\"executable\"] == str(executable)\n\n\n@pytest.mark.requires_linux\nclass TestLinux:\n    def test_tag(self):\n        assert Platform().linux is True\n\n    def test_default_shell(self):\n        assert Platform().default_shell == os.environ.get(\"SHELL\", \"bash\")\n\n    def test_format_for_subprocess_list(self):\n        assert Platform().format_for_subprocess([\"foo\", \"bar\"], shell=False) == [\"foo\", \"bar\"]\n\n    def test_format_for_subprocess_list_shell(self):\n        assert Platform().format_for_subprocess([\"foo\", \"bar\"], shell=True) == [\"foo\", \"bar\"]\n\n    def test_format_for_subprocess_string(self):\n        assert Platform().format_for_subprocess(\"foo bar\", shell=False) == [\"foo\", \"bar\"]\n\n    def test_format_for_subprocess_string_shell(self):\n        assert Platform().format_for_subprocess(\"foo bar\", shell=True) == \"foo bar\"\n\n    def test_home(self):\n        platform = Platform()\n\n        assert platform.home == platform.home == Path(os.path.expanduser(\"~\"))\n\n    def test_populate_default_popen_kwargs_executable(self):\n        platform = Platform()\n\n        kwargs = {}\n        platform.populate_default_popen_kwargs(kwargs, shell=True)\n        assert not kwargs\n\n        kwargs[\"executable\"] = \"foo\"\n        platform.populate_default_popen_kwargs(kwargs, shell=True)\n        assert kwargs[\"executable\"] == \"foo\"\n"
  },
  {
    "path": "tests/utils/test_runner.py",
    "content": "from __future__ import annotations\n\nimport pytest\n\nfrom hatch.utils.runner import parse_matrix_variables, select_environments\n\n\nclass TestParseMatrixVariables:\n    def test_empty(self):\n        assert parse_matrix_variables(()) == {}\n\n    def test_single(self):\n        assert parse_matrix_variables((\"py=3.9\",)) == {\"python\": {\"3.9\"}}\n\n    def test_multiple(self):\n        assert parse_matrix_variables((\"py=3.9\", \"version=42\")) == {\"python\": {\"3.9\"}, \"version\": {\"42\"}}\n\n    def test_no_values(self):\n        assert parse_matrix_variables((\"py=3.9\", \"version\")) == {\"python\": {\"3.9\"}, \"version\": set()}\n\n    def test_duplicate(self):\n        with pytest.raises(ValueError):  # noqa: PT011\n            parse_matrix_variables((\"py=3.9\", \"py=3.10\"))\n\n\nclass TestSelectEnvironments:\n    def test_empty(self):\n        assert select_environments({}, {}, {}) == []\n\n    def test_no_filters(self):\n        environments = {\n            \"a\": {\"python\": \"3.9\", \"feature\": \"foo\"},\n            \"b\": {\"python\": \"3.10\", \"feature\": \"bar\"},\n            \"c\": {\"python\": \"3.11\", \"feature\": \"baz\"},\n            \"d\": {\"python\": \"3.11\", \"feature\": \"foo\", \"version\": \"42\"},\n        }\n        assert select_environments(environments, {}, {}) == [\"a\", \"b\", \"c\", \"d\"]\n\n    def test_include_any(self):\n        environments = {\n            \"a\": {\"python\": \"3.9\", \"feature\": \"foo\"},\n            \"b\": {\"python\": \"3.10\", \"feature\": \"bar\"},\n            \"c\": {\"python\": \"3.11\", \"feature\": \"baz\"},\n            \"d\": {\"python\": \"3.11\", \"feature\": \"foo\", \"version\": \"42\"},\n        }\n        assert select_environments(environments, {\"version\": set()}, {}) == [\"d\"]\n\n    def test_include_specific(self):\n        environments = {\n            \"a\": {\"python\": \"3.9\", \"feature\": \"foo\"},\n            \"b\": {\"python\": \"3.10\", \"feature\": \"bar\"},\n            \"c\": {\"python\": \"3.11\", \"feature\": \"baz\"},\n            \"d\": {\"python\": \"3.11\", \"feature\": \"foo\", \"version\": \"42\"},\n        }\n        assert select_environments(environments, {\"python\": {\"3.11\"}}, {}) == [\"c\", \"d\"]\n\n    def test_include_multiple(self):\n        environments = {\n            \"a\": {\"python\": \"3.9\", \"feature\": \"foo\"},\n            \"b\": {\"python\": \"3.10\", \"feature\": \"bar\"},\n            \"c\": {\"python\": \"3.11\", \"feature\": \"baz\"},\n            \"d\": {\"python\": \"3.11\", \"feature\": \"foo\", \"version\": \"42\"},\n        }\n        assert select_environments(environments, {\"python\": {\"3.11\"}, \"feature\": {\"baz\"}}, {}) == [\"c\"]\n\n    def test_exclude_any(self):\n        environments = {\n            \"a\": {\"python\": \"3.9\", \"feature\": \"foo\"},\n            \"b\": {\"python\": \"3.10\", \"feature\": \"bar\"},\n            \"c\": {\"python\": \"3.11\", \"feature\": \"baz\"},\n            \"d\": {\"python\": \"3.11\", \"feature\": \"foo\", \"version\": \"42\"},\n        }\n        assert select_environments(environments, {}, {\"version\": set()}) == [\"a\", \"b\", \"c\"]\n\n    def test_exclude_specific(self):\n        environments = {\n            \"a\": {\"python\": \"3.9\", \"feature\": \"foo\"},\n            \"b\": {\"python\": \"3.10\", \"feature\": \"bar\"},\n            \"c\": {\"python\": \"3.11\", \"feature\": \"baz\"},\n            \"d\": {\"python\": \"3.11\", \"feature\": \"foo\", \"version\": \"42\"},\n        }\n        assert select_environments(environments, {}, {\"python\": {\"3.11\"}}) == [\"a\", \"b\"]\n\n    def test_exclude_multiple(self):\n        environments = {\n            \"a\": {\"python\": \"3.9\", \"feature\": \"foo\"},\n            \"b\": {\"python\": \"3.10\", \"feature\": \"bar\"},\n            \"c\": {\"python\": \"3.11\", \"feature\": \"baz\"},\n            \"d\": {\"python\": \"3.11\", \"feature\": \"foo\", \"version\": \"42\"},\n        }\n        assert select_environments(environments, {}, {\"python\": {\"3.11\"}, \"feature\": {\"baz\"}}) == [\"a\", \"b\"]\n\n    def test_include_and_exclude(self):\n        environments = {\n            \"a\": {\"python\": \"3.9\", \"feature\": \"foo\"},\n            \"b\": {\"python\": \"3.10\", \"feature\": \"bar\"},\n            \"c\": {\"python\": \"3.11\", \"feature\": \"baz\"},\n            \"d\": {\"python\": \"3.11\", \"feature\": \"foo\", \"version\": \"42\"},\n        }\n        assert select_environments(environments, {\"python\": {\"3.11\"}}, {\"feature\": {\"baz\"}}) == [\"d\"]\n"
  },
  {
    "path": "tests/utils/test_structures.py",
    "content": "import os\n\nfrom hatch.utils.structures import EnvVars\n\n\ndef get_random_name():\n    return os.urandom(16).hex().upper()\n\n\nclass TestEnvVars:\n    def test_restoration(self):\n        num_env_vars = len(os.environ)\n        with EnvVars():\n            os.environ.clear()\n\n        assert len(os.environ) == num_env_vars\n\n    def test_set(self):\n        env_var = get_random_name()\n\n        with EnvVars({env_var: \"foo\"}):\n            assert os.environ.get(env_var) == \"foo\"\n\n        assert env_var not in os.environ\n\n    def test_include(self):\n        env_var = get_random_name()\n        pattern = f\"{env_var[:-2]}*\"\n\n        with EnvVars({env_var: \"foo\"}):\n            num_env_vars = len(os.environ)\n\n            with EnvVars(include=[get_random_name(), pattern]):\n                assert len(os.environ) == 1\n                assert os.environ.get(env_var) == \"foo\"\n\n            assert len(os.environ) == num_env_vars\n\n    def test_exclude(self):\n        env_var = get_random_name()\n        pattern = f\"{env_var[:-2]}*\"\n\n        with EnvVars({env_var: \"foo\"}):\n            with EnvVars(exclude=[get_random_name(), pattern]):\n                assert env_var not in os.environ\n\n            assert os.environ.get(env_var) == \"foo\"\n\n    def test_precedence(self):\n        env_var1 = get_random_name()\n        env_var2 = get_random_name()\n        pattern = f\"{env_var1[:-2]}*\"\n\n        with EnvVars({env_var1: \"foo\"}):\n            num_env_vars = len(os.environ)\n\n            with EnvVars({env_var2: \"bar\"}, include=[pattern], exclude=[pattern, env_var2]):\n                assert len(os.environ) == 1\n                assert os.environ.get(env_var2) == \"bar\"\n\n            assert len(os.environ) == num_env_vars\n"
  },
  {
    "path": "tests/venv/__init__.py",
    "content": ""
  },
  {
    "path": "tests/venv/test_core.py",
    "content": "import json\nimport os\nimport re\nimport sys\n\nimport pytest\n\nfrom hatch.utils.structures import EnvVars\nfrom hatch.venv.core import VirtualEnv\n\n\ndef test_initialization_does_not_create(temp_dir, platform):\n    venv_dir = temp_dir / \"venv\"\n    venv = VirtualEnv(venv_dir, platform)\n\n    assert not venv.exists()\n\n    with pytest.raises(OSError, match=f\"Unable to locate executables directory within: {re.escape(str(venv_dir))}\"):\n        _ = venv.executables_directory\n\n\ndef test_remove_non_existent_no_error(temp_dir, platform):\n    venv_dir = temp_dir / \"venv\"\n    venv = VirtualEnv(venv_dir, platform)\n    venv.remove()\n\n\ndef test_creation(temp_dir, platform):\n    venv_dir = temp_dir / \"venv\"\n    venv = VirtualEnv(venv_dir, platform)\n    venv.create(sys.executable)\n\n    assert venv_dir.is_dir()\n    assert venv.exists()\n\n\ndef test_executables_directory(temp_dir, platform):\n    venv_dir = temp_dir / \"venv\"\n    venv = VirtualEnv(venv_dir, platform)\n    venv.create(sys.executable)\n\n    assert venv.executables_directory.is_dir()\n    for entry in venv.executables_directory.iterdir():\n        if entry.name.startswith(\"py\"):\n            break\n    else:  # no cov\n        msg = \"Unable to locate Python executable\"\n        raise AssertionError(msg)\n\n\ndef test_activation(temp_dir, platform):\n    venv_dir = temp_dir / \"venv\"\n    venv = VirtualEnv(venv_dir, platform)\n    venv.create(sys.executable)\n\n    with EnvVars(exclude=VirtualEnv.IGNORED_ENV_VARS):\n        os.environ[\"PATH\"] = str(temp_dir)\n        os.environ[\"VIRTUAL_ENV\"] = \"foo\"\n        for env_var in VirtualEnv.IGNORED_ENV_VARS:\n            os.environ[env_var] = \"foo\"\n\n        venv.activate()\n\n        assert os.environ[\"PATH\"] == f\"{venv.executables_directory}{os.pathsep}{temp_dir}\"\n        assert os.environ[\"VIRTUAL_ENV\"] == str(venv_dir)\n        for env_var in VirtualEnv.IGNORED_ENV_VARS:\n            assert env_var not in os.environ\n\n        venv.deactivate()\n\n        assert os.environ[\"PATH\"] == str(temp_dir)\n        assert os.environ[\"VIRTUAL_ENV\"] == \"foo\"\n        for env_var in VirtualEnv.IGNORED_ENV_VARS:\n            assert os.environ[env_var] == \"foo\"\n\n\ndef test_activation_path_env_var_missing(temp_dir, platform):\n    venv_dir = temp_dir / \"venv\"\n    venv = VirtualEnv(venv_dir, platform)\n    venv.create(sys.executable)\n\n    with EnvVars(exclude=VirtualEnv.IGNORED_ENV_VARS):\n        os.environ.pop(\"PATH\", None)\n        os.environ[\"VIRTUAL_ENV\"] = \"foo\"\n        for env_var in VirtualEnv.IGNORED_ENV_VARS:\n            os.environ[env_var] = \"foo\"\n\n        venv.activate()\n\n        assert os.environ[\"PATH\"] == f\"{venv.executables_directory}{os.pathsep}{os.defpath}\"\n        assert os.environ[\"VIRTUAL_ENV\"] == str(venv_dir)\n        for env_var in VirtualEnv.IGNORED_ENV_VARS:\n            assert env_var not in os.environ\n\n        venv.deactivate()\n\n        assert \"PATH\" not in os.environ\n        assert os.environ[\"VIRTUAL_ENV\"] == \"foo\"\n        for env_var in VirtualEnv.IGNORED_ENV_VARS:\n            assert os.environ[env_var] == \"foo\"\n\n\ndef test_context_manager(temp_dir, platform, extract_installed_requirements):\n    venv_dir = temp_dir / \"venv\"\n    venv = VirtualEnv(venv_dir, platform)\n    venv.create(sys.executable)\n\n    with EnvVars(exclude=VirtualEnv.IGNORED_ENV_VARS):\n        os.environ[\"PATH\"] = str(temp_dir)\n        os.environ[\"VIRTUAL_ENV\"] = \"foo\"\n        for env_var in VirtualEnv.IGNORED_ENV_VARS:\n            os.environ[env_var] = \"foo\"\n\n        with venv:\n            assert os.environ[\"PATH\"] == f\"{venv.executables_directory}{os.pathsep}{temp_dir}\"\n            assert os.environ[\"VIRTUAL_ENV\"] == str(venv_dir)\n            for env_var in VirtualEnv.IGNORED_ENV_VARS:\n                assert env_var not in os.environ\n\n            # Run here while we have cleanup\n            output = platform.run_command([\"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\"utf-8\")\n            assert not extract_installed_requirements(output.splitlines())\n\n        assert os.environ[\"PATH\"] == str(temp_dir)\n        assert os.environ[\"VIRTUAL_ENV\"] == \"foo\"\n        for env_var in VirtualEnv.IGNORED_ENV_VARS:\n            assert os.environ[env_var] == \"foo\"\n\n\ndef test_creation_allow_system_packages(temp_dir, platform, extract_installed_requirements):\n    venv_dir = temp_dir / \"venv\"\n    venv = VirtualEnv(venv_dir, platform)\n    venv.create(sys.executable, allow_system_packages=True)\n\n    with venv:\n        output = platform.run_command([\"pip\", \"freeze\"], check=True, capture_output=True).stdout.decode(\"utf-8\")\n\n        assert len(extract_installed_requirements(output.splitlines())) > 0\n\n\ndef test_python_data(temp_dir, platform):\n    venv_dir = temp_dir / \"venv\"\n    venv = VirtualEnv(venv_dir, platform)\n    venv.create(sys.executable)\n\n    with venv:\n        output = platform.run_command(\n            [\"python\", \"-W\", \"ignore\", \"-\"],\n            check=True,\n            capture_output=True,\n            input=b\"import json,sys;print(json.dumps([path for path in sys.path if path]))\",\n        ).stdout.decode(\"utf-8\")\n\n        assert venv.environment is venv.environment\n        assert venv.sys_path is venv.sys_path\n\n        assert venv.environment[\"sys_platform\"] == sys.platform\n        assert venv.sys_path == json.loads(output)\n"
  },
  {
    "path": "tests/venv/test_utils.py",
    "content": "from hatch.venv.utils import get_random_venv_name\n\n\nclass TestGetRandomVenvName:\n    def test_length(self):\n        assert len(get_random_venv_name()) == 4\n\n    def test_different(self):\n        assert get_random_venv_name() != get_random_venv_name()\n"
  },
  {
    "path": "tests/workspaces/__init__.py",
    "content": ""
  },
  {
    "path": "tests/workspaces/test_config.py",
    "content": "class TestWorkspaceConfiguration:\n    def test_workspace_members_editable_install(self, temp_dir, hatch):\n        \"\"\"Test that workspace members are installed as editable packages.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"workspace-root\"\nversion = \"0.1.0\"\n\n[tool.hatch.envs.default]\ntype = \"virtual\"\nworkspace.members = [\"packages/*\"]\n\"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        member1_dir = packages_dir / \"member1\"\n        member1_dir.mkdir()\n        (member1_dir / \"pyproject.toml\").write_text(\"\"\"\n[project]\nname = \"member1\"\nversion = \"0.1.0\"\ndependencies = [\"requests\"]\n\"\"\")\n\n        member2_dir = packages_dir / \"member2\"\n        member2_dir.mkdir()\n        (member2_dir / \"pyproject.toml\").write_text(\"\"\"\n[project]\nname = \"member2\"\nversion = \"0.1.0\"\ndependencies = [\"click\"]\n\"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\")\n            assert result.exit_code == 0\n\n            result = hatch(\"env\", \"show\", \"--json\")\n            assert result.exit_code == 0\n\n    def test_workspace_exclude_patterns(self, temp_dir, hatch):\n        \"\"\"Test that exclude patterns filter out workspace members.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"workspace-root\"\nversion = \"0.1.0\"\n\n[tool.hatch.envs.default]\nworkspace.members = [\"packages/*\"]\nworkspace.exclude = [\"packages/excluded*\"]\n\"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        included_dir = packages_dir / \"included\"\n        included_dir.mkdir()\n        (included_dir / \"pyproject.toml\").write_text(\"\"\"\n[project]\nname = \"included\"\nversion = \"0.1.0\"\n\"\"\")\n\n        excluded_dir = packages_dir / \"excluded-pkg\"\n        excluded_dir.mkdir()\n        (excluded_dir / \"pyproject.toml\").write_text(\"\"\"\n[project]\nname = \"excluded-pkg\"\nversion = \"0.1.0\"\n\"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\")\n            assert result.exit_code == 0\n\n    def test_workspace_parallel_dependency_resolution(self, temp_dir, hatch):\n        \"\"\"Test parallel dependency resolution for workspace members.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"workspace-root\"\nversion = \"0.1.0\"\n\n[tool.hatch.envs.default]\nworkspace.members = [\"packages/*\"]\nworkspace.parallel = true\n\"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        for i in range(3):\n            member_dir = packages_dir / f\"member{i}\"\n            member_dir.mkdir()\n            (member_dir / \"pyproject.toml\").write_text(f\"\"\"\n[project]\nname = \"member{i}\"\nversion = \"0.1.{i}\"\ndependencies = [\"requests\"]\n\"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\")\n            assert result.exit_code == 0\n\n    def test_workspace_member_features(self, temp_dir, hatch):\n        \"\"\"Test workspace members with specific features.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"workspace-root\"\nversion = \"0.1.0\"\n\n[tool.hatch.envs.default]\nworkspace.members = [\n    {path = \"packages/member1\", features = [\"dev\", \"test\"]}\n]\n\"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        member1_dir = packages_dir / \"member1\"\n        member1_dir.mkdir()\n        (member1_dir / \"pyproject.toml\").write_text(\"\"\"\n[project]\nname = \"member1\"\ndependencies = [\"requests\"]\nversion = \"0.1.0\"\n[project.optional-dependencies]\ndev = [\"black\", \"ruff\"]\ntest = [\"pytest\"]\n\"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\")\n            assert result.exit_code == 0\n\n    def test_workspace_no_members_fallback(self, temp_dir, hatch):\n        \"\"\"Test fallback when no workspace members are defined.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"workspace-root\"\nversion = \"0.1.0\"\n\n[tool.hatch.envs.default]\ndependencies = [\"requests\"]\n\"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\")\n            assert result.exit_code == 0\n\n            result = hatch(\"env\", \"show\", \"--json\")\n            assert result.exit_code == 0\n\n    def test_workspace_cross_member_dependencies(self, temp_dir, hatch):\n        \"\"\"Test workspace members depending on each other.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"workspace-root\"\nversion = \"0.1.0\"\n\n[tool.hatch.envs.default]\nworkspace.members = [\"packages/*\"]\n\"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        base_dir = packages_dir / \"base\"\n        base_dir.mkdir()\n        (base_dir / \"pyproject.toml\").write_text(\"\"\"\n[project]\nname = \"base\"\nversion = \"0.1.0\"\ndependencies = [\"requests\"]\n\"\"\")\n\n        app_dir = packages_dir / \"app\"\n        app_dir.mkdir()\n        (app_dir / \"pyproject.toml\").write_text(\"\"\"\n[project]\nname = \"app\"\nversion = \"0.1.0\"\ndependencies = [\"base\", \"click\"]\n\"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\")\n            assert result.exit_code == 0\n\n            result = hatch(\"dep\", \"show\", \"table\")\n            assert result.exit_code == 0\n\n    def test_workspace_build_all_members(self, temp_dir, hatch):\n        \"\"\"Test building all workspace members.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_pkg = workspace_root / \"workspace_root\"\n        workspace_pkg.mkdir()\n        (workspace_pkg / \"__init__.py\").write_text('__version__ = \"0.1.0\"')\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"workspace-root\"\nversion = \"0.1.0\"\n\n[tool.hatch.envs.default]\nworkspace.members = [\"packages/*\"]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.build.targets.wheel]\npackages = [\"workspace_root\"]\n\"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        for i in range(2):\n            member_dir = packages_dir / f\"member{i}\"\n            member_dir.mkdir()\n            (member_dir / \"pyproject.toml\").write_text(f\"\"\"\n[project]\nname = \"member{i}\"\nversion = \"0.1.{i}\"\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.build.targets.wheel]\npackages = [\"member{i}\"]\n\"\"\")\n\n            src_dir = member_dir / f\"member{i}\"\n            src_dir.mkdir()\n            (src_dir / \"__init__.py\").write_text(f'__version__ = \"0.1.{i}\"')\n\n        with workspace_root.as_cwd():\n            result = hatch(\"build\")\n            assert result.exit_code == 0\n\n    def test_environment_specific_workspace_slices(self, temp_dir, hatch):\n        \"\"\"Test different workspace slices per environment.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"workspace-root\"\nversion = \"0.1.0\"\n\n[tool.hatch.envs.unit-tests]\nworkspace.members = [\"packages/core\", \"packages/utils\"]\nscripts.test = \"pytest tests/unit\"\n\n[tool.hatch.envs.integration-tests]\nworkspace.members = [\"packages/*\"]\nscripts.test = \"pytest tests/integration\"\n\"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        for pkg in [\"core\", \"utils\", \"extras\"]:\n            pkg_dir = packages_dir / pkg\n            pkg_dir.mkdir()\n            (pkg_dir / \"pyproject.toml\").write_text(f\"\"\"\n[project]\nname = \"{pkg}\"\nversion = \"0.1.0\"\n\"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\", \"unit-tests\")\n            assert result.exit_code == 0\n\n            result = hatch(\"env\", \"create\", \"integration-tests\")\n            assert result.exit_code == 0\n\n    def test_workspace_test_matrices(self, temp_dir, hatch):\n        \"\"\"Test workspace configuration with test matrices.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"workspace-root\"\nversion = \"0.1.0\"\n\n[[tool.hatch.envs.test.matrix]]\npython = [\"3.9\", \"3.10\"]\n\n[tool.hatch.envs.test]\nworkspace.members = [\"packages/*\"]\ndependencies = [\"pytest\", \"coverage\"]\nscripts.test = \"pytest {args}\"\n\n[[tool.hatch.envs.test-core.matrix]]\npython = [\"3.9\", \"3.10\"]\n\n[tool.hatch.envs.test-core]\nworkspace.members = [\"packages/core\"]\ndependencies = [\"pytest\", \"coverage\"]\nscripts.test = \"pytest packages/core/tests {args}\"\n\"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        for pkg in [\"core\", \"utils\"]:\n            pkg_dir = packages_dir / pkg\n            pkg_dir.mkdir()\n            (pkg_dir / \"pyproject.toml\").write_text(f\"\"\"\n[project]\nname = \"{pkg}\"\nversion = \"0.1.0\"\n\"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"show\", \"test\")\n            assert result.exit_code == 0\n\n            result = hatch(\"env\", \"show\", \"test-core\")\n            assert result.exit_code == 0\n\n    def test_workspace_library_with_plugins(self, temp_dir, hatch):\n        \"\"\"Test library with plugins workspace configuration.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n    [project]\n    name = \"library-root\"\n    version = \"0.1.0\"\n\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [tool.hatch.build.targets.wheel]\n    packages = []\n\n    [tool.hatch.envs.default]\n    skip-install = true\n    workspace.members = [\"core\"]\n    dependencies = [\"pytest\"]\n\n    [tool.hatch.envs.full]\n    skip-install = true\n    workspace.members = [\n      \"core\",\n      \"plugins/database\",\n      \"plugins/cache\",\n      \"plugins/auth\"\n    ]\n    dependencies = [\"pytest\", \"pytest-asyncio\"]\n\n    [tool.hatch.envs.database-only]\n    skip-install = true\n    workspace.members = [\n      \"core\",\n      {path = \"plugins/database\", features = [\"postgresql\", \"mysql\"]}\n    ]\n    \"\"\")\n\n        # Create core package with source\n        core_dir = workspace_root / \"core\"\n        core_dir.mkdir()\n        (core_dir / \"pyproject.toml\").write_text(\"\"\"\n    [project]\n    name = \"core\"\n    version = \"0.1.0\"\n\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [tool.hatch.build.targets.wheel]\n    packages = [\"core\"]\n    \"\"\")\n        core_src = core_dir / \"core\"\n        core_src.mkdir()\n        (core_src / \"__init__.py\").write_text(\"\")\n\n        # Create plugins with source\n        plugins_dir = workspace_root / \"plugins\"\n        plugins_dir.mkdir()\n\n        for plugin in [\"database\", \"cache\", \"auth\"]:\n            plugin_dir = plugins_dir / plugin\n            plugin_dir.mkdir()\n            (plugin_dir / \"pyproject.toml\").write_text(f\"\"\"\n    [project]\n    name = \"{plugin}\"\n    version = \"0.1.0\"\n    dependencies = [\"core\"]\n\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [tool.hatch.build.targets.wheel]\n    packages = [\"{plugin}\"]\n\n    [project.optional-dependencies]\n    postgresql = [\"requests\"]\n    mysql = [\"click\"]\n    \"\"\")\n            plugin_src = plugin_dir / plugin\n            plugin_src.mkdir()\n            (plugin_src / \"__init__.py\").write_text(\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\", \"full\")\n            assert result.exit_code == 0\n\n            result = hatch(\"env\", \"create\", \"database-only\")\n            assert result.exit_code == 0\n\n    def test_workspace_multi_service_application(self, temp_dir, hatch):\n        \"\"\"Test multi-service application workspace configuration.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n    [project]\n    name = \"microservices-root\"\n    version = \"0.1.0\"\n\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [tool.hatch.build.targets.wheel]\n    packages = []\n\n    [tool.hatch.envs.default]\n    skip-install = true\n    workspace.members = [\"shared\"]\n    dependencies = [\"pytest\", \"requests\"]\n\n    [tool.hatch.envs.api]\n    skip-install = true\n    workspace.members = [\n      \"shared\",\n      {path = \"services/api\", features = [\"dev\"]}\n    ]\n    dependencies = [\"fastapi\", \"uvicorn\"]\n    scripts.dev = \"uvicorn services.api.main:app --reload\"\n\n    [tool.hatch.envs.worker]\n    skip-install = true\n    workspace.members = [\n      \"shared\",\n      {path = \"services/worker\", features = [\"dev\"]}\n    ]\n    dependencies = [\"celery\", \"redis\"]\n    scripts.dev = \"celery -A services.worker.tasks worker --loglevel=info\"\n\n    [tool.hatch.envs.integration]\n    skip-install = true\n    workspace.members = [\n      \"shared\",\n      \"services/api\",\n      \"services/worker\",\n      \"services/frontend\"\n    ]\n    dependencies = [\"pytest\", \"httpx\", \"docker\"]\n    scripts.test = \"pytest tests/integration {args}\"\n    \"\"\")\n\n        # Create shared package with source\n        shared_dir = workspace_root / \"shared\"\n        shared_dir.mkdir()\n        (shared_dir / \"pyproject.toml\").write_text(\"\"\"\n    [project]\n    name = \"shared\"\n    version = \"0.1.0\"\n\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [tool.hatch.build.targets.wheel]\n    packages = [\"shared\"]\n    \"\"\")\n        shared_src = shared_dir / \"shared\"\n        shared_src.mkdir()\n        (shared_src / \"__init__.py\").write_text(\"\")\n\n        # Create services with source\n        services_dir = workspace_root / \"services\"\n        services_dir.mkdir()\n\n        for service in [\"api\", \"worker\", \"frontend\"]:\n            service_dir = services_dir / service\n            service_dir.mkdir()\n            (service_dir / \"pyproject.toml\").write_text(f\"\"\"\n    [project]\n    name = \"{service}\"\n    version = \"0.1.0\"\n    dependencies = [\"shared\"]\n\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [tool.hatch.build.targets.wheel]\n    packages = [\"{service}\"]\n\n    [project.optional-dependencies]\n    dev = [\"black\", \"ruff\"]\n    \"\"\")\n            service_src = service_dir / service\n            service_src.mkdir()\n            (service_src / \"__init__.py\").write_text(\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\", \"api\")\n            assert result.exit_code == 0\n\n            result = hatch(\"env\", \"create\", \"worker\")\n            assert result.exit_code == 0\n\n            result = hatch(\"env\", \"create\", \"integration\")\n            assert result.exit_code == 0\n\n    def test_workspace_documentation_generation(self, temp_dir, hatch):\n        \"\"\"Test documentation generation workspace configuration.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n[project]\nname = \"docs-root\"\nversion = \"0.1.0\"\n\n[tool.hatch.envs.docs]\nworkspace.members = [\n  {path = \"packages/core\", features = [\"docs\"]},\n  {path = \"packages/cli\", features = [\"docs\"]},\n  {path = \"packages/plugins\", features = [\"docs\"]}\n]\ndependencies = [\n  \"mkdocs\",\n  \"mkdocs-material\",\n  \"mkdocstrings[python]\"\n]\nscripts.serve = \"mkdocs serve\"\nscripts.build = \"mkdocs build\"\n\n[tool.hatch.envs.docs-api-only]\nworkspace.members = [\n  {path = \"packages/core\", features = [\"docs\"]}\n]\ntemplate = \"docs\"\nscripts.serve = \"mkdocs serve --config-file mkdocs-api.yml\"\n\"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        for pkg in [\"core\", \"cli\", \"plugins\"]:\n            pkg_dir = packages_dir / pkg\n            pkg_dir.mkdir()\n            (pkg_dir / \"pyproject.toml\").write_text(f\"\"\"\n[project]\nname = \"{pkg}\"\nversion = \"0.1.0\"\n[project.optional-dependencies]\ndocs = [\"sphinx\", \"sphinx-rtd-theme\"]\n\"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\", \"docs\")\n            assert result.exit_code == 0\n\n            result = hatch(\"env\", \"create\", \"docs-api-only\")\n            assert result.exit_code == 0\n\n    def test_workspace_development_workflow(self, temp_dir, hatch, monkeypatch):\n        \"\"\"Test development workflow workspace configuration.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n    [project]\n    name = \"dev-workflow-root\"\n    version = \"0.1.0\"\n\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [tool.hatch.build.targets.wheel]\n    packages = []\n\n    [tool.hatch.envs.dev]\n    skip-install = true\n    workspace.members = [\"packages/*\"]\n    workspace.parallel = true\n    dependencies = [\n      \"pytest\",\n      \"black\",\n      \"ruff\",\n      \"mypy\",\n      \"pre-commit\"\n    ]\n    scripts.setup = \"pre-commit install\"\n    scripts.test = \"pytest {args}\"\n    scripts.lint = [\"ruff check .\", \"black --check .\", \"mypy .\"]\n    scripts.fmt = [\"ruff check --fix .\", \"black .\"]\n\n    [tool.hatch.envs.feature]\n    skip-install = true\n    template = \"dev\"\n    workspace.members = [\n      \"packages/core\",\n      \"packages/{env:FEATURE_PACKAGE}\"\n    ]\n    scripts.test = \"pytest packages/{env:FEATURE_PACKAGE}/tests {args}\"\n\n    [[tool.hatch.envs.release.matrix]]\n    package = [\"core\", \"utils\", \"cli\"]\n\n    [tool.hatch.envs.release]\n    detached = true\n    skip-install = true\n    workspace.members = [\"packages/{matrix:package}\"]\n    dependencies = [\"build\", \"twine\"]\n    scripts.build = \"python -m build packages/{matrix:package}\"\n    scripts.publish = \"twine upload packages/{matrix:package}/dist/*\"\n    \"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        for pkg in [\"core\", \"utils\", \"cli\"]:\n            pkg_dir = packages_dir / pkg\n            pkg_dir.mkdir()\n            (pkg_dir / \"pyproject.toml\").write_text(f\"\"\"\n    [project]\n    name = \"{pkg}\"\n    version = \"0.1.0\"\n\n    [build-system]\n    requires = [\"hatchling\"]\n    build-backend = \"hatchling.build\"\n\n    [tool.hatch.build.targets.wheel]\n    packages = [\"{pkg}\"]\n    \"\"\")\n            pkg_src = pkg_dir / pkg\n            pkg_src.mkdir()\n            (pkg_src / \"__init__.py\").write_text(\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\", \"dev\")\n            assert result.exit_code == 0\n\n            # Test feature environment with environment variable\n            monkeypatch.setenv(\"FEATURE_PACKAGE\", \"utils\")\n            result = hatch(\"env\", \"create\", \"feature\")\n            assert result.exit_code == 0\n\n            result = hatch(\"env\", \"create\", \"release\")\n            assert result.exit_code == 0\n\n    def test_workspace_overrides_matrix_conditional_members(self, temp_dir, hatch):\n        \"\"\"Test workspace members added conditionally via matrix overrides.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n    [project]\n    name = \"workspace-root\"\n    version = \"0.1.0\"\n\n    [[tool.hatch.envs.test.matrix]]\n    python = [\"3.9\", \"3.11\"]\n\n    [tool.hatch.envs.test]\n    workspace.members = [\"packages/core\"]\n\n    [tool.hatch.envs.test.overrides]\n    matrix.python.workspace.members = [\n        { value = \"packages/py311-only\", if = [\"3.11\"] }\n    ]\n    \"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        # Core package (always included)\n        core_dir = packages_dir / \"core\"\n        core_dir.mkdir()\n        (core_dir / \"pyproject.toml\").write_text(\"\"\"\n    [project]\n    name = \"core\"\n    version = \"0.1.0\"\n    \"\"\")\n\n        # Python 3.11+ only package\n        py311_dir = packages_dir / \"py311-only\"\n        py311_dir.mkdir()\n        (py311_dir / \"pyproject.toml\").write_text(\"\"\"\n    [project]\n    name = \"py311-only\"\n    version = \"0.1.0\"\n    \"\"\")\n\n        with workspace_root.as_cwd():\n            # Both environments should be created\n            result = hatch(\"env\", \"create\", \"test\")\n            assert result.exit_code == 0\n\n    def test_workspace_overrides_platform_conditional_members(self, temp_dir, hatch):\n        \"\"\"Test workspace members added conditionally via platform overrides.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n    [project]\n    name = \"workspace-root\"\n    version = \"0.1.0\"\n\n    [tool.hatch.envs.default]\n    workspace.members = [\"packages/core\"]\n\n    [tool.hatch.envs.default.overrides]\n    platform.linux.workspace.members = [\"packages/linux-specific\"]\n    platform.windows.workspace.members = [\"packages/windows-specific\"]\n    \"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        for pkg in [\"core\", \"linux-specific\", \"windows-specific\"]:\n            pkg_dir = packages_dir / pkg\n            pkg_dir.mkdir()\n            (pkg_dir / \"pyproject.toml\").write_text(f\"\"\"\n    [project]\n    name = \"{pkg}\"\n    version = \"0.1.0\"\n    \"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\")\n            assert result.exit_code == 0\n\n    def test_workspace_overrides_combined_conditions(self, temp_dir, hatch):\n        \"\"\"Test workspace members with combined matrix and platform conditions.\"\"\"\n        workspace_root = temp_dir / \"workspace\"\n        workspace_root.mkdir()\n\n        workspace_config = workspace_root / \"pyproject.toml\"\n        workspace_config.write_text(\"\"\"\n    [project]\n    name = \"workspace-root\"\n    version = \"0.1.0\"\n\n    [[tool.hatch.envs.test.matrix]]\n    python = [\"3.9\", \"3.11\"]\n\n    [tool.hatch.envs.test]\n    workspace.members = [\"packages/core\"]\n\n    [tool.hatch.envs.test.overrides]\n    matrix.python.workspace.members = [\n        { value = \"packages/linux-py311\", if = [\"3.11\"], platform = [\"linux\"] }\n    ]\n    \"\"\")\n\n        packages_dir = workspace_root / \"packages\"\n        packages_dir.mkdir()\n\n        for pkg in [\"core\", \"linux-py311\"]:\n            pkg_dir = packages_dir / pkg\n            pkg_dir.mkdir()\n            (pkg_dir / \"pyproject.toml\").write_text(f\"\"\"\n    [project]\n    name = \"{pkg}\"\n    version = \"0.1.0\"\n    \"\"\")\n\n        with workspace_root.as_cwd():\n            result = hatch(\"env\", \"create\", \"test\")\n            assert result.exit_code == 0\n"
  }
]