diff --git a/.authors.yml b/.authors.yml index dc7b1a4258..89d9a7ae00 100644 --- a/.authors.yml +++ b/.authors.yml @@ -162,7 +162,7 @@ aliases: - MinRK github: minrk - num_commits: 15 + num_commits: 16 first_commit: 2014-02-13 19:43:59 - name: Matty G email: meawoppl@gmail.com @@ -612,7 +612,7 @@ first_commit: 2015-08-30 06:44:37 - name: Marcel Bargull email: marcel.bargull@udo.edu - num_commits: 82 + num_commits: 85 first_commit: 2016-09-26 11:45:54 github: mbargull alternate_emails: @@ -1202,7 +1202,7 @@ alternate_emails: - clee@anaconda.com - name: Ken Odegard - num_commits: 168 + num_commits: 178 email: kodegard@anaconda.com first_commit: 2020-09-08 19:53:41 github: kenodegard @@ -1240,7 +1240,7 @@ github: pre-commit-ci[bot] aliases: - pre-commit-ci[bot] - num_commits: 61 + num_commits: 64 first_commit: 2021-11-20 01:47:17 - name: Jacob Walls email: jacobtylerwalls@gmail.com @@ -1251,7 +1251,7 @@ github: beeankha alternate_emails: - beeankha@gmail.com - num_commits: 20 + num_commits: 23 first_commit: 2022-01-19 16:40:06 - name: Conda Bot email: 18747875+conda-bot@users.noreply.github.com @@ -1262,7 +1262,7 @@ alternate_emails: - ad-team+condabot@anaconda.com - 18747875+conda-bot@users.noreply.github.com - num_commits: 44 + num_commits: 96 first_commit: 2022-01-17 18:09:22 - name: Uwe L. Korn email: xhochy@users.noreply.github.com @@ -1271,7 +1271,7 @@ - name: Daniel Holth email: dholth@anaconda.com github: dholth - num_commits: 14 + num_commits: 15 first_commit: 2022-04-28 05:22:14 - name: Rylan Chord email: rchord@users.noreply.github.com @@ -1281,7 +1281,7 @@ - name: Travis Hathaway email: travis.j.hathaway@gmail.com github: travishathaway - num_commits: 6 + num_commits: 7 first_commit: 2022-05-12 05:53:02 - name: Kyle Leaders email: remkade@users.noreply.github.com @@ -1305,7 +1305,7 @@ - name: Katherine Kinnaman email: kkinnaman@anaconda.com github: kathatherine - num_commits: 2 + num_commits: 3 first_commit: 2022-07-07 10:56:31 - name: dependabot[bot] email: 49699333+dependabot[bot]@users.noreply.github.com @@ -1376,7 +1376,7 @@ aliases: - Ryan github: ryanskeith - num_commits: 5 + num_commits: 6 first_commit: 2023-03-22 03:11:02 - name: Rishabh Singh email: 67859818+rishabh11336@users.noreply.github.com @@ -1434,3 +1434,8 @@ - h-vetinari num_commits: 1 first_commit: 2023-10-25 09:33:34 +- name: Finn Womack + email: flan313@gmail.com + num_commits: 1 + first_commit: 2024-02-06 11:43:45 + github: finnagin diff --git a/ci/github/.condarc b/.github/condarc similarity index 73% rename from ci/github/.condarc rename to .github/condarc index 44a36fcc35..a76e773f8f 100644 --- a/ci/github/.condarc +++ b/.github/condarc @@ -2,7 +2,5 @@ auto_update_conda: False auto_activate_base: True notify_outdated_conda: False changeps1: False -pkgs_dirs: -- /usr/share/miniconda/envs/test/pkgs always_yes: True local_repodata_ttl: 7200 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c0b0e8ff59..ee71e1a826 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,3 @@ -# this is the sibling workflow to tests-skip.yml, it is required to work around -# the skipped but required checks issue: -# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks name: Tests on: @@ -32,20 +29,29 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true +env: + # https://conda.github.io/conda-libmamba-solver/user-guide/configuration/#advanced-options + CONDA_LIBMAMBA_SOLVER_NO_CHANNELS_FROM_INSTALLED: true + jobs: # detect whether any code changes are included in this PR changes: runs-on: ubuntu-latest permissions: + # necessary to detect changes + # https://github.com/dorny/paths-filter#supported-workflows pull-requests: read outputs: code: ${{ steps.filter.outputs.code }} steps: - - uses: actions/checkout@v3 + - name: Checkout Source + uses: actions/checkout@v4 # dorny/paths-filter needs git clone for non-PR events - # https://github.com/marketplace/actions/paths-changes-filter#supported-workflows + # https://github.com/dorny/paths-filter#supported-workflows if: github.event_name != 'pull_request' - - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 + + - name: Filter Changes + uses: dorny/paths-filter@v3 id: filter with: filters: | @@ -65,6 +71,7 @@ jobs: runs-on: ubuntu-latest defaults: run: + # https://github.com/conda-incubator/setup-miniconda#use-a-default-shell shell: bash -el {0} strategy: fail-fast: false @@ -89,75 +96,140 @@ jobs: conda-version: canary test-type: parallel env: - CONDA_CHANNEL_LABEL: ${{ matrix.conda-version == 'canary' && 'conda-canary/label/dev' || 'defaults' }} - CONDA_VERSION: ${{ contains('canary,release', matrix.conda-version) && 'conda' || format('conda={0}', matrix.conda-version) }} - REPLAY_NAME: Linux-${{ matrix.conda-version }}-Py${{ matrix.python-version }} - REPLAY_DIR: ${{ github.workspace }}/pytest-replay + CONDA_CHANNEL_LABEL: ${{ matrix.conda-version == 'canary' && 'conda-canary/label/dev::' || '' }} + CONDA_VERSION: ${{ contains('canary|release', matrix.conda-version) && 'conda' || format('conda={0}', matrix.conda-version) }} PYTEST_MARKER: ${{ matrix.test-type == 'serial' && 'serial' || 'not serial' }} - PYTEST_NUMPROCESSES: ${{ matrix.test-type == 'serial' && 0 || 'auto' }} steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Checkout Source + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Timestamp - run: echo "TIMESTAMP=$(date -u "+%Y%m")" >> $GITHUB_ENV - shell: bash + - name: Hash + Timestamp + run: echo "HASH=${{ runner.os }}-${{ runner.arch }}-Py${{ matrix.python-version }}-${{ matrix.conda-version }}-${{ matrix.test-type }}-$(date -u "+%Y%m")" >> $GITHUB_ENV - - name: Cache conda - uses: actions/cache@v3 + - name: Cache Conda + uses: actions/cache@v4 with: path: ~/conda_pkgs_dir - key: ${{ runner.os }}-conda-${{ env.TIMESTAMP }} + key: cache-${{ env.HASH }} - - name: Setup miniconda - uses: conda-incubator/setup-miniconda@v2 + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@v3 with: - condarc-file: ./ci/github/.condarc - python-version: ${{ matrix.python-version }} + condarc-file: .github/condarc run-post: false # skip post cleanup - - name: Setup environment - run: | - conda install -q -y -c defaults \ - --file ./tests/requirements.txt \ - --file ./tests/requirements-linux.txt \ - ${{ env.CONDA_CHANNEL_LABEL }}::${{ env.CONDA_VERSION }} - pip install -e . --no-deps + - name: Conda Install + run: conda install + --yes + --file tests/requirements.txt + --file tests/requirements-${{ runner.os }}.txt + --file tests/requirements-ci.txt + python=${{ matrix.python-version }} + ${{ env.CONDA_CHANNEL_LABEL }}${{ env.CONDA_VERSION }} + + # TODO: how can we remove this step? + - name: Install Self + run: pip install -e . + + - name: Conda Info + # view test env info (not base) + run: python -m conda info --verbose + + - name: Conda List + run: conda list --show-channel-urls + + - name: Run Tests + run: pytest + --cov=conda_build + -n auto + -m "${{ env.PYTEST_MARKER }}" + + - name: Upload Coverage + uses: codecov/codecov-action@v4 + with: + flags: ${{ runner.os }},${{ runner.arch }},${{ matrix.python-version }},${{ matrix.test-type }} - - name: Show info - run: | - conda info -a - conda list --show-channel-urls + - name: Upload Test Results + if: '!cancelled()' + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ env.HASH }} + path: | + .coverage + test-report.xml + retention-days: 1 # temporary, combined in aggregate below + + # linux benchmarks + linux-benchmarks: + # only run test suite if there are code changes + needs: changes + if: needs.changes.outputs.code == 'true' - - name: Run tests - run: | - pytest \ - --color=yes \ - -v \ - -n "${{ env.PYTEST_NUMPROCESSES }}" \ - --basetemp "${{ runner.temp }}/${{ matrix.test-type }}" \ - --cov conda_build \ - --cov-append \ - --cov-branch \ - --cov-report xml \ - --replay-record-dir="${{ env.REPLAY_DIR }}" \ - --replay-base-name="${{ env.REPLAY_NAME }}" \ - -m "${{ env.PYTEST_MARKER }}" \ - ./tests - - - uses: codecov/codecov-action@v3 + runs-on: ubuntu-latest + defaults: + run: + # https://github.com/conda-incubator/setup-miniconda#use-a-default-shell + shell: bash -el {0} # bash exit immediately on error + login shell + strategy: + fail-fast: false + matrix: + python-version: ['3.12'] + + steps: + - name: Checkout Source + uses: actions/checkout@v4 with: - flags: ${{ matrix.test-type }},${{ matrix.python-version }},linux-64 + fetch-depth: 0 - - name: Upload Pytest Replay - if: '!cancelled()' - uses: actions/upload-artifact@v3 + - name: Hash + Timestamp + run: echo "HASH=${{ runner.os }}-${{ runner.arch }}-Py${{ matrix.python-version }}-benchmark-$(date -u "+%Y%m")" >> $GITHUB_ENV + + - name: Cache Conda + uses: actions/cache@v4 + with: + path: ~/conda_pkgs_dir + key: cache-${{ env.HASH }} + + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@v3 with: - name: ${{ env.REPLAY_NAME }}-${{ matrix.test-type }} - path: ${{ env.REPLAY_DIR }} + condarc-file: .github/condarc + run-post: false # skip post cleanup + + - name: Conda Install + run: conda install + --yes + --file tests/requirements.txt + --file tests/requirements-${{ runner.os }}.txt + --file tests/requirements-ci.txt + python=${{ matrix.python-version }} + ${{ env.CONDA_CHANNEL_LABEL }}${{ env.CONDA_VERSION }} + + - name: Install CodSpeed + run: pip install git+https://github.com/kenodegard/pytest-codspeed.git@fix-outerr-redirects#egg=pytest-codspeed + + # TODO: how can we remove this step? + - name: Install Self + run: pip install -e . + + - name: Conda Info + # view test env info (not base) + run: python -m conda info --verbose + + - name: Conda Config + run: conda config --show-sources + + - name: Conda List + run: conda list --show-channel-urls + + - name: Run Benchmarks + uses: CodSpeedHQ/action@v2 + with: + token: ${{ secrets.CODSPEED_TOKEN }} + run: $CONDA/envs/test/bin/pytest --codspeed # windows test suite windows: @@ -181,77 +253,78 @@ jobs: conda-version: canary test-type: parallel env: + ErrorActionPreference: Stop # powershell exit on first error CONDA_CHANNEL_LABEL: ${{ matrix.conda-version == 'canary' && 'conda-canary/label/dev' || 'defaults' }} - REPLAY_NAME: Win-${{ matrix.conda-version }}-Py${{ matrix.python-version }} - REPLAY_DIR: ${{ github.workspace }}\pytest-replay PYTEST_MARKER: ${{ matrix.test-type == 'serial' && 'serial' || 'not serial and not slow' }} - PYTEST_NUMPROCESSES: ${{ matrix.test-type == 'serial' && 0 || 'auto' }} steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Checkout Source + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Timestamp - run: echo "TIMESTAMP=$(date -u "+%Y%m")" >> $GITHUB_ENV - shell: bash + - name: Hash + Timestamp + shell: bash # use bash to run date command + run: echo "HASH=${{ runner.os }}-${{ runner.arch }}-Py${{ matrix.python-version }}-${{ matrix.conda-version }}-${{ matrix.test-type }}-$(date -u "+%Y%m")" >> $GITHUB_ENV - - name: Cache conda - uses: actions/cache@v3 + - name: Cache Conda + uses: actions/cache@v4 with: path: ~/conda_pkgs_dir - key: ${{ runner.os }}-conda-${{ env.TIMESTAMP }} + key: cache-${{ env.HASH }} - - name: Setup miniconda - uses: conda-incubator/setup-miniconda@v2 + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@v3 with: - condarc-file: .\ci\github\.condarc - python-version: ${{ matrix.python-version }} + condarc-file: .github\condarc run-post: false # skip post cleanup - - name: Setup environment - shell: cmd /C CALL {0} - run: | - @echo on - CALL choco install visualstudio2017-workload-vctools || exit 1 - CALL conda install -q -y -c defaults ^ - --file .\tests\requirements.txt ^ - --file .\tests\requirements-windows.txt ^ - ${{ env.CONDA_CHANNEL_LABEL }}::conda || exit 1 - CALL pip install -e . --no-deps || exit 1 - - - name: Show info - run: | - conda info -a - conda list --show-channel-urls - - - name: Run tests - run: | - pytest ` - --color=yes ` - -v ` - -n "${{ env.PYTEST_NUMPROCESSES }}" ` - --basetemp "${{ runner.temp }}\${{ matrix.test-type}}" ` - --cov conda_build ` - --cov-append ` - --cov-branch ` - --cov-report xml ` - --replay-record-dir="${{ env.REPLAY_DIR }}" ` - --replay-base-name="${{ env.REPLAY_NAME }}" ` - -m "${{ env.PYTEST_MARKER }}" ` - .\tests - - - uses: codecov/codecov-action@v3 + - name: Choco Install + run: choco install visualstudio2017-workload-vctools + + - name: Conda Install + run: conda install + --yes + --file tests\requirements.txt + --file tests\requirements-${{ runner.os }}.txt + --file tests\requirements-ci.txt + python=${{ matrix.python-version }} + ${{ env.CONDA_CHANNEL_LABEL }}::conda + + # TODO: how can we remove this step? + - name: Install Self + run: pip install -e . + + - name: Conda Info + # view test env info (not base) + run: python -m conda info --verbose + + - name: Conda List + run: conda list --show-channel-urls + + - name: Run Tests + # Windows is sensitive to long paths, using `--basetemp=${{ runner.temp }} to + # keep the test directories shorter + run: pytest + --cov=conda_build + --basetemp=${{ runner.temp }} + -n auto + -m "${{ env.PYTEST_MARKER }}" + + - name: Upload Coverage + uses: codecov/codecov-action@v4 with: - flags: ${{ matrix.test-type }},${{ matrix.python-version }},win-64 + flags: ${{ runner.os }},${{ runner.arch }},${{ matrix.python-version }},${{ matrix.test-type }} - - name: Upload Pytest Replay + - name: Upload Test Results if: '!cancelled()' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - path: ${{ env.REPLAY_DIR }} - name: ${{ env.REPLAY_NAME }}-${{ matrix.test-type }} + name: test-results-${{ env.HASH }} + path: | + .coverage + test-report.xml + retention-days: 1 # temporary, combined in aggregate below # macos test suite macos: @@ -262,6 +335,7 @@ jobs: runs-on: macos-11 defaults: run: + # https://github.com/conda-incubator/setup-miniconda#use-a-default-shell shell: bash -el {0} strategy: fail-fast: false @@ -279,79 +353,77 @@ jobs: test-type: parallel env: CONDA_CHANNEL_LABEL: ${{ matrix.conda-version == 'canary' && 'conda-canary/label/dev' || 'defaults' }} - REPLAY_NAME: macOS-${{ matrix.conda-version }}-Py${{ matrix.python-version }} - REPLAY_DIR: ${{ github.workspace }}/pytest-replay PYTEST_MARKER: ${{ matrix.test-type == 'serial' && 'serial' || 'not serial' }} - PYTEST_NUMPROCESSES: ${{ matrix.test-type == 'serial' && 0 || 'auto' }} steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Checkout Source + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Timestamp - run: echo "TIMESTAMP=$(date -u "+%Y%m")" >> $GITHUB_ENV - shell: bash + - name: Hash + Timestamp + run: echo "HASH=${{ runner.os }}-${{ runner.arch }}-Py${{ matrix.python-version }}-${{ matrix.conda-version }}-${{ matrix.test-type }}-$(date -u "+%Y%m")" >> $GITHUB_ENV - - name: Cache conda - uses: actions/cache@v3 + - name: Cache Conda + uses: actions/cache@v4 with: path: ~/conda_pkgs_dir - key: ${{ runner.os }}-conda-${{ env.TIMESTAMP }} + key: cache-${{ env.HASH }} - - name: Setup miniconda - uses: conda-incubator/setup-miniconda@v2 + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@v3 with: - condarc-file: ./ci/github/.condarc - python-version: ${{ matrix.python-version }} + condarc-file: .github/condarc run-post: false # skip post cleanup - - name: Setup environment - run: | - sudo xcode-select --switch /Applications/Xcode_11.7.app - conda install -q -y -c defaults \ - --file ./tests/requirements.txt \ - --file ./tests/requirements-macos.txt \ - ${{ env.CONDA_CHANNEL_LABEL }}::conda - pip install -e . --no-deps - - - name: Show info - run: | - conda info -a - conda list --show-channel-urls - - - name: Run tests - run: | - pytest \ - --color=yes \ - -v \ - -n "${{ env.PYTEST_NUMPROCESSES }}" \ - --basetemp "${{ runner.temp }}/${{ matrix.test-type }}" \ - --cov conda_build \ - --cov-append \ - --cov-branch \ - --cov-report xml \ - --replay-record-dir="${{ env.REPLAY_DIR }}" \ - --replay-base-name="${{ env.REPLAY_NAME }}" \ - -m "${{ env.PYTEST_MARKER }}" \ - ./tests - - - uses: codecov/codecov-action@v3 + - name: Xcode Install + run: sudo xcode-select --switch /Applications/Xcode_11.7.app + + - name: Conda Install + run: conda install + --yes + --file tests/requirements.txt + --file tests/requirements-${{ runner.os }}.txt + --file tests/requirements-ci.txt + python=${{ matrix.python-version }} + ${{ env.CONDA_CHANNEL_LABEL }}::conda + + # TODO: how can we remove this step? + - name: Install Self + run: pip install -e . + + - name: Conda Info + # view test env info (not base) + run: python -m conda info --verbose + + - name: Conda List + run: conda list --show-channel-urls + + - name: Run Tests + run: pytest + --cov=conda_build + -n auto + -m "${{ env.PYTEST_MARKER }}" + + - name: Upload Coverage + uses: codecov/codecov-action@v4 with: - flags: ${{ matrix.test-type }},${{ matrix.python-version }},osx-64 + flags: ${{ runner.os }},${{ runner.arch }},${{ matrix.python-version }},${{ matrix.test-type }} - - name: Upload Pytest Replay + - name: Upload Test Results if: '!cancelled()' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: ${{ env.REPLAY_NAME }}-${{ matrix.test-type }} - path: ${{ env.REPLAY_DIR }} + name: test-results-${{ env.HASH }} + path: | + .coverage + test-report.xml + retention-days: 1 # temporary, combined in aggregate below # aggregate and upload aggregate: # only aggregate test suite if there are code changes - needs: [changes, linux, windows, macos] + needs: [changes, linux, linux-benchmarks, windows, macos] if: >- !cancelled() && ( @@ -361,40 +433,37 @@ jobs: runs-on: ubuntu-latest steps: - - name: Download test results - uses: actions/download-artifact@v3 + - name: Download Artifacts + uses: actions/download-artifact@v4 - - name: Upload combined test results - # provides one downloadable archive of all .coverage/test-report.xml files - # of all matrix runs for further analysis. - uses: actions/upload-artifact@v3 + - name: Upload Combined Test Results + # provides one downloadable archive of all matrix run test results for further analysis + uses: actions/upload-artifact@v4 with: name: test-results-${{ github.sha }}-all - path: test-results-${{ github.sha }}-* - retention-days: 90 # default: 90 + path: test-results-* - name: Test Summary uses: test-summary/action@v2 with: - paths: ./test-results-${{ github.sha }}-**/test-report*.xml + paths: test-results-*/test-report.xml # required check analyze: - name: Analyze results - needs: [linux, windows, macos, aggregate] + needs: [linux, linux-benchmarks, windows, macos, aggregate] if: '!cancelled()' runs-on: ubuntu-latest steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe + - name: Determine Success + uses: re-actors/alls-green@v1.2.2 with: + # permit jobs to be skipped if there are no code changes (see changes job) allowed-skips: ${{ toJSON(needs) }} jobs: ${{ toJSON(needs) }} # canary builds build: - name: Canary Build needs: [analyze] # only build canary build if # - prior steps succeeded, @@ -415,24 +484,28 @@ jobs: subdir: linux-64 - runner: macos-latest subdir: osx-64 + - runner: macos-14 + subdir: osx-arm64 - runner: windows-latest subdir: win-64 runs-on: ${{ matrix.runner }} steps: # Clean checkout of specific git ref needed for package metadata version # which needs env vars GIT_DESCRIBE_TAG and GIT_BUILD_STR: - - uses: actions/checkout@v3 + - name: Checkout Source + uses: actions/checkout@v4 with: ref: ${{ github.ref }} clean: true fetch-depth: 0 # Explicitly use Python 3.12 since each of the OSes has a different default Python - - uses: actions/setup-python@v4 + - name: Setup Python + uses: actions/setup-python@v4 with: python-version: '3.12' - - name: Detect label + - name: Detect Label shell: python run: | from pathlib import Path @@ -453,8 +526,8 @@ jobs: Path(environ["GITHUB_ENV"]).write_text(f"ANACONDA_ORG_LABEL={label}") - - name: Create and upload canary build - uses: conda/actions/canary-release@v23.7.0 + - name: Create & Upload + uses: conda/actions/canary-release@v24.2.0 with: package-name: ${{ github.event.repository.name }} subdir: ${{ matrix.subdir }} diff --git a/.mailmap b/.mailmap index 17e816d480..02df1bf754 100644 --- a/.mailmap +++ b/.mailmap @@ -92,6 +92,7 @@ Evan Klitzke Felix Kühnl Ferry Firmansjah <103191403+ffirmanff@users.noreply.github.com> Filipe Fernandes ocefpaf +Finn Womack Floris Bruynooghe Gabriel Reis Gaëtan de Menten diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3277627305..9335532d1f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -54,7 +54,7 @@ repos: # auto format Python codes within docstrings - id: blacken-docs - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.2 + rev: v0.3.3 hooks: # lint & attempt to correct failures (e.g. pyupgrade) - id: ruff diff --git a/AUTHORS.md b/AUTHORS.md index 7667f98c40..969994f016 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -75,6 +75,7 @@ Authors are sorted alphabetically. * Felix Kühnl * Ferry Firmansjah * Filipe Fernandes +* Finn Womack * Floris Bruynooghe * Gabriel Reis * Gaëtan de Menten diff --git a/CHANGELOG.md b/CHANGELOG.md index 840bc6636a..42d745f874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,109 @@ [//]: # (current developments) +## 24.3.0 (2024-03-15) + +### Enhancements + +* Add compatibility for `LIEF=0.14`. (#5227 via #5228) + +### Bug fixes + +* Fix `stdlib` being recognized in variant hash inputs. (#5190 via #5195) + +### Deprecations + +* Mark `conda_build.bdist_conda` module as pending deprecation. (#5196) +* Mark `conda_build.build.have_prefix_files` as deprecated. (#5199) +* Mark `conda_build.conda_interface.handle_proxy_407` as deprecated. Handled by `conda.gateways.connection.session.CondaSession`. (#5203) +* Mark `conda_build.conda_interface.hashsum_file` as deprecated. Use `conda.gateways.disk.read.compute_sum` instead. (#5203) +* Mark `conda_build.conda_interface.md5_file` as deprecated. Use `conda.gateways.disk.read.compute_sum(path, 'md5')` instead. (#5203) +* Mark `conda_build.environ.PREFIX_ACTION` as deprecated. (#5203) +* Mark `conda_build.environ.LINK_ACTION` as deprecated. (#5203) +* Mark `conda_build.environ.cache_actions` as deprecated. (#5203) +* Mark `conda_build.index.DummyExecutor` as deprecated. (#5203) +* Mark `conda_build.index.MAX_THREADS_DEFAULT` as deprecated. (#5203) +* Mark `conda_build.index.LOCK_TIMEOUT_SECS` as deprecated. (#5203) +* Mark `conda_build.index.LOCKFILE_NAME` as deprecated. (#5203) +* Postpone `conda_build.index.channel_data` deprecation. (#5203) +* Rename `conda_build.environ.create_env('specs_or_actions' -> 'specs_or_precs')`. (#5203) +* Rename `conda_build.environ._execute_actions('actions' -> 'precs'). (#5203) +* Rename `conda_build.environ._display_actions('actions' -> 'precs'). (#5203) +* Rename `conda_build.inspect.check_install('platform' -> 'subdir')`. (#5203) +* Rename `conda_build.render.execute_download_actions('actions' -> 'precs')`. (#5203) +* Rename `conda_build.render.get_upstream_pins('actions' -> 'precs')`. (#5203) +* Remove `conda_build.cli.main_render.execute(print_results)`. (#5203) +* Remove `conda_build.conda_interface.Dist`. (#5203) +* Remove `conda_build.conda_interface.display_actions`. (#5203) +* Remove `conda_build.conda_interface.execute_actions`. (#5203) +* Remove `conda_build.conda_interface.execute_plan`. (#5203) +* Remove `conda_build.conda_interface.install_actions`. (#5203) +* Remove `conda_build.conda_interface.linked`. (#5203) +* Remove `conda_build.conda_interface.linked_data`. (#5203) +* Remove `conda_build.conda_interface.package_cache`. (#5203) +* Remove `conda_build.environ.get_install_actions`. Use `conda_build.environ.get_package_records` instead. (#5203) +* Remove `conda_build.index._determine_namespace`. (#5203) +* Remove `conda_build.index._make_seconds`. (#5203) +* Remove `conda_build.index.REPODATA_VERSION`. (#5203) +* Remove `conda_build.index.CHANNELDATA_VERSION`. (#5203) +* Remove `conda_build.index.REPODATA_JSON_FN`. (#5203) +* Remove `conda_build.index.REPODATA_FROM_PKGS_JSON_FN`. (#5203) +* Remove `conda_build.index.CHANNELDATA_FIELDS`. (#5203) +* Remove `conda_build.index._clear_newline_chars`. (#5203) +* Remove `conda_build.index._get_jinja2_environment`. (#5203) +* Remove `conda_build.index._maybe_write`. (#5203) +* Remove `conda_build.index._make_build_string`. (#5203) +* Remove `conda_build.index._warn_on_missing_dependencies`. (#5203) +* Remove `conda_build.index._cache_post_install_details`. (#5203) +* Remove `conda_build.index._cache_recipe`. (#5203) +* Remove `conda_build.index._cache_run_exports`. (#5203) +* Remove `conda_build.index._cache_icon`. (#5203) +* Remove `conda_build.index._make_subdir_index_html`. (#5203) +* Remove `conda_build.index._make_channeldata_index_html`. (#5203) +* Remove `conda_build.index._get_source_repo_git_info`. (#5203) +* Remove `conda_build.index._cache_info_file`. (#5203) +* Remove `conda_build.index._alternate_file_extension`. (#5203) +* Remove `conda_build.index._get_resolve_object`. (#5203) +* Remove `conda_build.index._get_newest_versions`. (#5203) +* Remove `conda_build.index._add_missing_deps`. (#5203) +* Remove `conda_build.index._add_prev_ver_for_features`. (#5203) +* Remove `conda_build.index._shard_newest_packages`. (#5203) +* Remove `conda_build.index._build_current_repodata`. (#5203) +* Remove `conda_build.index.ChannelIndex`. (#5203) +* Remove `conda_build.inspect.check_install('prepend')`. (#5203) +* Remove `conda_build.inspect.check_install('minimal_hint')`. (#5203) +* Remove `conda_build.noarch_python.ISWIN`. Use `conda_build.utils.on_win` instead. (#5203) +* Remove `conda_build.noarch_python._force_dir`. Use `os.makedirs(exist_ok=True)` instead. (#5203) +* Remove `conda_build.noarch_python._error_exit`. (#5203) +* Remove `conda_build.render.actions_to_pins`. (#5203) +* Remove `conda_build.utils.linked_data_no_multichannels`. (#5203) +* Mark `conda_build.api.get_output_file_path` as deprecated. Use `conda_build.api.get_output_file_paths` instead. (#5208) +* Mark `conda_build.environ.Environment` as deprecated. Use `conda.core.prefix_data.PrefixData` instead. (#5219) +* Mark `conda_build.conda_interface.get_version_from_git_tag` as deprecated. Use `conda_build.environ.get_version_from_git_tag` instead. (#5221) + +### Docs + +* Update advice for installing conda-build into base environment. (#5223) + +### Other + +* Add a check to print an additional warning and return an empty string when bits is "arm64" in `msvc_env_cmd`. (#4867) + +### Contributors + +* @beeankha +* @conda-bot +* @dholth +* @finnagin made their first contribution in https://github.com/conda/conda-build/pull/4867 +* @kathatherine +* @kenodegard +* @mbargull +* @minrk +* @ryanskeith +* @travishathaway +* @pre-commit-ci[bot] + + + ## 24.1.2 (2024-02-15) ### Bug fixes diff --git a/HOW_WE_USE_GITHUB.md b/HOW_WE_USE_GITHUB.md index d0a4f4266f..46a13ecd98 100644 --- a/HOW_WE_USE_GITHUB.md +++ b/HOW_WE_USE_GITHUB.md @@ -225,7 +225,7 @@ This is a duplicate of [link to primary issue]; please feel free to conti
 
 Please uninstall your current version of `conda` and reinstall the latest version.
-Feel free to use either the [miniconda](https://docs.conda.io/en/latest/miniconda.html)
+Feel free to use either the [miniconda](https://docs.anaconda.com/free/miniconda/)
 or [anaconda](https://www.anaconda.com/products/individual) installer,
 whichever is more appropriate for your needs.
 
diff --git a/conda_build/__init__.py b/conda_build/__init__.py index 91367d0d86..6b43ca6180 100644 --- a/conda_build/__init__.py +++ b/conda_build/__init__.py @@ -1,5 +1,6 @@ # Copyright (C) 2014 Anaconda, Inc # SPDX-License-Identifier: BSD-3-Clause + from .__version__ import __version__ __all__ = ["__version__"] @@ -15,3 +16,17 @@ "render", "skeleton", ] + +# Skip context logic for doc generation since we don't install all dependencies in the CI doc build environment, +# see .readthedocs.yml file +try: + import os + + from conda.base.context import reset_context + + # Disallow softlinks. This avoids a lot of dumb issues, at the potential cost of disk space. + os.environ["CONDA_ALLOW_SOFTLINKS"] = "false" + reset_context() + +except ImportError: + pass diff --git a/conda_build/__version__.py b/conda_build/__version__.py index e835e1be9d..e664582d94 100644 --- a/conda_build/__version__.py +++ b/conda_build/__version__.py @@ -6,6 +6,7 @@ Conda-build abides by CEP-8 which specifies using CalVer, so the dev version is: YY.MM.MICRO.devN+gHASH[.dirty] """ + try: from setuptools_scm import get_version diff --git a/conda_build/_link.py b/conda_build/_link.py index 21ea66aaed..af841c0275 100644 --- a/conda_build/_link.py +++ b/conda_build/_link.py @@ -4,6 +4,7 @@ This is code that is added to noarch Python packages. See conda_build/noarch_python.py. """ + from __future__ import annotations import os diff --git a/conda_build/api.py b/conda_build/api.py index 8a1298bbe9..f83e235354 100644 --- a/conda_build/api.py +++ b/conda_build/api.py @@ -8,6 +8,7 @@ Design philosophy: put variability into config. Make each function here accept kwargs, but only use those kwargs in config. Config must change to support new features elsewhere. """ + from __future__ import annotations # imports are done locally to keep the api clean and limited strictly @@ -20,13 +21,13 @@ # make the Config class available in the api namespace from .config import DEFAULT_PREFIX_LENGTH as _prefix_length from .config import Config, get_channel_urls, get_or_merge_config +from .deprecations import deprecated from .utils import ( CONDA_PACKAGE_EXTENSIONS, LoggingContext, ensure_list, expand_globs, find_recipe, - get_logger, get_skip_message, on_win, ) @@ -168,6 +169,7 @@ def get_output_file_paths( return sorted(list(set(outs))) +@deprecated("24.3.0", "24.5.0", addendum="Use `get_output_file_paths` instead.") def get_output_file_path( recipe_path_or_metadata, no_download_source=False, @@ -180,12 +182,6 @@ def get_output_file_path( Both split packages (recipes with more than one output) and build matrices, created with variants, contribute to the list of file paths here. """ - log = get_logger(__name__) - log.warn( - "deprecation warning: this function has been renamed to get_output_file_paths, " - "to reflect that potentially multiple paths are returned. This function will be " - "removed in the conda-build 4.0 release." - ) return get_output_file_paths( recipe_path_or_metadata, no_download_source=no_download_source, diff --git a/conda_build/build.py b/conda_build/build.py index 087e932f81..09643c8f18 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -3,6 +3,7 @@ """ Module that does most of the heavy lifting for the ``conda build`` command. """ + import fnmatch import json import os @@ -22,6 +23,9 @@ import yaml from bs4 import UnicodeDammit from conda import __version__ as conda_version +from conda.base.context import context, reset_context +from conda.core.prefix_data import PrefixData +from conda.models.channel import Channel from . import __version__ as conda_build_version from . import environ, noarch_python, source, tarcheck, utils @@ -34,14 +38,8 @@ PathType, TemporaryDirectory, UnsatisfiableError, - context, env_path_backup_var_exists, - get_conda_channel, - get_rc_urls, - pkgs_dirs, prefix_placeholder, - reset_context, - root_dir, url_path, ) from .config import Config @@ -1333,7 +1331,7 @@ def record_prefix_files(m, files_with_prefix): def sanitize_channel(channel): - return get_conda_channel(channel).urls(with_credentials=False, subdirs=[""])[0] + return Channel.from_value(channel).urls(with_credentials=False, subdirs=[""])[0] def write_info_files_file(m, files): @@ -1405,7 +1403,7 @@ def write_about_json(m): # conda env will be in most, but not necessarily all installations. # Don't die if we don't see it. stripped_channels = [] - for channel in get_rc_urls() + list(m.config.channel_urls): + for channel in (*context.channels, *m.config.channel_urls): stripped_channels.append(sanitize_channel(channel)) d["channels"] = stripped_channels evars = ["CIO_TEST"] @@ -1421,8 +1419,10 @@ def write_about_json(m): m.config.extra_meta, ) extra.update(m.config.extra_meta) - env = environ.Environment(root_dir) - d["root_pkgs"] = env.package_specs() + d["root_pkgs"] = [ + f"{prec.name} {prec.version} {prec.build}" + for prec in PrefixData(context.root_dir).iter_records() + ] # Include the extra section of the metadata in the about.json d["extra"] = extra json.dump(d, fo, indent=2, sort_keys=True) @@ -3385,7 +3385,7 @@ def test( and recipedir_or_package_or_metadata.endswith(CONDA_PACKAGE_EXTENSIONS) and any( os.path.dirname(recipedir_or_package_or_metadata) in pkgs_dir - for pkgs_dir in pkgs_dirs + for pkgs_dir in context.pkgs_dirs ) ) if not in_pkg_cache: @@ -4157,8 +4157,10 @@ def is_package_built(metadata, env, include_local=True): _delegated_update_index(d, verbose=metadata.config.debug, warn=False, threads=1) subdir = getattr(metadata.config, f"{env}_subdir") - urls = [url_path(metadata.config.output_folder), "local"] if include_local else [] - urls += get_rc_urls() + urls = [ + *([url_path(metadata.config.output_folder), "local"] if include_local else []), + *context.channels, + ] if metadata.config.channel_urls: urls.extend(metadata.config.channel_urls) diff --git a/conda_build/cli/main_build.py b/conda_build/cli/main_build.py index de698df22c..bdcaaa25d6 100644 --- a/conda_build/cli/main_build.py +++ b/conda_build/cli/main_build.py @@ -13,10 +13,11 @@ from typing import TYPE_CHECKING from conda.auxlib.ish import dals +from conda.base.context import context from conda.common.io import dashlist from .. import api, build, source, utils -from ..conda_interface import add_parser_channels, binstar_upload, cc_conda_build +from ..conda_interface import add_parser_channels, cc_conda_build from ..config import ( get_channel_urls, get_or_merge_config, @@ -55,14 +56,14 @@ def parse_args(args: Sequence[str] | None) -> tuple[ArgumentParser, Namespace]: action="store_false", help="Do not ask to upload the package to anaconda.org.", dest="anaconda_upload", - default=binstar_upload, + default=context.binstar_upload, ) parser.add_argument( "--no-binstar-upload", action="store_false", help=argparse.SUPPRESS, dest="anaconda_upload", - default=binstar_upload, + default=context.binstar_upload, ) parser.add_argument( "--no-include-recipe", diff --git a/conda_build/cli/main_metapackage.py b/conda_build/cli/main_metapackage.py index b295b4130e..a11c581702 100644 --- a/conda_build/cli/main_metapackage.py +++ b/conda_build/cli/main_metapackage.py @@ -6,8 +6,10 @@ import logging from typing import TYPE_CHECKING +from conda.base.context import context + from .. import api -from ..conda_interface import ArgumentParser, add_parser_channels, binstar_upload +from ..conda_interface import ArgumentParser, add_parser_channels if TYPE_CHECKING: from argparse import Namespace @@ -35,14 +37,14 @@ def parse_args(args: Sequence[str] | None) -> tuple[ArgumentParser, Namespace]: action="store_false", help="Do not ask to upload the package to anaconda.org.", dest="anaconda_upload", - default=binstar_upload, + default=context.binstar_upload, ) parser.add_argument( "--no-binstar-upload", action="store_false", help=argparse.SUPPRESS, dest="anaconda_upload", - default=binstar_upload, + default=context.binstar_upload, ) parser.add_argument("--token", help="Token to pass through to anaconda upload") parser.add_argument( diff --git a/conda_build/conda_interface.py b/conda_build/conda_interface.py index 3f25e89591..bb92f6b8b3 100644 --- a/conda_build/conda_interface.py +++ b/conda_build/conda_interface.py @@ -7,9 +7,10 @@ from functools import partial from importlib import import_module # noqa: F401 -from conda import __version__ as CONDA_VERSION # noqa: F401 -from conda.base.context import context, determine_target_prefix, reset_context +from conda import __version__ +from conda.base.context import context, determine_target_prefix from conda.base.context import non_x86_machines as non_x86_linux_machines # noqa: F401 +from conda.base.context import reset_context as _reset_context from conda.core.package_cache import ProgressiveFetchExtract # noqa: F401 from conda.exceptions import ( # noqa: F401 CondaError, @@ -58,31 +59,107 @@ walk_prefix, win_path_to_unix, ) +from conda.exports import get_index as _get_index from conda.gateways.disk.read import compute_sum from conda.models.channel import get_conda_build_local_url # noqa: F401 from .deprecations import deprecated -# TODO: Go to references of all properties below and import them from `context` instead -binstar_upload = context.binstar_upload -default_python = context.default_python -envs_dirs = context.envs_dirs -pkgs_dirs = list(context.pkgs_dirs) -cc_platform = context.platform -root_dir = context.root_dir -root_writable = context.root_writable -subdir = context.subdir -create_default_packages = context.create_default_packages - -get_rc_urls = lambda: list(context.channels) -get_prefix = partial(determine_target_prefix, context) -cc_conda_build = context.conda_build if hasattr(context, "conda_build") else {} +deprecated.constant("24.1.0", "24.5.0", "get_index", _get_index) +deprecated.constant( + "24.5", + "24.7", + "reset_context", + _reset_context, + addendum="Use `conda.base.context.reset_context` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "binstar_upload", + context.binstar_upload, + addendum="Use `conda.base.context.context.binstar_upload` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "default_python", + context.default_python, + addendum="Use `conda.base.context.context.default_python` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "envs_dirs", + context.envs_dirs, + addendum="Use `conda.base.context.context.envs_dirs` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "pkgs_dirs", + list(context.pkgs_dirs), + addendum="Use `conda.base.context.context.pkgs_dirs` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "cc_platform", + context.platform, + addendum="Use `conda.base.context.context.platform` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "root_dir", + context.root_dir, + addendum="Use `conda.base.context.context.root_dir` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "root_writable", + context.root_writable, + addendum="Use `conda.base.context.context.root_writable` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "subdir", + context.subdir, + addendum="Use `conda.base.context.context.subdir` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "create_default_packages", + context.create_default_packages, + addendum="Use `conda.base.context.context.create_default_packages` instead.", +) -get_conda_channel = Channel.from_value +deprecated.constant( + "24.5", + "24.7", + "get_rc_urls", + lambda: list(context.channels), + addendum="Use `conda.base.context.context.channels` instead.", +) +deprecated.constant( + "24.5", + "24.7", + "get_prefix", + partial(determine_target_prefix, context), + addendum="Use `conda.base.context.context.target_prefix` instead.", +) +cc_conda_build = context.conda_build if hasattr(context, "conda_build") else {} -# Disallow softlinks. This avoids a lot of dumb issues, at the potential cost of disk space. -os.environ["CONDA_ALLOW_SOFTLINKS"] = "false" -reset_context() +deprecated.constant( + "24.5", + "24.7", + "get_conda_channel", + Channel.from_value, + addendum="Use `conda.models.channel.Channel.from_value` instead.", +) # When deactivating envs (e.g. switching from root to build/test) this env var is used, # except the PR that removed this has been reverted (for now) and Windows doesn't need it. @@ -116,6 +193,15 @@ def md5_file(path: str | os.PathLike) -> str: return compute_sum(path, "md5") +deprecated.constant( + "24.5", + "24.7", + "CONDA_VERSION", + __version__, + addendum="Use `conda.__version__` instead.", +) + + @deprecated( "24.3", "24.5", diff --git a/conda_build/config.py b/conda_build/config.py index 5d4ba590d3..6e6cc8e28c 100644 --- a/conda_build/config.py +++ b/conda_build/config.py @@ -3,6 +3,7 @@ """ Module to store conda build settings. """ + from __future__ import annotations import copy @@ -15,15 +16,9 @@ from os.path import abspath, expanduser, expandvars, join from typing import TYPE_CHECKING -from .conda_interface import ( - binstar_upload, - cc_conda_build, - cc_platform, - root_dir, - root_writable, - subdir, - url_path, -) +from conda.base.context import context + +from .conda_interface import cc_conda_build, url_path from .utils import ( get_build_folders, get_conda_operation_locks, @@ -87,7 +82,7 @@ def set_invocation_time(): def _get_default_settings(): return [ Setting("activate", True), - Setting("anaconda_upload", binstar_upload), + Setting("anaconda_upload", context.binstar_upload), Setting("force_upload", True), Setting("channel_urls", []), Setting("dirty", False), @@ -321,7 +316,7 @@ def set_lang(variant, lang): def arch(self): """Always the native (build system) arch, except when pretending to be some other platform""" - return self._arch or subdir.rsplit("-", 1)[1] + return self._arch or context.subdir.rsplit("-", 1)[1] @arch.setter def arch(self, value): @@ -337,7 +332,7 @@ def arch(self, value): def platform(self): """Always the native (build system) OS, except when pretending to be some other platform""" - return self._platform or subdir.rsplit("-", 1)[0] + return self._platform or context.subdir.rsplit("-", 1)[0] @platform.setter def platform(self, value): @@ -380,8 +375,8 @@ def noarch(self): return self.host_platform == "noarch" def reset_platform(self): - if not self.platform == cc_platform: - self.platform = cc_platform + if not self.platform == context.platform: + self.platform = context.platform @property def subdir(self): @@ -459,8 +454,8 @@ def croot(self) -> str: self._croot = abspath(expanduser(_bld_root_env)) elif _bld_root_rc: self._croot = abspath(expanduser(expandvars(_bld_root_rc))) - elif root_writable: - self._croot = join(root_dir, "conda-bld") + elif context.root_writable: + self._croot = join(context.root_dir, "conda-bld") else: self._croot = abspath(expanduser("~/conda-bld")) return self._croot @@ -717,7 +712,7 @@ def bldpkgs_dirs(self): # subdir should be the native platform, while self.subdir would be the host platform. return { join(self.croot, self.host_subdir), - join(self.croot, subdir), + join(self.croot, context.subdir), join(self.croot, "noarch"), } diff --git a/conda_build/convert.py b/conda_build/convert.py index c2882d1508..793f0dc93c 100644 --- a/conda_build/convert.py +++ b/conda_build/convert.py @@ -3,6 +3,7 @@ """ Tools for converting conda packages """ + import glob import hashlib import json diff --git a/conda_build/create_test.py b/conda_build/create_test.py index 1788bbe97d..1a8a0f1c34 100644 --- a/conda_build/create_test.py +++ b/conda_build/create_test.py @@ -3,6 +3,7 @@ """ Module to handle generating test files. """ + from __future__ import annotations import json diff --git a/conda_build/deprecations.py b/conda_build/deprecations.py index 67ceb59c7f..494f0f85f1 100644 --- a/conda_build/deprecations.py +++ b/conda_build/deprecations.py @@ -1,6 +1,7 @@ # Copyright (C) 2014 Anaconda, Inc # SPDX-License-Identifier: BSD-3-Clause """Tools to aid in deprecating code.""" + from __future__ import annotations import sys diff --git a/conda_build/environ.py b/conda_build/environ.py index 4fe68add36..a6fca61837 100644 --- a/conda_build/environ.py +++ b/conda_build/environ.py @@ -24,6 +24,7 @@ DEFAULTS_CHANNEL_NAME, UNKNOWN_CHANNEL, ) +from conda.base.context import context, reset_context from conda.common.io import env_vars from conda.core.index import LAST_CHANNEL_URLS from conda.core.link import PrefixSetup, UnlinkLinkTransaction @@ -46,11 +47,6 @@ PackageRecord, ProgressiveFetchExtract, TemporaryDirectory, - context, - create_default_packages, - pkgs_dirs, - reset_context, - root_dir, ) from .deprecations import deprecated from .exceptions import BuildLockError, DependencyNeedsBuildingError @@ -425,7 +421,7 @@ def conda_build_vars(prefix, config): "HTTP_PROXY": os.getenv("HTTP_PROXY", ""), "REQUESTS_CA_BUNDLE": os.getenv("REQUESTS_CA_BUNDLE", ""), "DIRTY": "1" if config.dirty else "", - "ROOT": root_dir, + "ROOT": context.root_dir, } @@ -827,18 +823,21 @@ def os_vars(m, prefix): return d +@deprecated("24.3", "24.5") class InvalidEnvironment(Exception): pass # Stripped-down Environment class from conda-tools ( https://github.com/groutr/conda-tools ) # Vendored here to avoid the whole dependency for just this bit. +@deprecated("24.3", "24.5") def _load_json(path): with open(path) as fin: x = json.load(fin) return x +@deprecated("24.3", "24.5") def _load_all_json(path): """ Load all json files in a directory. Return dictionary with filenames mapped to json @@ -852,6 +851,7 @@ def _load_all_json(path): return result +@deprecated("24.3", "24.5", addendum="Use `conda.core.prefix_data.PrefixData` instead.") class Environment: def __init__(self, path): """ @@ -917,7 +917,7 @@ def get_install_actions( conda_log_level = logging.WARN specs = list(specs) if specs: - specs.extend(create_default_packages) + specs.extend(context.create_default_packages) if verbose or debug: capture = contextlib.nullcontext if debug: @@ -989,7 +989,7 @@ def get_install_actions( pkg_dir = str(exc) folder = 0 while ( - os.path.dirname(pkg_dir) not in pkgs_dirs + os.path.dirname(pkg_dir) not in context.pkgs_dirs and folder < 20 ): pkg_dir = os.path.dirname(pkg_dir) @@ -999,7 +999,7 @@ def get_install_actions( "Removing the folder and retrying", pkg_dir, ) - if pkg_dir in pkgs_dirs and os.path.isdir(pkg_dir): + if pkg_dir in context.pkgs_dirs and os.path.isdir(pkg_dir): utils.rm_rf(pkg_dir) if retries < max_env_retry: log.warn( @@ -1190,7 +1190,10 @@ def create_env( with utils.try_acquire_locks(locks, timeout=config.timeout): pkg_dir = str(exc) folder = 0 - while os.path.dirname(pkg_dir) not in pkgs_dirs and folder < 20: + while ( + os.path.dirname(pkg_dir) not in context.pkgs_dirs + and folder < 20 + ): pkg_dir = os.path.dirname(pkg_dir) folder += 1 log.warn( @@ -1264,9 +1267,9 @@ def get_pkg_dirs_locks(dirs, config): def clean_pkg_cache(dist: str, config: Config) -> None: with utils.LoggingContext(logging.DEBUG if config.debug else logging.WARN): - locks = get_pkg_dirs_locks([config.bldpkgs_dir] + pkgs_dirs, config) + locks = get_pkg_dirs_locks((config.bldpkgs_dir, *context.pkgs_dirs), config) with utils.try_acquire_locks(locks, timeout=config.timeout): - for pkgs_dir in pkgs_dirs: + for pkgs_dir in context.pkgs_dirs: if any( os.path.exists(os.path.join(pkgs_dir, f"{dist}{ext}")) for ext in ("", *CONDA_PACKAGE_EXTENSIONS) @@ -1282,7 +1285,7 @@ def clean_pkg_cache(dist: str, config: Config) -> None: # Note that this call acquires the relevant locks, so this must be called # outside the lock context above. - remove_existing_packages(pkgs_dirs, [dist], config) + remove_existing_packages(context.pkgs_dirs, [dist], config) def remove_existing_packages(dirs, fns, config): diff --git a/conda_build/index.py b/conda_build/index.py index c3968d238a..e4d07a52ff 100644 --- a/conda_build/index.py +++ b/conda_build/index.py @@ -8,13 +8,20 @@ from functools import partial from os.path import dirname +from conda.base.context import context from conda.core.index import get_index from conda_index.index import update_index as _update_index -from . import conda_interface, utils -from .conda_interface import CondaHTTPError, context, url_path +from . import utils +from .conda_interface import CondaHTTPError, url_path from .deprecations import deprecated -from .utils import JSONDecodeError, get_logger, on_win +from .utils import ( + CONDA_PACKAGE_EXTENSION_V1, + CONDA_PACKAGE_EXTENSION_V2, + JSONDecodeError, + get_logger, + on_win, +) log = get_logger(__name__) @@ -122,7 +129,7 @@ def get_build_index( # native content and the noarch content. if subdir == "noarch": - subdir = conda_interface.subdir + subdir = context.subdir try: # get_index() is like conda reading the index, not conda_index # creating a new index. @@ -243,3 +250,54 @@ def _delegated_update_index( current_index_versions=current_index_versions, debug=debug, ) + + +@deprecated( + "24.1.0", "24.5.0", addendum="Use `conda_index._apply_instructions` instead." +) +def _apply_instructions(subdir, repodata, instructions): + repodata.setdefault("removed", []) + utils.merge_or_update_dict( + repodata.get("packages", {}), + instructions.get("packages", {}), + merge=False, + add_missing_keys=False, + ) + # we could have totally separate instructions for .conda than .tar.bz2, but it's easier if we assume + # that a similarly-named .tar.bz2 file is the same content as .conda, and shares fixes + new_pkg_fixes = { + k.replace(CONDA_PACKAGE_EXTENSION_V1, CONDA_PACKAGE_EXTENSION_V2): v + for k, v in instructions.get("packages", {}).items() + } + + utils.merge_or_update_dict( + repodata.get("packages.conda", {}), + new_pkg_fixes, + merge=False, + add_missing_keys=False, + ) + utils.merge_or_update_dict( + repodata.get("packages.conda", {}), + instructions.get("packages.conda", {}), + merge=False, + add_missing_keys=False, + ) + + for fn in instructions.get("revoke", ()): + for key in ("packages", "packages.conda"): + if fn.endswith(CONDA_PACKAGE_EXTENSION_V1) and key == "packages.conda": + fn = fn.replace(CONDA_PACKAGE_EXTENSION_V1, CONDA_PACKAGE_EXTENSION_V2) + if fn in repodata[key]: + repodata[key][fn]["revoked"] = True + repodata[key][fn]["depends"].append("package_has_been_revoked") + + for fn in instructions.get("remove", ()): + for key in ("packages", "packages.conda"): + if fn.endswith(CONDA_PACKAGE_EXTENSION_V1) and key == "packages.conda": + fn = fn.replace(CONDA_PACKAGE_EXTENSION_V1, CONDA_PACKAGE_EXTENSION_V2) + popped = repodata[key].pop(fn, None) + if popped: + repodata["removed"].append(fn) + repodata["removed"].sort() + + return repodata diff --git a/conda_build/inspect_pkg.py b/conda_build/inspect_pkg.py index 7a9985fc8a..7d7c61f8f9 100644 --- a/conda_build/inspect_pkg.py +++ b/conda_build/inspect_pkg.py @@ -14,11 +14,11 @@ from typing import TYPE_CHECKING from conda.api import Solver +from conda.base.context import context from conda.core.index import get_index from conda.core.prefix_data import PrefixData from conda.models.records import PrefixRecord -from . import conda_interface from .conda_interface import ( specs_from_args, ) @@ -104,14 +104,14 @@ def check_install( Solver( prefix, channel_urls, - [subdir or conda_interface.subdir], + [subdir or context.subdir], specs_from_args(packages), ).solve_for_transaction(ignore_pinned=True).print_transaction_summary() def print_linkages( depmap: dict[ - PrefixRecord | Literal["not found" | "system" | "untracked"], + PrefixRecord | Literal["not found", "system", "untracked"], list[tuple[str, str, str]], ], show_files: bool = False, @@ -217,7 +217,7 @@ def inspect_linkages( untracked: bool = False, all_packages: bool = False, show_files: bool = False, - groupby: Literal["package" | "dependency"] = "package", + groupby: Literal["package", "dependency"] = "package", sysroot="", ): if not packages and not untracked and not all_packages: diff --git a/conda_build/jinja_context.py b/conda_build/jinja_context.py index 2ef9b910d1..cc5c3b24c7 100644 --- a/conda_build/jinja_context.py +++ b/conda_build/jinja_context.py @@ -73,39 +73,13 @@ def __init__( # Using any of these methods on an Undefined variable # results in another Undefined variable. - __add__ = ( - __radd__ - ) = ( - __mul__ - ) = ( - __rmul__ - ) = ( - __div__ - ) = ( - __rdiv__ - ) = ( - __truediv__ - ) = ( + __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = __truediv__ = ( __rtruediv__ - ) = ( - __floordiv__ - ) = ( - __rfloordiv__ - ) = ( - __mod__ - ) = ( - __rmod__ - ) = ( - __pos__ - ) = ( - __neg__ - ) = ( + ) = __floordiv__ = __rfloordiv__ = __mod__ = __rmod__ = __pos__ = __neg__ = ( __call__ - ) = ( - __getitem__ - ) = __lt__ = __le__ = __gt__ = __ge__ = __complex__ = __pow__ = __rpow__ = ( - lambda self, *args, **kwargs: self._return_undefined(self._undefined_name) - ) + ) = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __complex__ = __pow__ = ( + __rpow__ + ) = lambda self, *args, **kwargs: self._return_undefined(self._undefined_name) # Accessing an attribute of an Undefined variable # results in another Undefined variable. diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 633b6de8fc..01f3367d03 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -16,10 +16,11 @@ from typing import TYPE_CHECKING, overload from bs4 import UnicodeDammit +from conda.base.context import context from conda.gateways.disk.read import compute_sum from . import exceptions, utils, variants -from .conda_interface import MatchSpec, envs_dirs +from .conda_interface import MatchSpec from .config import Config, get_or_merge_config from .features import feature_list from .license_family import ensure_valid_license_family @@ -781,7 +782,7 @@ def build_string_from_metadata(metadata): # but we don't presently have an API there. def _get_env_path(env_name_or_path): if not os.path.isdir(env_name_or_path): - for envs_dir in list(envs_dirs) + [os.getcwd()]: + for envs_dir in list(context.envs_dirs) + [os.getcwd()]: path = os.path.join(envs_dir, env_name_or_path) if os.path.isdir(path): env_name_or_path = path @@ -1341,8 +1342,7 @@ def fromdict(cls, metadata, config=None, variant=None): return m @overload - def get_section(self, section: Literal["source", "outputs"]) -> list[dict]: - ... + def get_section(self, section: Literal["source", "outputs"]) -> list[dict]: ... @overload def get_section( @@ -1356,8 +1356,7 @@ def get_section( "about", "extra", ], - ) -> dict: - ... + ) -> dict: ... def get_section(self, name): section = self.meta.get(name) @@ -2553,9 +2552,9 @@ def get_output_metadata_set( ) ] = (out, out_metadata) out_metadata_map[HashableDict(out)] = out_metadata - ref_metadata.other_outputs = ( - out_metadata.other_outputs - ) = all_output_metadata + ref_metadata.other_outputs = out_metadata.other_outputs = ( + all_output_metadata + ) except SystemExit: if not permit_undefined_jinja: raise diff --git a/conda_build/os_utils/external.py b/conda_build/os_utils/external.py index 8b84833c00..f1d91d098b 100644 --- a/conda_build/os_utils/external.py +++ b/conda_build/os_utils/external.py @@ -5,7 +5,8 @@ from glob import glob from os.path import expanduser, isfile, join -from ..conda_interface import root_dir +from conda.base.context import context + from ..utils import on_win @@ -16,10 +17,10 @@ def find_executable(executable, prefix=None, all_matches=False): result = None if on_win: dir_paths = [ - join(root_dir, "Scripts"), - join(root_dir, "Library\\mingw-w64\\bin"), - join(root_dir, "Library\\usr\\bin"), - join(root_dir, "Library\\bin"), + join(context.root_dir, "Scripts"), + join(context.root_dir, "Library\\mingw-w64\\bin"), + join(context.root_dir, "Library\\usr\\bin"), + join(context.root_dir, "Library\\bin"), ] if prefix: dir_paths[0:0] = [ @@ -30,7 +31,7 @@ def find_executable(executable, prefix=None, all_matches=False): ] else: dir_paths = [ - join(root_dir, "bin"), + join(context.root_dir, "bin"), ] if prefix: dir_paths.insert(0, join(prefix, "bin")) diff --git a/conda_build/os_utils/liefldd.py b/conda_build/os_utils/liefldd.py index 9f358f619a..9b14454c4f 100644 --- a/conda_build/os_utils/liefldd.py +++ b/conda_build/os_utils/liefldd.py @@ -27,6 +27,16 @@ lief.logging.disable() have_lief = True + try: + PE_HEADER_CHARACTERISTICS = lief.PE.Header.CHARACTERISTICS + except AttributeError: + # Fallback for lief<0.14. + PE_HEADER_CHARACTERISTICS = lief.PE.HEADER_CHARACTERISTICS + try: + EXE_FORMATS = lief.Binary.FORMATS + except AttributeError: + # Fallback for lief<0.14. + EXE_FORMATS = lief.EXE_FORMATS except ImportError: have_lief = False @@ -78,15 +88,15 @@ def codefile_class( if not (binary := ensure_binary(path)): return None elif ( - binary.format == lief.EXE_FORMATS.PE - and lief.PE.HEADER_CHARACTERISTICS.DLL in binary.header.characteristics_list + binary.format == EXE_FORMATS.PE + and PE_HEADER_CHARACTERISTICS.DLL in binary.header.characteristics_list ): return DLLfile - elif binary.format == lief.EXE_FORMATS.PE: + elif binary.format == EXE_FORMATS.PE: return EXEfile - elif binary.format == lief.EXE_FORMATS.MACHO: + elif binary.format == EXE_FORMATS.MACHO: return machofile - elif binary.format == lief.EXE_FORMATS.ELF: + elif binary.format == EXE_FORMATS.ELF: return elffile else: return None @@ -105,7 +115,7 @@ def get_libraries(file): result = [] binary = ensure_binary(file) if binary: - if binary.format == lief.EXE_FORMATS.PE: + if binary.format == EXE_FORMATS.PE: result = binary.libraries else: result = [ @@ -113,7 +123,7 @@ def get_libraries(file): ] # LIEF returns LC_ID_DYLIB name @rpath/libbz2.dylib in binary.libraries. Strip that. binary_name = None - if binary.format == lief.EXE_FORMATS.MACHO: + if binary.format == EXE_FORMATS.MACHO: binary_name = [ command.name for command in binary.commands @@ -174,7 +184,7 @@ def get_rpathy_thing_raw_partial(file, elf_attribute, elf_dyn_tag): rpaths = [] if binary: binary_format = binary.format - if binary_format == lief.EXE_FORMATS.ELF: + if binary_format == EXE_FORMATS.ELF: binary_type = binary.type if ( binary_type == lief.ELF.ELF_CLASS.CLASS32 @@ -182,7 +192,7 @@ def get_rpathy_thing_raw_partial(file, elf_attribute, elf_dyn_tag): ): rpaths = _get_elf_rpathy_thing(binary, elf_attribute, elf_dyn_tag) elif ( - binary_format == lief.EXE_FORMATS.MACHO + binary_format == EXE_FORMATS.MACHO and binary.has_rpath and elf_dyn_tag == lief.ELF.DYNAMIC_TAGS.RPATH ): @@ -232,7 +242,7 @@ def set_rpath(old_matching, new_rpath, file): binary = ensure_binary(file) if not binary: return - if binary.format == lief.EXE_FORMATS.ELF and ( + if binary.format == EXE_FORMATS.ELF and ( binary.type == lief.ELF.ELF_CLASS.CLASS32 or binary.type == lief.ELF.ELF_CLASS.CLASS64 ): @@ -244,7 +254,7 @@ def set_rpath(old_matching, new_rpath, file): def get_rpaths(file, exe_dirname, envroot, windows_root=""): rpaths, rpaths_type, binary_format, binary_type = get_runpaths_or_rpaths_raw(file) - if binary_format == lief.EXE_FORMATS.PE: + if binary_format == EXE_FORMATS.PE: # To allow the unix-y rpath code to work we consider # exes as having rpaths of env + CONDA_WINDOWS_PATHS # and consider DLLs as having no rpaths. @@ -259,9 +269,9 @@ def get_rpaths(file, exe_dirname, envroot, windows_root=""): rpaths.append("/".join((windows_root, "System32", "downlevel"))) rpaths.append(windows_root) if envroot: - # and not lief.PE.HEADER_CHARACTERISTICS.DLL in binary.header.characteristics_list: + # and not .DLL in binary.header.characteristics_list: rpaths.extend(list(_get_path_dirs(envroot))) - elif binary_format == lief.EXE_FORMATS.MACHO: + elif binary_format == EXE_FORMATS.MACHO: rpaths = [rpath.rstrip("/") for rpath in rpaths] return [from_os_varnames(binary_format, binary_type, rpath) for rpath in rpaths] @@ -299,13 +309,13 @@ def _inspect_linkages_this(filename, sysroot="", arch="native"): def to_os_varnames(binary, input_): """Don't make these functions - they are methods to match the API for elffiles.""" - if binary.format == lief.EXE_FORMATS.MACHO: + if binary.format == EXE_FORMATS.MACHO: return ( input_.replace("$SELFDIR", "@loader_path") .replace("$EXEDIR", "@executable_path") .replace("$RPATH", "@rpath") ) - elif binary.format == lief.EXE_FORMATS.ELF: + elif binary.format == EXE_FORMATS.ELF: if binary.ehdr.sz_ptr == 8: libdir = "/lib64" else: @@ -315,19 +325,19 @@ def to_os_varnames(binary, input_): def from_os_varnames(binary_format, binary_type, input_): """Don't make these functions - they are methods to match the API for elffiles.""" - if binary_format == lief.EXE_FORMATS.MACHO: + if binary_format == EXE_FORMATS.MACHO: return ( input_.replace("@loader_path", "$SELFDIR") .replace("@executable_path", "$EXEDIR") .replace("@rpath", "$RPATH") ) - elif binary_format == lief.EXE_FORMATS.ELF: + elif binary_format == EXE_FORMATS.ELF: if binary_type == lief.ELF.ELF_CLASS.CLASS64: libdir = "/lib64" else: libdir = "/lib" return input_.replace("$ORIGIN", "$SELFDIR").replace("$LIB", libdir) - elif binary_format == lief.EXE_FORMATS.PE: + elif binary_format == EXE_FORMATS.PE: return input_ @@ -344,10 +354,10 @@ def _get_path_dirs(prefix): def get_uniqueness_key(file): binary = ensure_binary(file) if not binary: - return lief.EXE_FORMATS.UNKNOWN - elif binary.format == lief.EXE_FORMATS.MACHO: - return binary.name - elif binary.format == lief.EXE_FORMATS.ELF and ( # noqa + return EXE_FORMATS.UNKNOWN + elif binary.format == EXE_FORMATS.MACHO: + return str(file) + elif binary.format == EXE_FORMATS.ELF and ( # noqa binary.type == lief.ELF.ELF_CLASS.CLASS32 or binary.type == lief.ELF.ELF_CLASS.CLASS64 ): @@ -357,8 +367,8 @@ def get_uniqueness_key(file): ] if result: return result[0] - return binary.name - return binary.name + return str(file) + return str(file) def _get_resolved_location( @@ -467,7 +477,7 @@ def inspect_linkages_lief( default_paths = [] if not binary: default_paths = [] - elif binary.format == lief.EXE_FORMATS.ELF: + elif binary.format == EXE_FORMATS.ELF: if binary.type == lief.ELF.ELF_CLASS.CLASS64: default_paths = [ "$SYSROOT/lib64", @@ -477,9 +487,9 @@ def inspect_linkages_lief( ] else: default_paths = ["$SYSROOT/lib", "$SYSROOT/usr/lib"] - elif binary.format == lief.EXE_FORMATS.MACHO: + elif binary.format == EXE_FORMATS.MACHO: default_paths = ["$SYSROOT/usr/lib"] - elif binary.format == lief.EXE_FORMATS.PE: + elif binary.format == EXE_FORMATS.PE: # We do not include C:\Windows nor C:\Windows\System32 in this list. They are added in # get_rpaths() instead since we need to carefully control the order. default_paths = [ @@ -499,7 +509,7 @@ def inspect_linkages_lief( uniqueness_key = get_uniqueness_key(binary) if uniqueness_key not in already_seen: parent_exe_dirname = None - if binary.format == lief.EXE_FORMATS.PE: + if binary.format == EXE_FORMATS.PE: tmp_filename = filename2 while tmp_filename: if ( @@ -519,7 +529,7 @@ def inspect_linkages_lief( ) tmp_filename = filename2 rpaths_transitive = [] - if binary.format == lief.EXE_FORMATS.PE: + if binary.format == EXE_FORMATS.PE: rpaths_transitive = rpaths_by_binary[tmp_filename] else: while tmp_filename: @@ -534,7 +544,7 @@ def inspect_linkages_lief( "$RPATH/" + lib if not lib.startswith("/") and not lib.startswith("$") - and binary.format != lief.EXE_FORMATS.MACHO # noqa + and binary.format != EXE_FORMATS.MACHO # noqa else lib ) for lib in libraries @@ -556,7 +566,7 @@ def inspect_linkages_lief( # can be run case-sensitively if the user wishes. # """ - if binary.format == lief.EXE_FORMATS.PE: + if binary.format == EXE_FORMATS.PE: import random path_fixed = ( os.path.dirname(path_fixed) diff --git a/conda_build/os_utils/macho.py b/conda_build/os_utils/macho.py index 950ebd6d57..516df7a0a6 100644 --- a/conda_build/os_utils/macho.py +++ b/conda_build/os_utils/macho.py @@ -191,12 +191,10 @@ def find_apple_cctools_executable(name, build_prefix, nofail=False): tool = tool_xcr if os.path.exists(tool): return tool - except Exception as _: # noqa + except Exception: # noqa print( - "ERROR :: Failed to run `{}`. Please use `conda` to install `cctools` into your base environment.\n" - " An option on macOS is to install `Xcode` or `Command Line Tools for Xcode`.".format( - tool - ) + f"ERROR :: Failed to run `{tool}`. Use `conda` to install `cctools` into your base environment.\n" + f" An option on macOS is to install `Xcode` or `Command Line Tools for Xcode`." ) sys.exit(1) return tool diff --git a/conda_build/post.py b/conda_build/post.py index 4512c9e508..eea8a584b6 100644 --- a/conda_build/post.py +++ b/conda_build/post.py @@ -1402,9 +1402,11 @@ def check_overlinking_impl( if diffs: log = utils.get_logger(__name__) log.warning( - "Partially parsed some '.tbd' files in sysroot {}, pretending .tbds are their install-names\n" + "Partially parsed some '.tbd' files in sysroot %s, pretending .tbds are their install-names\n" "Adding support to 'conda-build' for parsing these in 'liefldd.py' would be easy and useful:\n" - "{} ...".format(sysroot, list(diffs)[1:3]) + "%s...", + sysroot, + list(diffs)[1:3], ) sysroots_files[srs] = sysroot_files sysroots_files = OrderedDict( diff --git a/conda_build/render.py b/conda_build/render.py index 9ba417bf23..7e64256dda 100644 --- a/conda_build/render.py +++ b/conda_build/render.py @@ -25,6 +25,7 @@ from pathlib import Path import yaml +from conda.base.context import context from . import environ, exceptions, source, utils from .conda_interface import ( @@ -32,7 +33,6 @@ ProgressiveFetchExtract, TemporaryDirectory, UnsatisfiableError, - pkgs_dirs, specs_from_url, ) from .exceptions import DependencyNeedsBuildingError @@ -247,7 +247,7 @@ def _filter_run_exports(specs, ignore_list): def find_pkg_dir_or_file_in_pkgs_dirs( distribution: str, m: MetaData, files_only: bool = False ) -> str | None: - for cache in map(Path, (*pkgs_dirs, *m.config.bldpkgs_dirs)): + for cache in map(Path, (*context.pkgs_dirs, *m.config.bldpkgs_dirs)): package = cache / (distribution + CONDA_PACKAGE_EXTENSION_V1) if package.is_file(): return str(package) @@ -274,6 +274,7 @@ def find_pkg_dir_or_file_in_pkgs_dirs( archive.add(entry, arcname=entry.name) return str(package) + return None @lru_cache(maxsize=None) @@ -385,7 +386,7 @@ def execute_download_actions(m, precs, env, package_subset=None, require_files=F pfe = ProgressiveFetchExtract(link_prefs=(link_prec,)) with utils.LoggingContext(): pfe.execute() - for pkg_dir in pkgs_dirs: + for pkg_dir in context.pkgs_dirs: _loc = join(pkg_dir, prec.fn) if isfile(_loc): pkg_loc = _loc diff --git a/conda_build/skeletons/cpan.py b/conda_build/skeletons/cpan.py index 891f62f3cb..8d1d996e1b 100644 --- a/conda_build/skeletons/cpan.py +++ b/conda_build/skeletons/cpan.py @@ -3,6 +3,7 @@ """ Tools for converting CPAN packages to conda recipes. """ + import codecs import gzip import hashlib @@ -149,7 +150,6 @@ class InvalidReleaseError(RuntimeError): - """ An exception that is raised when a release is not available on MetaCPAN. """ @@ -158,7 +158,6 @@ class InvalidReleaseError(RuntimeError): class PerlTmpDownload(TmpDownload): - """ Subclass Conda's TmpDownload to replace : in download filenames. Critical on win. diff --git a/conda_build/skeletons/cran.py b/conda_build/skeletons/cran.py index 423941164e..7140c9a89f 100755 --- a/conda_build/skeletons/cran.py +++ b/conda_build/skeletons/cran.py @@ -3,6 +3,7 @@ """ Tools for converting Cran packages to conda recipes. """ + from __future__ import annotations import argparse diff --git a/conda_build/skeletons/pypi.py b/conda_build/skeletons/pypi.py index 92e2ff9efd..f39a5e2318 100644 --- a/conda_build/skeletons/pypi.py +++ b/conda_build/skeletons/pypi.py @@ -3,6 +3,7 @@ """ Tools for converting PyPI packages to conda recipes. """ + import keyword import logging import os @@ -19,13 +20,13 @@ import pkginfo import requests import yaml +from conda.base.context import context from conda.gateways.disk.read import compute_sum from requests.packages.urllib3.util.url import parse_url from ..conda_interface import ( StringIO, configparser, - default_python, download, human_bytes, input, @@ -281,7 +282,8 @@ def skeletonize( if not config: config = Config() - python_version = python_version or config.variant.get("python", default_python) + if not python_version: + python_version = config.variant.get("python", context.default_python) created_recipes = [] while packages: @@ -556,7 +558,7 @@ def add_parser(repos): pypi.add_argument( "--python-version", action="store", - default=default_python, + default=context.default_python, help="""Version of Python to use to run setup.py. Default is %(default)s.""", choices=["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"], ) diff --git a/conda_build/source.py b/conda_build/source.py index 0c25d60c47..f1cfdb2613 100644 --- a/conda_build/source.py +++ b/conda_build/source.py @@ -379,9 +379,8 @@ def git_mirror_checkout_recursive( ) if verbose: print( - "Relative submodule {} found: url is {}, submod_mirror_dir is {}".format( - submod_name, submod_url, submod_mirror_dir - ) + f"Relative submodule {submod_name} found: url is {submod_url}, " + f"submod_mirror_dir is {submod_mirror_dir}" ) with TemporaryDirectory() as temp_checkout_dir: git_mirror_checkout_recursive( diff --git a/conda_build/utils.py b/conda_build/utils.py index 7635c45a6f..24303d9ba5 100644 --- a/conda_build/utils.py +++ b/conda_build/utils.py @@ -53,7 +53,9 @@ CONDA_PACKAGE_EXTENSIONS, KNOWN_SUBDIRS, ) +from conda.base.context import context from conda.gateways.disk.read import compute_sum +from conda.models.channel import Channel from conda.models.match_spec import MatchSpec from .conda_interface import ( @@ -63,11 +65,7 @@ TemporaryDirectory, VersionOrder, cc_conda_build, - context, download, - get_conda_channel, - pkgs_dirs, - root_dir, unix_path_to_win, win_path_to_unix, ) @@ -86,7 +84,7 @@ on_linux = sys.platform == "linux" codec = getpreferredencoding() or "utf-8" -root_script_dir = os.path.join(root_dir, "Scripts" if on_win else "bin") +root_script_dir = os.path.join(context.root_dir, "Scripts" if on_win else "bin") mmap_MAP_PRIVATE = 0 if on_win else mmap.MAP_PRIVATE mmap_PROT_READ = 0 if on_win else mmap.PROT_READ mmap_PROT_WRITE = 0 if on_win else mmap.PROT_WRITE @@ -710,7 +708,7 @@ def merge_tree( # at any time, but the lock within this process should all be tied to the same tracking # mechanism. _lock_folders = ( - os.path.join(root_dir, "locks"), + os.path.join(context.root_dir, "locks"), os.path.expanduser(os.path.join("~", ".conda_build_locks")), ) @@ -754,9 +752,7 @@ def get_conda_operation_locks(locking=True, bldpkgs_dirs=None, timeout=900): bldpkgs_dirs = ensure_list(bldpkgs_dirs) # locks enabled by default if locking: - _pkgs_dirs = pkgs_dirs[:1] - locked_folders = _pkgs_dirs + list(bldpkgs_dirs) - for folder in locked_folders: + for folder in (*context.pkgs_dirs[:1], *bldpkgs_dirs): if not os.path.isdir(folder): os.makedirs(folder) lock = get_lock(folder, timeout=timeout) @@ -1132,8 +1128,9 @@ def convert_path_for_cygwin_or_msys2(exe, path): def get_skip_message(m): - return "Skipped: {} from {} defines build/skip for this configuration ({}).".format( - m.name(), m.path, {k: m.config.variant[k] for k in m.get_used_vars()} + return ( + f"Skipped: {m.name()} from {m.path} defines build/skip for this configuration " + f"({({k: m.config.variant[k] for k in m.get_used_vars()})})." ) @@ -1897,13 +1894,11 @@ def sort_list_in_nested_structure(dictionary, omissions=""): @overload -def ensure_valid_spec(spec: str, warn: bool = False) -> str: - ... +def ensure_valid_spec(spec: str, warn: bool = False) -> str: ... @overload -def ensure_valid_spec(spec: MatchSpec, warn: bool = False) -> MatchSpec: - ... +def ensure_valid_spec(spec: MatchSpec, warn: bool = False) -> MatchSpec: ... def ensure_valid_spec(spec: str | MatchSpec, warn: bool = False) -> str | MatchSpec: @@ -2110,7 +2105,7 @@ def write_bat_activation_text(file_handle, m): def download_channeldata(channel_url): global channeldata_cache if channel_url.startswith("file://") or channel_url not in channeldata_cache: - urls = get_conda_channel(channel_url).urls() + urls = Channel.from_value(channel_url).urls() urls = {url.rsplit("/", 1)[0] for url in urls} data = {} for url in urls: diff --git a/conda_build/variants.py b/conda_build/variants.py index 2ece5f4bd6..c5bbe9a41e 100644 --- a/conda_build/variants.py +++ b/conda_build/variants.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause """This file handles the parsing of feature specifications from files, ending up with a configuration matrix""" + import os.path import re import sys @@ -11,8 +12,9 @@ from itertools import product import yaml +from conda.base.context import context -from .conda_interface import cc_conda_build, subdir +from .conda_interface import cc_conda_build from .utils import ensure_list, get_logger, islist, on_win, trim_empty_keys from .version import _parse as parse_version @@ -84,7 +86,7 @@ }, } -arch_name = subdir.rsplit("-", 1)[-1] +arch_name = context.subdir.rsplit("-", 1)[-1] SUFFIX_MAP = { "PY": "python", @@ -303,15 +305,10 @@ def _combine_spec_dictionaries( ensure_list(v) ): raise ValueError( - "All entries associated by a zip_key " - "field must be the same length. In {}, {} and {} are " - "different ({} and {})".format( - spec_source, - k, - group_item, - len(ensure_list(v)), - len(ensure_list(spec[group_item])), - ) + f"All entries associated by a zip_key " + f"field must be the same length. In {spec_source}, {k} and {group_item} " + f"are different ({len(ensure_list(v))} and " + f"{len(ensure_list(spec[group_item]))})" ) values[group_item] = ensure_list(spec[group_item]) elif k in values: @@ -338,17 +335,10 @@ def _combine_spec_dictionaries( ] if len(missing_subvalues): raise ValueError( - "variant config in {} is ambiguous because it\n" - "does not fully implement all zipped keys (To be clear: missing {})\n" - "or specifies a subspace that is not fully implemented (To be clear:\n" - ".. we did not find {} from {} in {}:{}).".format( - spec_source, - missing_group_items, - missing_subvalues, - spec, - k, - values[k], - ) + f"variant config in {spec_source} is ambiguous because it does not fully " + f"implement all zipped keys (missing {missing_group_items}) or specifies a " + f"subspace that is not fully implemented (we did not find {missing_subvalues} " + f"from {spec} in {k}:{values[k]})." ) return values diff --git a/docs/source/resources/define-metadata.rst b/docs/source/resources/define-metadata.rst index e0e5bfff2d..e4b599c438 100644 --- a/docs/source/resources/define-metadata.rst +++ b/docs/source/resources/define-metadata.rst @@ -116,6 +116,18 @@ If an extracted archive contains only 1 folder at its top level, its contents will be moved 1 level up, so that the extracted package contents sit in the root of the work folder. +You can also specify multiple URLs for the same source archive. +They will be attempted in order, should one fail. + +.. code-block:: yaml + + source: + url: + - https://archive.linux.duke.edu/cran/src/contrib/ggblanket_6.0.0.tar.gz + - https://archive.linux.duke.edu/cran/src/contrib/Archive/ggblanket/ggblanket_6.0.0.tar.gz + sha256: cd2181fe3d3365eaf36ff8bbbc90ea9d76c56d40e63386b4eefa0e3120ec6665 + + Source from git --------------- @@ -125,7 +137,7 @@ The git_url can also be a relative path to the recipe directory. source: git_url: https://github.com/ilanschnell/bsdiff4.git - git_rev: 1.1.4 + git_rev: 1.1.4 # (Defaults to "HEAD") git_depth: 1 # (Defaults to -1/not shallow) The depth argument relates to the ability to perform a shallow clone. @@ -734,7 +746,7 @@ implicitly added by host requirements (e.g. libpng exports libpng), and with - libpng Here, because no specific kind of ``run_exports`` is specified, libpng's ``run_exports`` -are considered "weak." This means they will only apply when libpng is in the +are considered "weak". This means they will only apply when libpng is in the host section, when they will add their export to the run section. If libpng were listed in the build section, the ``run_exports`` would not apply to the run section. @@ -746,6 +758,9 @@ listed in the build section, the ``run_exports`` would not apply to the run sect strong: - libgcc +There is also ``run_exports/weak`` which is equivalent to an unspecific kind of +``run_exports`` but useful if you want to define both strong and weak run exports. + Strong ``run_exports`` are used for things like runtimes, where the same runtime needs to be present in the host and the run environment, and exactly which runtime that should be is determined by what's present in the build section. @@ -1554,9 +1569,17 @@ information displays in the Anaconda.org channel. about: home: https://github.com/ilanschnell/bsdiff4 - license: BSD + license: BSD 3-Clause license_file: LICENSE - summary: binary diff and patch using the BSDIFF4-format + license_family: BSD + license_url: https://github.com/bacchusrx/bsdiff4/blob/master/LICENSE + summary: binary diff and patch using the BSDIFF4 format + description: | + This module provides an interface to the BSDIFF4 format, command line interfaces + (bsdiff4, bspatch4) and tests. + dev_url: https://github.com/ilanschnell/bsdiff4 + doc_url: https://bsdiff4.readthedocs.io + doc_source_url: https://github.com/ilanschnell/bsdiff4/blob/main/README.rst License file diff --git a/docs/source/resources/variants.rst b/docs/source/resources/variants.rst index 3209fd3620..90953126ee 100644 --- a/docs/source/resources/variants.rst +++ b/docs/source/resources/variants.rst @@ -323,7 +323,7 @@ your Jinja2 templates. There are two ways that you can feed this information into the API: 1. Pass the ``variants`` keyword argument to API functions. Currently, the - ``build``, ``render``, ``get_output_file_path``, and ``check`` functions + ``build``, ``render``, ``get_output_file_paths``, and ``check`` functions accept this argument. ``variants`` should be a dictionary where each value is a list of versions to iterate over. These are aggregated as detailed in the `Aggregation of multiple variants`_ section below. diff --git a/docs/source/user-guide/getting-started.rst b/docs/source/user-guide/getting-started.rst index 113632fe73..64c82e292e 100644 --- a/docs/source/user-guide/getting-started.rst +++ b/docs/source/user-guide/getting-started.rst @@ -16,16 +16,17 @@ Prerequisites Before starting the tutorials, you need to install: -- `Miniconda or Anaconda `_ +- `Miniconda `_ or `Anaconda `_ - conda-build - Git The most straightforward way to do this is to install Miniconda or Anaconda, which contain conda, and then use conda to install conda-build -and Git. Make sure you install these packages into a new environment -and not your base environment.:: +and Git. Make sure you install these packages into your base environment.:: - conda create -n my-conda-build-environment conda-build git + conda install -n base conda-build git + +For more information on installing and updating conda-build, see :doc:`Installing and updating conda-build <../install-conda-build>`. .. _submissions: diff --git a/news/4867-arm64-msvc-env-cmd-no-op b/news/4867-arm64-msvc-env-cmd-no-op deleted file mode 100644 index 134dcd14fd..0000000000 --- a/news/4867-arm64-msvc-env-cmd-no-op +++ /dev/null @@ -1,19 +0,0 @@ -### Enhancements - -* - -### Bug fixes - -* - -### Deprecations - -* - -### Docs - -* - -### Other - -* Added a check to print an additional warning and return an empty string when bits is "arm64" in msvc_env_cmd. (#4867) diff --git a/news/5195-fix-stdlib-variant b/news/5195-fix-stdlib-variant deleted file mode 100644 index 526692f286..0000000000 --- a/news/5195-fix-stdlib-variant +++ /dev/null @@ -1,19 +0,0 @@ -### Enhancements - -* - -### Bug fixes - -* Fix stdlib being recognized in variant hash inputs. (#5190 via #5195) - -### Deprecations - -* - -### Docs - -* - -### Other - -* diff --git a/news/5196-deprecate-bdist-conda b/news/5196-deprecate-bdist-conda deleted file mode 100644 index 3f37838bf0..0000000000 --- a/news/5196-deprecate-bdist-conda +++ /dev/null @@ -1,19 +0,0 @@ -### Enhancements - -* - -### Bug fixes - -* - -### Deprecations - -* Mark `conda_build.bdist_conda` module as pending deprecation. (#5196) - -### Docs - -* - -### Other - -* diff --git a/news/5203-remove-deprecations b/news/5203-remove-deprecations deleted file mode 100644 index fb77c3b149..0000000000 --- a/news/5203-remove-deprecations +++ /dev/null @@ -1,82 +0,0 @@ -### Enhancements - -* - -### Bug fixes - -* - -### Deprecations - -* Mark `conda_build.conda_interface.handle_proxy_407` as deprecated. Handled by `conda.gateways.connection.session.CondaSession`. (#5203) -* Mark `conda_build.conda_interface.hashsum_file` as deprecated. Use `conda.gateways.disk.read.compute_sum` instead. (#5203) -* Mark `conda_build.conda_interface.md5_file` as deprecated. Use `conda.gateways.disk.read.compute_sum(path, 'md5')` instead. (#5203) -* Mark `conda_build.environ.PREFIX_ACTION` as deprecated. (#5203) -* Mark `conda_build.environ.LINK_ACTION` as deprecated. (#5203) -* Mark `conda_build.environ.cache_actions` as deprecated. (#5203) -* Mark `conda_build.index.DummyExecutor` as deprecated. (#5203) -* Mark `conda_build.index.MAX_THREADS_DEFAULT` as deprecated. (#5203) -* Mark `conda_build.index.LOCK_TIMEOUT_SECS` as deprecated. (#5203) -* Mark `conda_build.index.LOCKFILE_NAME` as deprecated. (#5203) -* Postpone `conda_build.index.channel_data` deprecation. (#5203) -* Rename `conda_build.environ.create_env('specs_or_actions' -> 'specs_or_precs')`. (#5203) -* Rename `conda_build.environ._execute_actions('actions' -> 'precs'). (#5203) -* Rename `conda_build.environ._display_actions('actions' -> 'precs'). (#5203) -* Rename `conda_build.inspect.check_install('platform' -> 'subdir')`. (#5203) -* Rename `conda_build.render.execute_download_actions('actions' -> 'precs')`. (#5203) -* Rename `conda_build.render.get_upstream_pins('actions' -> 'precs')`. (#5203) -* Remove `conda_build.cli.main_render.execute(print_results)`. (#5203) -* Remove `conda_build.conda_interface.Dist`. (#5203) -* Remove `conda_build.conda_interface.display_actions`. (#5203) -* Remove `conda_build.conda_interface.execute_actions`. (#5203) -* Remove `conda_build.conda_interface.execute_plan`. (#5203) -* Remove `conda_build.conda_interface.get_index`. (#5203) -* Remove `conda_build.conda_interface.install_actions`. (#5203) -* Remove `conda_build.conda_interface.linked`. (#5203) -* Remove `conda_build.conda_interface.linked_data`. (#5203) -* Remove `conda_build.conda_interface.package_cache`. (#5203) -* Remove `conda_build.environ.get_install_actions`. Use `conda_build.environ.get_package_records` instead. (#5203) -* Remove `conda_build.index._determine_namespace`. (#5203) -* Remove `conda_build.index._make_seconds`. (#5203) -* Remove `conda_build.index.REPODATA_VERSION`. (#5203) -* Remove `conda_build.index.CHANNELDATA_VERSION`. (#5203) -* Remove `conda_build.index.REPODATA_JSON_FN`. (#5203) -* Remove `conda_build.index.REPODATA_FROM_PKGS_JSON_FN`. (#5203) -* Remove `conda_build.index.CHANNELDATA_FIELDS`. (#5203) -* Remove `conda_build.index._clear_newline_chars`. (#5203) -* Remove `conda_build.index._apply_instructions`. (#5203) -* Remove `conda_build.index._get_jinja2_environment`. (#5203) -* Remove `conda_build.index._maybe_write`. (#5203) -* Remove `conda_build.index._make_build_string`. (#5203) -* Remove `conda_build.index._warn_on_missing_dependencies`. (#5203) -* Remove `conda_build.index._cache_post_install_details`. (#5203) -* Remove `conda_build.index._cache_recipe`. (#5203) -* Remove `conda_build.index._cache_run_exports`. (#5203) -* Remove `conda_build.index._cache_icon`. (#5203) -* Remove `conda_build.index._make_subdir_index_html`. (#5203) -* Remove `conda_build.index._make_channeldata_index_html`. (#5203) -* Remove `conda_build.index._get_source_repo_git_info`. (#5203) -* Remove `conda_build.index._cache_info_file`. (#5203) -* Remove `conda_build.index._alternate_file_extension`. (#5203) -* Remove `conda_build.index._get_resolve_object`. (#5203) -* Remove `conda_build.index._get_newest_versions`. (#5203) -* Remove `conda_build.index._add_missing_deps`. (#5203) -* Remove `conda_build.index._add_prev_ver_for_features`. (#5203) -* Remove `conda_build.index._shard_newest_packages`. (#5203) -* Remove `conda_build.index._build_current_repodata`. (#5203) -* Remove `conda_build.index.ChannelIndex`. (#5203) -* Remove `conda_build.inspect.check_install('prepend')`. (#5203) -* Remove `conda_build.inspect.check_install('minimal_hint')`. (#5203) -* Remove `conda_build.noarch_python.ISWIN`. Use `conda_build.utils.on_win` instead. (#5203) -* Remove `conda_build.noarch_python._force_dir`. Use `os.makedirs(exist_ok=True)` instead. (#5203) -* Remove `conda_build.noarch_python._error_exit`. (#5203) -* Remove `conda_build.render.actions_to_pins`. (#5203) -* Remove `conda_build.utils.linked_data_no_multichannels`. (#5203) - -### Docs - -* - -### Other - -* diff --git a/news/5221-deprecate-get_version_from_git_tag b/news/5221-deprecate-get_version_from_git_tag deleted file mode 100644 index 2c1e811a54..0000000000 --- a/news/5221-deprecate-get_version_from_git_tag +++ /dev/null @@ -1,19 +0,0 @@ -### Enhancements - -* - -### Bug fixes - -* - -### Deprecations - -* Mark `conda_build.conda_interface.get_version_from_git_tag` as deprecated. Use `conda_build.environ.get_version_from_git_tag` instead. (#5221) - -### Docs - -* - -### Other - -* diff --git a/news/5222-deprecating-conda_interface b/news/5222-deprecating-conda_interface new file mode 100644 index 0000000000..288f24474b --- /dev/null +++ b/news/5222-deprecating-conda_interface @@ -0,0 +1,32 @@ +### Enhancements + +* + +### Bug fixes + +* + +### Deprecations + +* Deprecate `conda_build.conda_interface.CONDA_VERSION` constant. Use `conda.__version__` instead. (#5222) +* Deprecate `conda_build.conda_interface.binstar_upload` constant. Use `conda.base.context.context.binstar_upload` instead. (#5222) +* Deprecate `conda_build.conda_interface.default_python` constant. Use `conda.base.context.context.default_python` instead. (#5222) +* Deprecate `conda_build.conda_interface.envs_dirs` constant. Use `conda.base.context.context.envs_dirs` instead. (#5222) +* Deprecate `conda_build.conda_interface.pkgs_dirs` constant. Use `conda.base.context.context.pkgs_dirs` instead. (#5222) +* Deprecate `conda_build.conda_interface.cc_platform` constant. Use `conda.base.context.context.platform` instead. (#5222) +* Deprecate `conda_build.conda_interface.root_dir` constant. Use `conda.base.context.context.root_dir` instead. (#5222) +* Deprecate `conda_build.conda_interface.root_writable` constant. Use `conda.base.context.context.root_writable` instead. (#5222) +* Deprecate `conda_build.conda_interface.subdir` constant. Use `conda.base.context.context.subdir` instead. (#5222) +* Deprecate `conda_build.conda_interface.create_default_packages` constant. Use `conda.base.context.context.create_default_packages` instead. (#5222) +* Deprecate `conda_build.conda_interface.get_rc_urls` function. Use `conda.base.context.context.channels` instead. (#5222) +* Deprecate `conda_build.conda_interface.get_prefix` function. Use `conda.base.context.context.target_prefix` instead. (#5222) +* Deprecate `conda_build.conda_interface.get_conda_channel` function. Use `conda.models.channel.Channel.from_value` instead. (#5222) +* Deprecate `conda_build.conda_interface.reset_context` function. Use `conda.base.context.reset_context` instead. (#5222) + +### Docs + +* + +### Other + +* diff --git a/news/5199-deprecate-have_prefix_files b/news/5233-enable-codspeed similarity index 66% rename from news/5199-deprecate-have_prefix_files rename to news/5233-enable-codspeed index eccab010da..efb32df4d1 100644 --- a/news/5199-deprecate-have_prefix_files +++ b/news/5233-enable-codspeed @@ -8,7 +8,7 @@ ### Deprecations -* Mark `conda_build.build.have_prefix_files` as deprecated. (#5199) +* ### Docs @@ -16,4 +16,4 @@ ### Other -* +* Enable CodSpeed benchmarks for select tests. (#5233) diff --git a/pyproject.toml b/pyproject.toml index e8cfc5e011..dd3e95dd56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,8 +133,16 @@ addopts = [ # "--store-durations", # not available yet "--strict-markers", "--tb=native", + "--xdoctest-modules", + "--xdoctest-style=google", "-vv", ] +doctest_optionflags = [ + "NORMALIZE_WHITESPACE", + "IGNORE_EXCEPTION_DETAIL", + "ALLOW_UNICODE", + "ELLIPSIS", +] markers = [ "serial: execute test serially (to avoid race conditions)", "slow: execute the slow tests if active", diff --git a/tests/bdist-recipe/conda_build_test/__init__.py b/tests/bdist-recipe/conda_build_test/__init__.py index 3574c4128a..1f22b11325 100644 --- a/tests/bdist-recipe/conda_build_test/__init__.py +++ b/tests/bdist-recipe/conda_build_test/__init__.py @@ -3,4 +3,5 @@ """ conda build test package """ + print("conda_build_test has been imported") diff --git a/tests/cli/test_main_build.py b/tests/cli/test_main_build.py index 60f24cf7ca..9da5b48418 100644 --- a/tests/cli/test_main_build.py +++ b/tests/cli/test_main_build.py @@ -296,7 +296,7 @@ def test_no_force_upload( # render recipe api.output_yaml(testing_metadata, "meta.yaml") - pkg = api.get_output_file_path(testing_metadata) + pkg = api.get_output_file_paths(testing_metadata) # mock Config.set_keys to always set anaconda_upload to True # conda's Context + conda_build's MetaData & Config objects interact in such an diff --git a/tests/conftest.py b/tests/conftest.py index f347317d90..f055b05d80 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,7 @@ from conda.common.compat import on_mac, on_win from pytest import MonkeyPatch +import conda_build import conda_build.config from conda_build.config import ( Config, @@ -32,6 +33,14 @@ from conda_build.variants import get_default_variant +@pytest.hookimpl +def pytest_report_header(config: pytest.Config): + # ensuring the expected development conda is being run + expected = Path(__file__).parent.parent / "conda_build" / "__init__.py" + assert expected.samefile(conda_build.__file__) + return f"conda_build.__file__: {conda_build.__file__}" + + @pytest.fixture(scope="function") def testing_workdir(monkeypatch: MonkeyPatch, tmp_path: Path) -> Iterator[str]: """Create a workdir in a safe temporary folder; cd into dir above before test, cd out after diff --git a/tests/requirements-linux.txt b/tests/requirements-Linux.txt similarity index 100% rename from tests/requirements-linux.txt rename to tests/requirements-Linux.txt diff --git a/tests/requirements-windows.txt b/tests/requirements-Windows.txt similarity index 100% rename from tests/requirements-windows.txt rename to tests/requirements-Windows.txt diff --git a/tests/requirements-ci.txt b/tests/requirements-ci.txt new file mode 100644 index 0000000000..23d78bb0b2 --- /dev/null +++ b/tests/requirements-ci.txt @@ -0,0 +1,19 @@ +anaconda-client +conda-forge::xdoctest +conda-verify +contextlib2 +coverage +cytoolz +git +numpy +perl +pip +pyflakes +pytest +pytest-cov +pytest-forked +pytest-mock +pytest-rerunfailures +pytest-xdist +ruamel.yaml +tomli # [py<3.11] for coverage pyproject.toml diff --git a/tests/requirements-macos.txt b/tests/requirements-macOS.txt similarity index 100% rename from tests/requirements-macos.txt rename to tests/requirements-macOS.txt diff --git a/tests/requirements.txt b/tests/requirements.txt index 5f96c8fd66..5e94d4111a 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,37 +1,22 @@ beautifulsoup4 chardet conda >=23.5.0 -conda-forge::anaconda-client conda-index >=0.4.0 +conda-libmamba-solver # ensure we use libmamba conda-package-handling >=1.3 -conda-verify -contextlib2 -cytoolz filelock -git jinja2 jsonschema >=4.19 menuinst >=2 -numpy packaging -perl -pip pkginfo psutil py-lief -pyflakes -pytest -pytest-cov -pytest-forked -pytest-mock -pytest-replay -pytest-rerunfailures -pytest-xdist +python >=3.8 python-libarchive-c pytz +pyyaml requests -ripgrep -ruamel.yaml +ripgrep # for faster grep setuptools_scm # needed for devenv version detection -tomli # [py<3.11] for coverage pyproject.toml tqdm diff --git a/tests/test-recipes/metadata/_pin_subpackage_benchmark/meta.yaml b/tests/test-recipes/metadata/_pin_subpackage_benchmark/meta.yaml new file mode 100644 index 0000000000..311b5a95d2 --- /dev/null +++ b/tests/test-recipes/metadata/_pin_subpackage_benchmark/meta.yaml @@ -0,0 +1,252 @@ +# Performance regression test for https://github.com/conda/conda-build/pull/5224 +# This is a reduced version of +# https://github.com/conda-forge/arrow-cpp-feedstock/blob/e6f573674c5f9c35c6a614a1563b2fe3eeb3e72b/recipe/meta.yaml +# stripped of everything apart from the large number of inter-output +# pin_subpackage dependencies/run_exports. +# Addendum: Omit libarrow-all, pyarrow, pyarrow-tests to reduce benchmark duration. + +package: + name: apache-arrow + version: 15.0.2 + +outputs: +# - name: libarrow-all +# build: +# run_exports: +# - {{ pin_subpackage("libarrow", max_pin="x") }} +# - {{ pin_subpackage("libarrow-acero", max_pin="x") }} +# - {{ pin_subpackage("libarrow-dataset", max_pin="x") }} +# - {{ pin_subpackage("libarrow-flight", max_pin="x") }} +# - {{ pin_subpackage("libarrow-flight-sql", max_pin="x") }} +# - {{ pin_subpackage("libarrow-gandiva", max_pin="x") }} +# - {{ pin_subpackage("libarrow-substrait", max_pin="x") }} +# - {{ pin_subpackage("libparquet", max_pin="x") }} +# requirements: +# host: +# - {{ pin_subpackage("libarrow", exact=True) }} +# - {{ pin_subpackage("libarrow-acero", exact=True) }} +# - {{ pin_subpackage("libarrow-dataset", exact=True) }} +# - {{ pin_subpackage("libarrow-flight", exact=True) }} +# - {{ pin_subpackage("libarrow-flight-sql", exact=True) }} +# - {{ pin_subpackage("libarrow-gandiva", exact=True) }} +# - {{ pin_subpackage("libarrow-substrait", exact=True) }} +# - {{ pin_subpackage("libparquet", exact=True) }} +# run: +# - {{ pin_subpackage("libarrow", exact=True) }} +# - {{ pin_subpackage("libarrow-acero", exact=True) }} +# - {{ pin_subpackage("libarrow-dataset", exact=True) }} +# - {{ pin_subpackage("libarrow-flight", exact=True) }} +# - {{ pin_subpackage("libarrow-flight-sql", exact=True) }} +# - {{ pin_subpackage("libarrow-gandiva", exact=True) }} +# - {{ pin_subpackage("libarrow-substrait", exact=True) }} +# - {{ pin_subpackage("libparquet", exact=True) }} + + - name: libarrow + build: + run_exports: + - {{ pin_subpackage("libarrow", max_pin="x") }} + + - name: libarrow-acero + build: + run_exports: + - {{ pin_subpackage("libarrow-acero", max_pin="x") }} + requirements: + host: + - {{ pin_subpackage("libarrow", exact=True) }} + run: + - {{ pin_subpackage("libarrow", exact=True) }} + + - name: libarrow-dataset + build: + run_exports: + - {{ pin_subpackage("libarrow-dataset", max_pin="x") }} + requirements: + host: + - {{ pin_subpackage("libarrow", exact=True) }} + - {{ pin_subpackage("libarrow-acero", exact=True) }} + - {{ pin_subpackage("libparquet", exact=True) }} + run: + - {{ pin_subpackage("libarrow", exact=True) }} + - {{ pin_subpackage("libarrow-acero", exact=True) }} + - {{ pin_subpackage("libparquet", exact=True) }} + + - name: libarrow-flight + build: + run_exports: + - {{ pin_subpackage("libarrow-flight", max_pin="x") }} + requirements: + run: + - {{ pin_subpackage("libarrow", exact=True) }} + + - name: libarrow-flight-sql + build: + run_exports: + - {{ pin_subpackage("libarrow-flight-sql", max_pin="x") }} + requirements: + host: + - {{ pin_subpackage("libarrow", exact=True) }} + - {{ pin_subpackage("libarrow-flight", exact=True) }} + run: + - {{ pin_subpackage("libarrow", exact=True) }} + - {{ pin_subpackage("libarrow-flight", exact=True) }} + + - name: libarrow-gandiva + build: + run_exports: + - {{ pin_subpackage("libarrow-gandiva", max_pin="x") }} + requirements: + build: + host: + - {{ pin_subpackage("libarrow", max_pin="x") }} + run: + - {{ pin_subpackage("libarrow", exact=True) }} + + - name: libarrow-substrait + build: + run_exports: + - {{ pin_subpackage("libarrow-substrait", max_pin="x") }} + requirements: + host: + - {{ pin_subpackage("libarrow", exact=True) }} + - {{ pin_subpackage("libarrow-acero", exact=True) }} + - {{ pin_subpackage("libarrow-dataset", exact=True) }} + run: + - {{ pin_subpackage("libarrow", exact=True) }} + - {{ pin_subpackage("libarrow-acero", exact=True) }} + - {{ pin_subpackage("libarrow-dataset", exact=True) }} + + - name: libparquet + build: + run_exports: + - {{ pin_subpackage("libparquet", max_pin="x") }} + requirements: + host: + - {{ pin_subpackage("libarrow", max_pin="x") }} + run: + - {{ pin_subpackage("libarrow", exact=True) }} + +# - name: pyarrow +# requirements: +# host: +# - {{ pin_subpackage("libarrow-all", exact=True) }} +# run: +# - {{ pin_subpackage("libarrow", exact=True) }} +# - {{ pin_subpackage("libarrow-acero", exact=True) }} +# - {{ pin_subpackage("libarrow-dataset", exact=True) }} +# - {{ pin_subpackage("libarrow-flight", exact=True) }} +# - {{ pin_subpackage("libarrow-flight-sql", exact=True) }} +# - {{ pin_subpackage("libarrow-gandiva", exact=True) }} +# - {{ pin_subpackage("libarrow-substrait", exact=True) }} +# - {{ pin_subpackage("libparquet", exact=True) }} +# +# - name: pyarrow-tests +# requirements: +# host: +# - {{ pin_subpackage("libarrow-all", exact=True) }} +# - {{ pin_subpackage('pyarrow', exact=True) }} +# run: +# - {{ pin_subpackage('pyarrow', exact=True) }} + +# The original recipe had 173 selector lines; adding placeholders for these here: +about: + description: > + 00 # [x86_64] + 01 # [not x86_64] + 02 # [unix] + 03 # [not unix] + 04 # [linux] + 05 # [not linux] + 06 # [osx] + 07 # [not osx] + 08 # [win] + 09 # [not win] + 10 # [x86_64] + 11 # [not x86_64] + 12 # [unix] + 13 # [not unix] + 14 # [linux] + 15 # [not linux] + 16 # [osx] + 17 # [not osx] + 18 # [win] + 19 # [not win] + 20 # [x86_64] + 21 # [not x86_64] + 22 # [unix] + 23 # [not unix] + 24 # [linux] + 25 # [not linux] + 26 # [osx] + 27 # [not osx] + 28 # [win] + 29 # [not win] + 30 # [x86_64] + 31 # [not x86_64] + 32 # [unix] + 33 # [not unix] + 34 # [linux] + 35 # [not linux] + 36 # [osx] + 37 # [not osx] + 38 # [win] + 39 # [not win] + 40 # [x86_64] + 41 # [not x86_64] + 42 # [unix] + 43 # [not unix] + 44 # [linux] + 45 # [not linux] + 46 # [osx] + 47 # [not osx] + 48 # [win] + 49 # [not win] + 50 # [x86_64] + 51 # [not x86_64] + 52 # [unix] + 53 # [not unix] + 54 # [linux] + 55 # [not linux] + 56 # [osx] + 57 # [not osx] + 58 # [win] + 59 # [not win] + 60 # [x86_64] + 61 # [not x86_64] + 62 # [unix] + 63 # [not unix] + 64 # [linux] + 65 # [not linux] + 66 # [osx] + 67 # [not osx] + 68 # [win] + 69 # [not win] + 70 # [x86_64] + 71 # [not x86_64] + 72 # [unix] + 73 # [not unix] + 74 # [linux] + 75 # [not linux] + 76 # [osx] + 77 # [not osx] + 78 # [win] + 79 # [not win] + 80 # [x86_64] + 81 # [not x86_64] + 82 # [unix] + 83 # [not unix] + 84 # [linux] + 85 # [not linux] + 86 # [osx] + 87 # [not osx] + 88 # [win] + 89 # [not win] + 90 # [x86_64] + 91 # [not x86_64] + 92 # [unix] + 93 # [not unix] + 94 # [linux] + 95 # [not linux] + 96 # [osx] + 97 # [not osx] + 98 # [win] + 99 # [not win] diff --git a/tests/test_api_build.py b/tests/test_api_build.py index 6ad6577c50..7bedf3e215 100644 --- a/tests/test_api_build.py +++ b/tests/test_api_build.py @@ -3,6 +3,7 @@ """ This module tests the build API. These are high-level integration tests. """ + from __future__ import annotations import json @@ -26,6 +27,7 @@ import yaml from binstar_client.commands import remove, show from binstar_client.errors import NotFound +from conda.base.context import reset_context from conda.common.compat import on_linux, on_mac, on_win from conda.exceptions import ClobberError, CondaMultiError from conda_index.api import update_index @@ -35,7 +37,6 @@ CondaError, LinkError, context, - reset_context, url_path, ) from conda_build.config import Config @@ -242,7 +243,7 @@ def test_offline( def test_git_describe_info_on_branch(testing_config): recipe_path = os.path.join(metadata_dir, "_git_describe_number_branch") m = api.render(recipe_path, config=testing_config)[0][0] - output = api.get_output_file_path(m)[0] + output = api.get_output_file_paths(m)[0] # missing hash because we set custom build string in meta.yaml test_path = os.path.join( testing_config.croot, @@ -306,9 +307,7 @@ def test_output_build_path_git_source(testing_config): test_path = os.path.join( testing_config.croot, testing_config.host_subdir, - "conda-build-test-source-git-jinja2-1.20.2-py{}{}{}_0_g262d444.tar.bz2".format( - sys.version_info.major, sys.version_info.minor, _hash - ), + f"conda-build-test-source-git-jinja2-1.20.2-py{sys.version_info.major}{sys.version_info.minor}{_hash}_0_g262d444.tar.bz2", ) assert output == test_path @@ -625,7 +624,7 @@ def test_numpy_setup_py_data(testing_config): m = api.render(recipe_path, config=testing_config, numpy="1.16")[0][0] _hash = m.hash_dependencies() assert ( - os.path.basename(api.get_output_file_path(m)[0]) + os.path.basename(api.get_output_file_paths(m)[0]) == f"load_setup_py_test-0.1.0-np116py{sys.version_info.major}{sys.version_info.minor}{_hash}_0.tar.bz2" ) @@ -795,7 +794,7 @@ def test_relative_git_url_submodule_clone(testing_workdir, testing_config, monke # This will (after one spin round the loop) install and run 'git' with the # build env prepended to os.environ[] metadata = api.render(testing_workdir, config=testing_config)[0][0] - output = api.get_output_file_path(metadata, config=testing_config)[0] + output = api.get_output_file_paths(metadata, config=testing_config)[0] assert f"relative_submodules-{tag}-" in output api.build(metadata, config=testing_config) @@ -811,7 +810,7 @@ def test_noarch(testing_workdir): ) with open(filename, "w") as outfile: outfile.write(yaml.dump(data, default_flow_style=False, width=999999999)) - output = api.get_output_file_path(testing_workdir)[0] + output = api.get_output_file_paths(testing_workdir)[0] assert os.path.sep + "noarch" + os.path.sep in output or not noarch assert os.path.sep + "noarch" + os.path.sep not in output or noarch @@ -819,15 +818,15 @@ def test_noarch(testing_workdir): def test_disable_pip(testing_metadata): testing_metadata.config.disable_pip = True testing_metadata.meta["requirements"] = {"host": ["python"], "run": ["python"]} - testing_metadata.meta["build"][ - "script" - ] = 'python -c "import pip; print(pip.__version__)"' + testing_metadata.meta["build"]["script"] = ( + 'python -c "import pip; print(pip.__version__)"' + ) with pytest.raises(subprocess.CalledProcessError): api.build(testing_metadata) - testing_metadata.meta["build"][ - "script" - ] = 'python -c "import setuptools; print(setuptools.__version__)"' + testing_metadata.meta["build"]["script"] = ( + 'python -c "import setuptools; print(setuptools.__version__)"' + ) with pytest.raises(subprocess.CalledProcessError): api.build(testing_metadata) diff --git a/tests/test_api_consistency.py b/tests/test_api_consistency.py index 56685f66d1..9d88b60eee 100644 --- a/tests/test_api_consistency.py +++ b/tests/test_api_consistency.py @@ -43,7 +43,7 @@ def test_api_output_yaml(): def test_api_get_output_file_path(): - argspec = getargspec(api.get_output_file_path) + argspec = getargspec(api.get_output_file_paths) assert argspec.args == [ "recipe_path_or_metadata", "no_download_source", diff --git a/tests/test_api_debug.py b/tests/test_api_debug.py index 42fa1275fe..af24d8acfb 100644 --- a/tests/test_api_debug.py +++ b/tests/test_api_debug.py @@ -4,6 +4,7 @@ This module tests the test API. These are high-level integration tests. Lower level unit tests should go in test_render.py """ + from __future__ import annotations import subprocess diff --git a/tests/test_api_render.py b/tests/test_api_render.py index 878617e78d..7849daa01c 100644 --- a/tests/test_api_render.py +++ b/tests/test_api_render.py @@ -4,15 +4,19 @@ This module tests the test API. These are high-level integration tests. Lower level unit tests should go in test_render.py """ + import os import re +from itertools import count, islice import pytest import yaml +from conda.base.context import context from conda.common.compat import on_win from conda_build import api, render -from conda_build.conda_interface import cc_conda_build, subdir +from conda_build.conda_interface import cc_conda_build +from conda_build.variants import validate_spec from .utils import metadata_dir, variants_dir @@ -105,7 +109,7 @@ def test_get_output_file_path_jinja2(testing_config): def test_output_without_jinja_does_not_download(mocker, testing_config): mock = mocker.patch("conda_build.source") - api.get_output_file_path( + api.get_output_file_paths( os.path.join(metadata_dir, "source_git"), config=testing_config ) mock.assert_not_called() @@ -166,7 +170,7 @@ def test_pin_depends(testing_config): def test_cross_recipe_with_only_build_section(testing_config): recipe = os.path.join(metadata_dir, "_cross_prefix_elision_compiler_used") metadata = api.render(recipe, config=testing_config, bypass_env_check=True)[0][0] - assert metadata.config.host_subdir != subdir + assert metadata.config.host_subdir != context.subdir assert metadata.config.build_prefix != metadata.config.host_prefix assert not metadata.build_is_host @@ -175,7 +179,7 @@ def test_cross_info_index_platform(testing_config): recipe = os.path.join(metadata_dir, "_cross_build_unix_windows") metadata = api.render(recipe, config=testing_config, bypass_env_check=True)[0][0] info_index = metadata.info_index() - assert metadata.config.host_subdir != subdir + assert metadata.config.host_subdir != context.subdir assert metadata.config.host_subdir == info_index["subdir"] assert metadata.config.host_platform != metadata.config.platform assert metadata.config.host_platform == info_index["platform"] @@ -297,3 +301,35 @@ def test_pin_expression_works_with_python_prereleases(testing_config): assert len(ms) == 2 m = next(m_[0] for m_ in ms if m_[0].meta["package"]["name"] == "bar") assert "python >=3.10.0rc1,<3.11.0a0" in m.meta["requirements"]["run"] + + +@pytest.mark.benchmark +def test_pin_subpackage_benchmark(testing_config): + # Performance regression test for https://github.com/conda/conda-build/pull/5224 + recipe = os.path.join(metadata_dir, "_pin_subpackage_benchmark") + + # Create variant config of size comparable (for subdir linux-64) to + # https://github.com/conda-forge/conda-forge-pinning-feedstock/blob/3c7d60f56a8cb7d1b8f5a8da0b02ae1f1f0982d7/recipe/conda_build_config.yaml + # Addendum: Changed number of single-value keys from 327 to 33 to reduce benchmark duration. + def create_variants(): + # ("pkg_1, ("1.1", "1.2", ...)), ("pkg_2", ("2.1", "2.2", ...)), ... + packages = ((f"pkg_{i}", (f"{i}.{j}" for j in count(1))) for i in count(1)) + variant = {} + variant["zip_keys"] = [] + for version_count, package_count in [(1, 4), (4, 3), (4, 3)]: + zipped = [] + for package, versions in islice(packages, package_count): + zipped.append(package) + variant[package] = list(islice(versions, version_count)) + variant["zip_keys"].append(zipped) + # for version_count, package_count in [(3, 1), (2, 4), (1, 327)]: + for version_count, package_count in [(3, 1), (2, 4), (1, 33)]: + for package, versions in islice(packages, package_count): + variant[package] = list(islice(versions, version_count)) + validate_spec("", variant) + return variant + + ms = api.render( + recipe, config=testing_config, channels=[], variants=create_variants() + ) + assert len(ms) == 11 - 3 # omits libarrow-all, pyarrow, pyarrow-tests diff --git a/tests/test_api_skeleton_cpan.py b/tests/test_api_skeleton_cpan.py index 238635ba50..9f08ccbae6 100644 --- a/tests/test_api_skeleton_cpan.py +++ b/tests/test_api_skeleton_cpan.py @@ -5,7 +5,6 @@ conda_build.api.skeletonize and check the output files """ - import pytest from conda_build import api diff --git a/tests/test_api_skeleton_cran.py b/tests/test_api_skeleton_cran.py index 9b62b4ac30..912b2bee0c 100644 --- a/tests/test_api_skeleton_cran.py +++ b/tests/test_api_skeleton_cran.py @@ -4,6 +4,7 @@ Integrative tests of the CRAN skeleton that start from conda_build.api.skeletonize and check the output files """ + from pathlib import Path from typing import Sequence diff --git a/tests/test_api_test.py b/tests/test_api_test.py index a258bbba0d..2bb76838aa 100644 --- a/tests/test_api_test.py +++ b/tests/test_api_test.py @@ -3,6 +3,7 @@ """ This module tests the test API. These are high-level integration tests. """ + import os import pytest diff --git a/tests/test_build.py b/tests/test_build.py index f11be7727c..16bffa648f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -4,6 +4,7 @@ This file tests the build.py module. It sits lower in the stack than the API tests, and is more unit-test oriented. """ + import json import os import sys diff --git a/tests/test_cpan_skeleton.py b/tests/test_cpan_skeleton.py index 2b726f6c88..1b02331f4b 100644 --- a/tests/test_cpan_skeleton.py +++ b/tests/test_cpan_skeleton.py @@ -4,7 +4,6 @@ Unit tests of the CPAN skeleton utility functions """ - from pathlib import Path import pytest diff --git a/tests/test_cran_skeleton.py b/tests/test_cran_skeleton.py index a0f5575114..0db839a8f4 100644 --- a/tests/test_cran_skeleton.py +++ b/tests/test_cran_skeleton.py @@ -3,6 +3,7 @@ """ Unit tests of the CRAN skeleton utility functions """ + import os import pytest diff --git a/tests/test_develop.py b/tests/test_develop.py index add9f65c03..d72bb247d3 100644 --- a/tests/test_develop.py +++ b/tests/test_develop.py @@ -3,6 +3,7 @@ """ Simple tests for testing functions in develop module - lower level than going through API. """ + from pathlib import Path from typing import Generator diff --git a/tests/test_environ.py b/tests/test_environ.py index 93311ab81b..d45fc8ed7f 100644 --- a/tests/test_environ.py +++ b/tests/test_environ.py @@ -1,6 +1,9 @@ # Copyright (C) 2014 Anaconda, Inc # SPDX-License-Identifier: BSD-3-Clause import os +import sys + +from conda.core.prefix_data import PrefixData from conda_build import environ @@ -15,3 +18,12 @@ def test_environment_creation_preserves_PATH(testing_workdir, testing_config): subdir=testing_config.build_subdir, ) assert os.environ["PATH"] == ref_path + + +def test_environment(): + """Asserting PrefixData can accomplish the same thing as Environment.""" + assert (specs := environ.Environment(sys.prefix).package_specs()) + assert specs == [ + f"{prec.name} {prec.version} {prec.build}" + for prec in PrefixData(sys.prefix).iter_records() + ] diff --git a/tests/test_inspect_pkg.py b/tests/test_inspect_pkg.py index 2f35bd3b0e..dae6d7f6ca 100644 --- a/tests/test_inspect_pkg.py +++ b/tests/test_inspect_pkg.py @@ -207,6 +207,14 @@ def test_which_package(tmp_path: Path): @pytest.mark.benchmark def test_which_package_battery(tmp_path: Path): # regression: https://github.com/conda/conda-build/issues/5126 + + # NOTE: CodSpeed on Python 3.12+ activates the stack profiler trampoline backend + # and thus runs the test twice (once without profiling and once with profiling), + # unfortunately this means that on the second iteration tmp_path is no longer empty + # so we create a randomized unique directory to compensate + tmp_path = tmp_path / uuid4().hex + tmp_path.mkdir() + # create a dummy environment (tmp_path / "conda-meta").mkdir() (tmp_path / "conda-meta" / "history").touch() @@ -214,7 +222,7 @@ def test_which_package_battery(tmp_path: Path): # dummy packages with files removed = [] - for _ in range(100): + for _ in range(10): name = f"package_{uuid4().hex}" # mock a package with 100 files diff --git a/tests/test_metadata.py b/tests/test_metadata.py index b176d4103d..0f6da9b089 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -5,6 +5,7 @@ import os import subprocess import sys +from itertools import product from typing import TYPE_CHECKING import pytest @@ -54,52 +55,108 @@ def test_uses_vcs_in_metadata(testing_workdir, testing_metadata): def test_select_lines(): - lines = """ -test -test [abc] no -test [abc] # no - -test [abc] - 'quoted # [abc] ' - "quoted # [abc] yes " -test # stuff [abc] yes -test {{ JINJA_VAR[:2] }} -test {{ JINJA_VAR[:2] }} # stuff [abc] yes -test {{ JINJA_VAR[:2] }} # stuff yes [abc] -test {{ JINJA_VAR[:2] }} # [abc] stuff yes -{{ environ["test"] }} # [abc] -""" + lines = "\n".join( + ( + "", + "test", + "test [abc] no", + "test [abc] # no", + " ' test ' ", + ' " test " ', + "", + "# comment line", + "test [abc]", + " 'quoted # [abc] '", + ' "quoted # [abc] yes "', + "test # stuff [abc] yes", + "test {{ JINJA_VAR[:2] }}", + "test {{ JINJA_VAR[:2] }} # stuff [abc] yes", + "test {{ JINJA_VAR[:2] }} # stuff yes [abc]", + "test {{ JINJA_VAR[:2] }} # [abc] stuff yes", + '{{ environ["test"] }} # [abc]', + "", # trailing newline + ) + ) - assert ( - select_lines(lines, {"abc": True}, variants_in_place=True) - == """ -test -test [abc] no -test [abc] # no - -test - 'quoted' - "quoted" -test -test {{ JINJA_VAR[:2] }} -test {{ JINJA_VAR[:2] }} -test {{ JINJA_VAR[:2] }} -test {{ JINJA_VAR[:2] }} -{{ environ["test"] }} -""" + assert select_lines(lines, {"abc": True}, variants_in_place=True) == "\n".join( + ( + "", + "test", + "test [abc] no", + "test [abc] # no", + " ' test '", + ' " test "', + "", + "test", + " 'quoted'", + ' "quoted"', + "test", + "test {{ JINJA_VAR[:2] }}", + "test {{ JINJA_VAR[:2] }}", + "test {{ JINJA_VAR[:2] }}", + "test {{ JINJA_VAR[:2] }}", + '{{ environ["test"] }}', + "", # trailing newline + ) ) - assert ( - select_lines(lines, {"abc": False}, variants_in_place=True) - == """ -test -test [abc] no -test [abc] # no - -test {{ JINJA_VAR[:2] }} -""" + assert select_lines(lines, {"abc": False}, variants_in_place=True) == "\n".join( + ( + "", + "test", + "test [abc] no", + "test [abc] # no", + " ' test '", + ' " test "', + "", + "test {{ JINJA_VAR[:2] }}", + "", # trailing newline + ) ) +@pytest.mark.benchmark +def test_select_lines_battery(): + test_foo = "test [foo]" + test_bar = "test [bar]" + test_baz = "test [baz]" + test_foo_and_bar = "test [foo and bar]" + test_foo_and_baz = "test [foo and baz]" + test_foo_or_bar = "test [foo or bar]" + test_foo_or_baz = "test [foo or baz]" + + lines = "\n".join( + ( + test_foo, + test_bar, + test_baz, + test_foo_and_bar, + test_foo_and_baz, + test_foo_or_bar, + test_foo_or_baz, + ) + * 10 + ) + + for _ in range(10): + for foo, bar, baz in product((True, False), repeat=3): + namespace = {"foo": foo, "bar": bar, "baz": baz} + selection = ( + ["test"] + * ( + foo + + bar + + baz + + (foo and bar) + + (foo and baz) + + (foo or bar) + + (foo or baz) + ) + * 10 + ) + selection = "\n".join(selection) + "\n" # trailing newline + assert select_lines(lines, namespace, variants_in_place=True) == selection + + def test_disallow_leading_period_in_version(testing_metadata): testing_metadata.meta["package"]["version"] = ".ste.ve" testing_metadata.final = True diff --git a/tests/test_render.py b/tests/test_render.py index 6cfd0abeea..aef9d0e928 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -27,7 +27,7 @@ ) def test_noarch_output(build, testing_metadata): testing_metadata.meta["build"].update(build) - output = api.get_output_file_path(testing_metadata) + output = api.get_output_file_paths(testing_metadata) assert os.path.sep + "noarch" + os.path.sep in output[0] diff --git a/tests/test_subpackages.py b/tests/test_subpackages.py index 3937036d14..4fe966c054 100644 --- a/tests/test_subpackages.py +++ b/tests/test_subpackages.py @@ -8,9 +8,9 @@ from pathlib import Path import pytest +from conda.base.context import context from conda_build import api, utils -from conda_build.conda_interface import subdir from conda_build.render import finalize_metadata from .utils import get_valid_recipes, subpackage_dir @@ -55,7 +55,7 @@ def test_rm_rf_does_not_remove_relative_source_package_files( def test_output_pkg_path_shows_all_subpackages(testing_metadata): testing_metadata.meta["outputs"] = [{"name": "a"}, {"name": "b"}] out_dicts_and_metadata = testing_metadata.get_output_metadata_set() - outputs = api.get_output_file_path( + outputs = api.get_output_file_paths( [(m, None, None) for (_, m) in out_dicts_and_metadata] ) assert len(outputs) == 2 @@ -64,7 +64,7 @@ def test_output_pkg_path_shows_all_subpackages(testing_metadata): def test_subpackage_version_provided(testing_metadata): testing_metadata.meta["outputs"] = [{"name": "a", "version": "2.0"}] out_dicts_and_metadata = testing_metadata.get_output_metadata_set() - outputs = api.get_output_file_path( + outputs = api.get_output_file_paths( [(m, None, None) for (_, m) in out_dicts_and_metadata] ) assert len(outputs) == 1 @@ -78,7 +78,7 @@ def test_subpackage_independent_hash(testing_metadata): testing_metadata.meta["requirements"]["run"] = ["a"] out_dicts_and_metadata = testing_metadata.get_output_metadata_set() assert len(out_dicts_and_metadata) == 2 - outputs = api.get_output_file_path( + outputs = api.get_output_file_paths( [(m, None, None) for (_, m) in out_dicts_and_metadata] ) assert len(outputs) == 2 @@ -145,7 +145,7 @@ def test_output_specific_subdir(testing_config): assert len(metadata) == 3 for m, _, _ in metadata: if m.name() in ("default_subdir", "default_subdir_2"): - assert m.config.target_subdir == subdir + assert m.config.target_subdir == context.subdir elif m.name() == "custom_subdir": assert m.config.target_subdir == "linux-aarch64" else: