Skip to content

ci: build and test conda packages and wheels - #378

Open
jharlow-intel wants to merge 16 commits into
intel:mainfrom
jharlow-intel:ci/intel-python-ci
Open

jharlow-intel wants to merge 16 commits into
intel:mainfrom
jharlow-intel:ci/intel-python-ci

Conversation

@jharlow-intel

@jharlow-intel jharlow-intel commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds Intel Internal CI (gh-ci): ci/, a noarch conda-forge recipe in conda-recipe/, and .github/workflows/intel-ci.yml. The workflow builds the conda package and the pure-Python wheel for Python 3.11 to 3.14 on Linux and runs dokimi/tests against each installed package. Intel's internal CI then tests and scans the same packages. release.yml stays the release process: gh-ci publishes nothing for dokimi.

Changes

  • conda-recipe/: noarch: python, conda-forge only, Linux only. tests/test_conda_recipe.py keeps its version, Python floor, console script and run requirements in step with pyproject.toml.
  • ci/test.py: runs the suite with the project's pytest config against the installed package, and fails if dokimi imports from the checkout. Two tests that need the source layout are deselected, and the installed package's data files are checked instead.
  • ci/matrix.py, ci/build.py, ci/hooks.py: the shared gh-ci scripts, plus generic support for a project one directory down and a pure-Python wheel built with pip wheel.

Risk

New files only; nothing existing changes. A release now also bumps the version in conda-recipe/meta.yaml, which test_conda_recipe.py enforces; docs/releasing.md doesn't mention it yet. ci/ is outside the ruff and mypy scope.

Testing

On the fork, with a temporary push trigger: Intel CI built and tested all four conda packages and wheels, and ci passed apart from four test_repo_guidance_history.py tests that need a git identity on GitHub-hosted runners. The changelog-fragment and comment-block checks pass locally.

Revert

Revert the commit; nothing else is left behind.

Checklist

  • Tests added/updated and pytest passes
  • ruff, mypy, and schema checks pass
  • Docs updated (or N/A) — see the docs-drift checklist in CONTRIBUTING.md
  • Self-reviewed per "Before opening a PR" in AGENTS.md
  • No ad-hoc run artifacts committed
  • New files carry the SPDX license header (or are exempt in .github/.licenserc.yaml)

Adds what Intel Python CI (gh-ci) needs: ci/, a noarch conda-forge recipe
and .github/workflows/intel-ci.yml. The workflow builds the conda package and
the pure Python wheel for each Python in the recipe and pyproject.toml, on
Linux, and runs dokimi/tests against each installed package with ci/test.py.
Intel's Jenkins tests and scans the same packages again. release.yml stays
the release process.

The recipe's version, Python floor, console script and run requirements are
checked against pyproject.toml by tests/test_conda_recipe.py. The wheel
matrix follows the recipe's platforms, Linux.

Signed-off-by: Harlow, Jordan <jordan.harlow@intel.com>
@jharlow-intel jharlow-intel self-assigned this Nov 3, 2026
@jharlow-intel
jharlow-intel requested a review from a team as a code owner November 3, 2026 16:01
Copilot AI balanced review requested due to automatic review settings November 3, 2026 16:01
@jharlow-intel jharlow-intel added the enhancement New feature or request label Nov 3, 2026
@exai-bot

exai-bot commented Nov 3, 2026 •

Copy link
Copy Markdown

OpenCodeReview: Commit 09ac3d0 was not reviewed. This PR has reached its review limit (6 reviews).

CI at `09ac3d0`: 11 still running, 8 passing, 2 skipped
Check Status
CodeQL (actions) still running
CodeQL (python) still running
Dependency review still running
Linux Python 3.11 still running
Linux Python 3.14 still running
Linux Wheels Python 3.11 still running
Linux Wheels Python 3.14 still running
Package install smoke test (py3.11) still running
Tests (py3.11) still running
Tests (py3.14) still running

1 more not listed.

To review this commit: re-request review from exai-bot in the Reviewers list.

<details><summary>Last review (ccb25d4): no findings</summary>

Open threads from the bot: 1

</details>

Comment thread .github/workflows/intel-ci.yml Outdated
Comment on lines +17 to +18
group: intel-ci-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
Must-fix — Impact: a push to main can lose its entire build/test run, leaving no artifacts for Jenkins for that commit. Trigger: three main pushes while the first run is still active. Evidence: all main pushes share this concurrency group; GitHub concurrency keeps only one pending run and replaces that pending run when another arrives, even with cancel-in-progress false. The existing ci.yml explicitly avoids this behavior by using github.run_id for non-PR events. Workaround: group PR runs by PR number/ref, but use github.run_id for push/tag runs. Severity justification: medium because this is a bounded but material omission of the intended per-commit CI execution.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Fixed in c60220d: the group is now ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}, the same as ci.yml, so every push and tag run has a group of its own. tests/test_intel_ci.py asserts intel-ci.yml's concurrency equals ci.yml's.

Comment thread ci/build.py Outdated
Comment on lines +278 to +282
built = sorted(
path
for path in output.iterdir()
if path.name.endswith(PACKAGE_SUFFIXES)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
Must-fix — Impact: stale packages are reported as current build outputs and can cause conflicting installations; wheel metadata rewriting also applies to old wheels. Trigger: run the documented build command into the same --output directory before and after changing the package version, then pass that directory to hooks.py. Evidence: collect() enumerates every package in output, not just those moved from scratch; hooks.install() installs all matching artifacts, so two versions of dokimi are requested together. Workaround: use a fresh output directory for every build. Return only the paths moved by this invocation, or reject a nonempty output directory, and add a reused-output regression test. Severity justification: medium because this is a bounded artifact-integrity and local CI failure, not a failure of the workflow's fresh-output path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced with the real CLI: a second ci/build.py --system wheel into the same --output printed build: dokimi-0.0.1-py3-none-any.whl, dokimi-0.1.0-py3-none-any.whl. Fixed in 544dd10: collect() returns only the packages this run moved, so the report and the wheel-requires rewrite cover only this build (dokimi declares no wheel-requires, so the rewrite half was latent here). The upload glob and hooks.py still take every package in the directory; the docstring now says to give each build an empty --output. Refusing a non-empty directory would also cover those, but it changes how Jenkins may call the shared script, so that stays your call.

@codecov-commenter

codecov-commenter commented Nov 3, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.78%. Comparing base (3258d6c) to head (09ac3d0).
⚠️ Report is 38 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #378      +/-   ##
==========================================
+ Coverage   94.40%   94.78%   +0.37%     
==========================================
  Files         313      353      +40     
  Lines       40216    46575    +6359     
  Branches     6822     7974    +1152     
==========================================
+ Hits        37966    44146    +6180     
- Misses       1435     1541     +106     
- Partials      815      888      +73     

see 122 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

4 open issues
What changed in this PR

Adds Intel Internal CI (“gh-ci”) infrastructure to build/test dokimi’s conda package and pure-Python wheels in GitHub Actions, producing artifacts that Intel Jenkins can retest/scan.

Changes:

  • Add conda-build recipe + config to build a noarch: python package (Linux-only) and keep it synchronized with pyproject.toml.
  • Introduce shared CI scripts (ci/matrix.py, ci/build.py, ci/hooks.py, ci/test.py) to build artifacts and run the test suite against the installed package.
  • Add a dedicated GitHub Actions workflow (.github/workflows/intel-ci.yml) and CI configuration (ci/config.yml) to run the matrix and tests.
File Description
dokimi/​tests/​test_conda_recipe.py Adds tests to ensure conda recipe metadata stays aligned with pyproject.toml.
conda-recipe/​meta.yaml Introduces the noarch conda recipe used by gh-ci/Jenkins builds.
conda-recipe/​conda_build_config.yaml Declares the Python versions for conda package builds.
ci/​test.py Runs pytest from the checkout against the installed package and validates import/data-file behavior.
ci/​matrix.py Derives build matrix (platforms + Python versions) from recipe/pyproject declarations.
ci/​hooks.py Installs built artifacts into fresh envs and runs ci/test.py in an isolated way.
ci/​config.yml Defines artifact naming and test dependencies for gh-ci.
ci/​build.py Builds conda packages or wheels per version and collects artifacts for upload.
ci/​README.md Documents how gh-ci works for this repository.
.github/​workflows/​intel-ci.yml Adds the Intel Python CI workflow to build/test conda packages and wheels.

Review effort: Lite

- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: dokimi ${{ matrix.name }} Python ${{ matrix.python }}
path: ${{ runner.temp }}/output/*.conda

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue now: the workflow installs conda-build from conda-forge (26.9.1 today), whose default package format is .conda (Config().conda_pkg_format is CondaPkgFormat.V2), and if-no-files-found: error would make a .tar.bz2 build fail loudly rather than drop it.

Comment on lines +18 to +24
pytestmark = pytest.mark.skipif(not _RECIPE.is_file(), reason="not a checkout: the recipe is next to the project")

_SCRIPT = _PROJECT / "scripts" / "conda_forge_specs.py"
_spec = importlib.util.spec_from_file_location("conda_forge_specs", _SCRIPT)
assert _spec and _spec.loader
cfs = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(cfs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not reproduce: with only dokimi/ copied (no conda-recipe/ next to it) the module imports scripts/conda_forge_specs.py without error and both tests are skipped (2 skipped). A layout without scripts/ would also break the other test files that load a script at module level, so this follows the suite's convention.

recipe, project = _recipe(), _project()
assert str(recipe["package"]["version"]) == project["version"]
assert f"python {project['requires-python']}" in recipe["requirements"]["run"]
entries = dict(e.split(" = ") for e in recipe["build"]["entry_points"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recipe in this repo spells every entry point name = module:func, and a different spelling makes the test fail with a ValueError, not pass silently. Left as is.

Comment thread ci/test.py Outdated
with tempfile.TemporaryDirectory() as home:
# Tests that commit to scratch repositories need a git identity.
gitconfig = pathlib.Path(home) / "gitconfig"
gitconfig.write_text("[user]\n\tname = gh-ci\n\temail = gh-ci@example.com\n")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The content is ASCII, so every default encoding writes the same bytes. Left as is.

@napetrov napetrov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only findings not already raised on this PR (stale packages in collect(), main-push concurrency, .tar.bz2 upload and the Copilot items are already covered and omitted). Found by automated haiku review passes; the three nit: threads were reproduced with a local probe on head, the question: threads were read, not run.

Comment thread ci/test.py
return pathlib.Path(shown.stdout.strip()).resolve()


CHECK_DATA = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: a wheel missing products.yaml or intents.yaml still passes gh-ci

CHECK_DATA checks only DEFAULT_PRODUCTS_PATH and DEFAULT_SCHEMA_PATH, but it replaces the deselected test_default_data_files_ship_in_the_wheel, which covers four files; registry._DEFAULT_REGISTRY and intents._DEFAULT_INTENTS are dropped.
Fix: add those two paths to paths. (Read, not run.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: CHECK_DATA printed two of the four paths. Fixed in a5c235e: it now checks registry._DEFAULT_REGISTRY and intents._DEFAULT_INTENTS as well, and tests/test_intel_ci.py compares its output with the four files of the deselected test. Moving intents.yaml or products.yaml aside now makes the check exit 1.

Comment thread ci/matrix.py Outdated
"s390x": False,
},
}
CONDA_SKIP_RE = re.compile(r"\s*skip\s*:\s*(?:true|True|yes)\s*#\s*\[(.+)\]\s*")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: a skip line with a trailing comment keeps the skipped platform in the matrix

CONDA_SKIP_RE requires the line to end at ].
How to check: from_conda_recipe on skip: true # [win] # windows not supported → ['linux','windows','macos'] (# [win] alone → ['linux','macos']).
Expected: windows excluded, as conda-build does. Fix: drop the end anchor / allow \s*(#.*)?$.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduces (skip: true # [win] # windows not supported keeps windows; conda-build applies that selector). Latent for dokimi, whose only skip is # [not linux] with no trailing comment. matrix.py is a shared gh-ci script, so the fix belongs in the shared copy; left to the author.

Comment thread ci/matrix.py Outdated
)
CLASSIFIER_RE = re.compile(r"Programming Language :: Python :: 3\.(\d+)")
FREE_THREADING_CLASSIFIER = "Programming Language :: Python :: Free Threading"
LOWER_BOUND_RE = re.compile(r">=\s*3\.(\d+)")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: requires-python = ">3.10" still yields a 3.10 cell; upper bounds ignored

LOWER_BOUND_RE only reads >=3.N.
How to check: from_pyproject with >3.10 and a 3.10 classifier → ['3.10'].
Expected: []. Fix: filter classifiers with packaging.specifiers.SpecifierSet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduces (>3.10 with a 3.10 classifier gives ['3.10']). For the >= form the script raises a DeclarationError rather than filtering, so the matching result here would be that error, not []. Latent: dokimi declares >=3.11. Shared script, left to the author.

Comment thread ci/matrix.py
CONDA_SKIP_RE = re.compile(r"\s*skip\s*:\s*(?:true|True|yes)\s*#\s*\[(.+)\]\s*")
OS_CLASSIFIERS = {
"Operating System :: OS Independent": tuple(PLATFORMS),
"Operating System :: POSIX": ("linux",),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: a FreeBSD-only classifier produces Linux cells

OS_CLASSIFIERS prefix-matches Operating System :: POSIX.
How to check: from_os_classifiers with Operating System :: POSIX :: BSD :: FreeBSD → ['linux'].
Expected: no Linux cell. Fix: exact match for the bare POSIX key.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduces (Operating System :: POSIX :: BSD :: FreeBSD gives ['linux']). Latent: dokimi's pyproject.toml names no Operating System. Shared script, left to the author.

build:
name: ${{ matrix.name }} Python ${{ matrix.python }}
needs: matrix
runs-on: ${{ vars.GH_CI_RUNNER || (github.repository_owner == 'intel' && 'intel-ubuntu-latest' || 'ubuntu-latest') }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Windows/macOS matrix cells would build on the Linux runner

build_matrix can emit non-Linux cells (recipe without not linux skip, or OS classifiers), but every job's runs-on ignores matrix.platform, so such a cell builds Linux packages under a Windows/macOS artifact name. Latent today. Should runs-on map from matrix.platform, or matrix.py reject non-Linux?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latent: the recipe skips not linux and pyproject.toml names no Operating System, so matrix.py emits Linux cells only. Mapping runs-on from matrix.platform or refusing non-Linux cells is a design call for the author.

Comment thread ci/build.py Outdated
record = metadata.removesuffix("METADATA") + "RECORD"
for index, (info, data) in enumerate(members):
if info.filename == metadata:
head, blank, body = data.decode("utf-8").partition("\n\n")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: CRLF METADATA would put Requires-Dist after the body

partition("\n\n") finds no separator with CRLF, so the whole file is treated as the header and the added requirements land after the description, where installers ignore them. Latent (pip writes LF); splitting on \r?\n\r?\n would cover it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct for CRLF metadata (partition("\n\n") finds no separator). Latent twice over: pip and setuptools write LF, and dokimi declares no [tool.gh-ci] wheel-requires, so add_requirements never runs for dokimi. Left to the author for the shared script.

The recipe added in this PR repeats the version, and test_conda_recipe.py
fails a release PR that bumps only pyproject.toml, as step 1 said to.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
The wheel cells resolved every dependency from PyPI, so litellm 1.108.0
failed 16 tests on any pull request that changes no dependency. Every
event but a tag now installs dokimi/requirements.lock.txt with --no-deps
--require-hashes, then the wheel with --no-deps. A tag still tests what a
user of the release gets. The conda cells are unchanged: conda-forge does
not carry the lock's versions, so the lock cannot pin them.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Nov 4, 2026

Copy link
Copy Markdown
Contributor
  • Ran: the full suite at the head plus our commits, the release step from docs/releasing.md, and the wheel test cells through ci/hooks.py --kind wheel for 3.11 and 3.14.
  • Pushed:
    • 70f3fce: releasing.md said the version lives in one place and step 1 bumps only pyproject.toml, which fails test_conda_recipe.py. The doc now names the copy in meta.yaml.
    • 91ba665: the wheel cells resolved every dependency from PyPI, so litellm 1.108.0 (released today) failed 16 tests on any PR, although ci.yml keeps its latest-deps run off PRs. For every event but a tag, hooks.py now installs dokimi/requirements.lock.txt with --no-deps --require-hashes, then the wheel with --no-deps. A tag still tests what a release's users get.
  • Wheel cells on the host: --event pr installs litellm 1.106.4 and passes on 3.11 and 3.14. --event tag installs 1.108.0 and gives the 16 failures.
  • Not changed: the conda cells. conda-forge has litellm 1.106.0 at most, and not the lock's 1.106.4, so the lock cannot pin them. Jenkins installs from config.yml and does not run hooks.py, so its wheel cells still resolve from PyPI.

CodeFactor's bandit flags B310 on the urlopen, which fetches a fixed https
URL and checks the binary against a pinned sha256. The nosec matches the
other urlopen calls in the repo.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

36bcbae: CodeFactor's one finding was bandit B310 on the rattler-build urlopen in ci/build.py, a fixed https URL with a pinned sha256. It now carries # nosec B310, like the repo's other urlopen calls; bandit reports it before and not after.

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at 36bcbae. The recipe, the version sync and the lock-based wheel install read well. Three things block merge: the repo's own make ci-local crashes on the new workflow, main pushes can lose their run, and the data-file check in ci/test.py is weaker than the test it replaces.

What I ran, on a Linux review host at 36bcbae:

  • make ci-local: aborts before running any step (see the inline comment on the matrix).
  • tests/test_conda_recipe.py: 2 passed. Mutating the recipe's version, Python floor, entry point and run requirements: 22 variants, all caught.
  • zizmor on intel-ci.yml: no findings. pip wheel plus add_requirements on the real project gives a valid wheel with the two Requires-Dist lines.

Open bot points I confirm:

  • I agree with exai-bot that the concurrency group on main pushes can drop a run. New evidence: ci.yml:26-29 says the same and uses github.run_id for non-PR events; intel-ci.yml:16-18 keys on github.ref, so every main push shares one group.

Waiting on the author or a decision: @napetrov's question on ci/test.py:49 (the wheel check covers two of the four data files) is still unfixed at this head. The comment above CHECK_DATA says the deselected test is the only check of these files, but test_default_data_files_ship_in_the_wheel has four parameters (products.yaml, intents.yaml, DEFAULT_PRODUCTS_PATH, DEFAULT_SCHEMA_PATH) and CHECK_DATA checks two. A wheel without products.yaml or intents.yaml passes gh-ci. @napetrov's questions on runs-on ignoring matrix.platform and on CRLF METADATA wait for the author.

Checked, no issues

  • Commits: all four carry a Signed-off-by.
  • Repo rules: no changelog fragment is needed (nothing under dokimi/dokimi/, dokimi/data/ or pyproject.toml changes); releasing.md is already in the nav; SPDX headers are present; no generated artifacts.
  • Lock install: every requirement in ci/config.yml is in requirements.lock.txt at a satisfying version.
Bot points I checked that no longer apply at this head (4)
  • exai-bot's point on stale packages in collect() does not apply to the workflow: every build job writes to a fresh $RUNNER_TEMP/output, so only a local rerun into a reused --output is affected.
  • Copilot's point on the *.conda upload glob does not apply: the job installs the latest conda-build from conda-forge, which writes .conda, and the fork run in the description uploaded all four artifacts under if-no-files-found: error.
  • Copilot's point on the module-level import in test_conda_recipe.py does not apply: scripts/conda_forge_specs.py sits next to tests/ in every layout where the tests run, and ci/test.py runs them from the checkout.
  • Copilot's point on splitting entry points on ' = ' does not apply: the test compares against meta.yaml, so a different spacing fails loudly instead of passing silently.

Comment thread .github/workflows/intel-ci.yml Resolved
intel-ci.yml's fromJSON matrix is a string: ci_pythons() crashed on it, so make ci-local
could not run. Such a job builds packages, so it is reported as a skip.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
One group per ref let a third main push replace the pending second run, so Jenkins
never got that commit's packages. Group as ci.yml does: by PR number, else run_id.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
collect() listed every package in --output, so a reused one reported an earlier build's
packages as this build's and added wheel-requires to them again.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
…st covers

products.yaml and intents.yaml were dropped, so a wheel missing either still passed.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Second round on the review threads, on top of 36bcbae. Four commits pushed to the branch, one per defect, each with a test that fails at 36bcbae:

  • a213e13 fix(ci-local): make ci-local crashed on intel-ci.yml's fromJSON matrix (antonwolfy). ci_pythons() now reads legs only from a mapping, and a job whose matrix comes from another job's output is reported as a skip, so its conda install -n base step does not run on a developer machine.
  • c60220d ci(intel-ci): the concurrency group is now ci.yml's (pull_request.number || run_id), so a queued push to main is never replaced by a later one.
  • 544dd10 fix(ci): build.py reports and rewrites only the packages it built, not ones an earlier build left in --output.
  • a5c235e fix(ci): ci/test.py checks all four default data files of the test it deselects, adding products.yaml and intents.yaml.

On the head and merged with main (Python 3.13, Linux): the full suite, ruff, mypy, bandit, zizmor, the benchmark gate, check_comment_blocks, check_growth and the changelog check all pass, and build.py --system wheel then hooks.py --kind wheel --event pr passes against the installed wheel.

Still open, for the author:

  • build.py: the upload glob and hooks.py still take every package in the directory. The docstring now says to give each build an empty --output. Refusing a non-empty directory would cover both, but it changes how Jenkins may call the shared script.
  • matrix.py (trailing comment after a skip selector, >3.10, the FreeBSD classifier) and build.py (CRLF METADATA): all reproduce, all latent for dokimi. These belong in the shared gh-ci copy.
  • runs-on for non-Linux cells: latent, as the matrix is Linux only today. Design call.

The copilot threads (module-level import, .tar.bz2 upload, entry-point split, gitconfig encoding) are answered inline: none fails on this repository.

@xaleryb xaleryb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at a5c235e: CI green, Intel CI cells included; the blocking threads are fixed, and what stays open is latent for dokimi or in the shared gh-ci copy.

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at a5c235e. The four fixes since my last review hold: the fromJSON matrix no longer crashes make ci-local, main pushes keep their own concurrency group, collect() lists only what it built, and the data check covers all four files of the test it replaces. One nit inline.

What I ran, on a Linux review host at a5c235e:

  • make ci-local: 31 passed, 0 failed, 11 skipped. The four intel-ci.yml jobs with a fromJSON matrix are reported as skips; the matrix job runs and passes. Changed-line coverage passes.
  • Mutation: reverting the collect() change, dropping the two added paths from the data check, and reverting the concurrency group each make a test in test_intel_ci.py fail. Reverting either half of the ci_local.py fix makes the new test_ci_local.py case fail.

Waiting on the author or a decision: @napetrov's questions on runs-on ignoring matrix.platform and on CRLF METADATA, and the three matrix.py points (skip line with a trailing comment, >3.10, FreeBSD classifier), are latent for dokimi and live in the shared gh-ci scripts. They wait for the author.

Checked, no issues

  • Commits: all eight carry a Signed-off-by.
  • Data check: CHECK_DATA lists the same four paths as the parameters of the deselected test.
  • Concurrency: the group expression is identical to ci.yml:28.
Bot points I checked that no longer apply at this head (5)
  • exai-bot's point on the shared concurrency group does not apply: intel-ci.yml:18 now uses ci.yml's pull_request.number || run_id, and a test pins the two equal.
  • exai-bot's point on stale packages in collect() does not apply to what it reports: collect() now returns only the packages this run moved, and every workflow build job writes to a fresh output directory.
  • Copilot's point on the *.conda upload glob does not apply: conda-build from conda-forge writes .conda, and if-no-files-found: error fails loudly on any other format.
  • Copilot's point on the module-level import in test_conda_recipe.py does not apply: scripts/conda_forge_specs.py sits next to tests/ wherever the tests run.
  • Copilot's point on splitting entry points on ' = ' does not apply: the test compares with meta.yaml, so another spelling fails loudly instead of passing.

Comment thread dokimi/scripts/ci_local.py Resolved
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a commit per nit, up to 21b51d5, which fix the nit from my review:

  • 21b51d5: dokimi/scripts/ci_local.py:30: the docstring now says build and test jobs, matching the four fromJSON jobs that are skipped.

make ci-local at 21b51d5: 31 passed, 0 failed, 11 skipped.

Not approving yet. Still open: @napetrov on runs-on ignores matrix.platform: map it or make matrix.py reject non-Linux cells (design call, latent); @napetrov on CRLF METADATA in build.py add_requirements and the three matrix.py points (trailing-comment skip, >3.10, FreeBSD classifier) are in the shared gh-ci scripts, latent for dokimi.

… and POSIX as declared

intel-ci.yml runs every job on a Linux runner, so BUILT_PLATFORMS is Linux and a
declared Windows or macOS platform is reported and left out instead of getting a cell.
A skip selector may carry a trailing comment, as in conda-build's sel_pat. Every
requires-python clause now counts: a classifier it excludes on its newest patch release
is an error, as >= already was (>3.10 still admits 3.10.1). A POSIX sub-classifier other
than POSIX :: Linux, such as FreeBSD, no longer means Linux.

Signed-off-by: Harlow, Jordan <jordan.harlow@intel.com>
partition("\n\n") found no blank line in CRLF METADATA, so the Requires-Dist lines
landed after the description, where installers ignore them.

Signed-off-by: Harlow, Jordan <jordan.harlow@intel.com>
@napetrov

napetrov commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

My open points here do not block this PR. They are latent for dokimi and live in the shared gh-ci scripts, so they move to #385:

  • runs-on: matrix.py should reject non-Linux cells (no mapping for now).
  • CRLF METADATA, the trailing-comment skip, >3.10 and the FreeBSD classifier.

From my side, nothing is left open on #378.

Comment thread ci/README.md Resolved
Signed-off-by: Harlow, Jordan <jordan.harlow@intel.com>
Signed-off-by: Harlow, Jordan <jordan.harlow@intel.com>
Comment thread ci/config.yml
Comment on lines +20 to +21
- "markdown-it-py >=3"
- packaging

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · high
CI check Test Linux Python 3.11 fails: The new conda environment installs ci/config.yml test.requires, which omits mypy (a project dev dependency). This causes the typecheck test collection error and the repeated mypy version-probe failures. Proposed fix, not run by the bot.

Suggestion:

Suggested change
- "markdown-it-py >=3"
- packaging
- "markdown-it-py >=3"
- packaging
- "mypy >=1.8"

@napetrov napetrov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: comment only. No blocking thread; four nits, inline. Head ab77902, merge base 3258d6c, lane L3. Earlier threads re-checked on head: most are fixed, four are still open (below).

CI: 2 failing jobs, Intel CI / Test Linux Python 3.11 and 3.14, with no base run of this workflow to compare. Their logs name tests/test_repo_l0_typecheck.py, which is in neither tree, so CI may have tested a different tree than head. Question: which commit did those cells check out? Please re-run them on ab77902 after that is settled; I could not attribute the failures to the PR.

# Finding Label Provenance Scope Probe
1 a build under a broken directory above scratch loses its package nit introduced local probes/out-p3_collect.txt
2 a non-ASCII digit in a classifier is read as a Python version nit introduced local probes/out-p6_b2_b4.txt
3 config.yml workflow and artifacts keys are read by nothing (C3) nit introduced local probes/out-b3.txt
4 a non-UTF-8 pyproject.toml ends the matrix job with a traceback nit introduced local probes/out-b4.txt

Raised before (status on head):

  • @copilot-pull-request-reviewer, test_conda_recipe.py line 24: still open. Our probe reproduces it: collection aborts with FileNotFoundError when scripts/ is absent (probes/out-t4.txt).
  • @copilot-pull-request-reviewer, upload glob, intel-ci.yml line 78: still open. Its entry-point split and gitconfig encoding threads: still open (latent).
  • @antonwolfy, make ci-local crash, main-push concurrency, collect() and the docstring: fixed on head.
  • napetrov, wheel data files (ci/test.py line 48), trailing-comment skip, FreeBSD classifier, CRLF METADATA: fixed on head.
  • napetrov, >3.10 yields a 3.10 cell (ci/matrix.py line 162): still open (probes/out-p4_edges.txt).
  • napetrov, Windows and macOS cells on the Linux runner (intel-ci.yml line 47): still open, latent; conda cells are Linux only today.

Coverage: checked the diff, the ci/ readers on head with the probes above, the napetrov and bot threads against head, and the CI log lines against both trees. Not looked at: the full pytest suite (pytest plugins missing here, ran with the addopts cleared), conda and wheel builds (no conda here), ruff and mypy.

Comment thread ci/build.py
if (
package.is_file()
and package.name.endswith(PACKAGE_SUFFIXES)
and "broken" not in package.parts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: a build whose temp path has a directory named broken loses its package

ci/build.py:285 (collect): the filter tests package.parts, the whole absolute path, not the path under the scratch dir. A TMPDIR such as /x/broken drops every package, and the job fails with build: the build produced no package.

How to check: collect(root/"scratch", out) with a package under root/broken/ci-run/ → BuildError: the build produced no package (probes/out-p3_collect.txt; introduced, new file).
Expected: only packages under the scratch dir are filtered; a build under /x/broken keeps its package.
This is a different defect from the stale-package one in the same function (#378 (comment), fixed on head): here the filter itself is wrong.

Fix: test package.relative_to(scratch).parts, and add a test with a broken directory above the scratch dir.

Comment thread ci/matrix.py
CONDA_SPEC_RE = re.compile(
r"3\.(\d+)(?:\.\*)?(?:\s+\*_(?:cpython|cp3(\d+)(t?)))?"
)
CLASSIFIER_RE = re.compile(r"Programming Language :: Python :: 3\.(\d+)")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: a non-ASCII digit in a classifier is read as a Python version

ci/matrix.py:169 (CLASSIFIER_RE): \d matches any Unicode decimal digit and int() accepts it, so a classifier with a fullwidth 4 reads as 3.14 with no error. REQUIRES_PYTHON_RE uses \d the same way. The conda reader rejects the same digit (CONDA_SPEC_RE), so only the pyproject reader is silent.

How to check: from_pyproject with Programming Language :: Python :: 3.14 → [Python(version='3.14')] (probes/out-p6_b2_b4.txt; introduced, new file).
Expected: a non-ASCII digit is refused, as the conda reader refuses it.
Fix: use [0-9] in CLASSIFIER_RE and REQUIRES_PYTHON_RE, with a test on a fullwidth digit.

Comment thread ci/README.md
| `matrix.py` | Reads the platforms and Python versions from the recipe, or with `--system wheel` from `dokimi/pyproject.toml`. A pull request gets the oldest and the newest versions. |
| `build.py` | Builds one Python version: with conda-build, which runs the recipe's own tests, or with `--system wheel` with `pip wheel`. |
| `hooks.py` | The workflow's test jobs: installs one package into a clean environment with the requirements in `config.yml`, and runs `test.py`. A conda package's environment comes from conda-forge; a wheel's has only Python from conda-forge and everything else from PyPI, pinned by `dokimi/requirements.lock.txt` except on a release tag. |
| `config.yml` | Names the workflow that builds the packages, its artifact names, and what the tests need. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: config.yml's workflow and artifacts keys are read by nothing

ci/README.md:18 (config.yml): the README says the file names the workflow and its artifact names. No code in ci/ reads those keys (hooks.py reads only the test key), and intel-ci.yml hard-codes the names, so editing them changes nothing, silently.

How to check: grep for readers of workflow and artifacts in ci/*.py → none (probes/out-b3.txt; introduced, new file).
Expected: the README describes only keys something reads.
Fix: remove the two keys and the README clause, or read the names from the file where the workflow uses them.

Comment thread ci/matrix.py
if __name__ == "__main__":
try:
sys.exit(main())
except DeclarationError as error:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: a non-UTF-8 pyproject.toml ends the matrix job with a bare traceback

ci/matrix.py:736 (main): it catches only DeclarationError, but from_pyproject lets UnicodeDecodeError escape, and invalid TOML raises TOMLDecodeError the same way. The job still exits 1, with a Python traceback instead of the matrix: message.

How to check: from_pyproject on a latin-1 pyproject.toml → UnicodeDecodeError (probes/out-b4.txt); invalid TOML → TOMLDecodeError (probes/out-p4_edges.txt; introduced, new file).
Expected: a DeclarationError naming the file, as for the other bad inputs.
Fix: decode and parse inside the reader and raise DeclarationError, with a test on a latin-1 file.

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at ab77902. The delta since 21b51d5 holds up: the CRLF METADATA fix, the trailing-comment skip, the exact POSIX classifier match and the requires-python filter all give the right answers on the cases I tried. The merge still waits for one open point: exai-bot's report that the conda test jobs fail without mypy in ci/config.yml. One nit inline.

What I ran, on a Linux review host at ab77902:

  • make ci-local: 30 passed, 1 failed, 11 skipped; 12,964 tests. The one failed step is ci.yml:test, with two timing assertions (test_unbalanced_quote_in_hook_completes_quickly: 1.19 s against a 1 s limit; test_the_regex_limit_is_shared_by_every_pattern_of_one_check). Neither file is touched by the PR, and both pass when run alone (2 passed), so I do not count them against it.
  • Probes of with_requirements (LF, CRLF, no body, empty list) and of admits (>=3.11,<4, <3.15, !=3.12, ~=3.12, ~=3.12.1, >3.12.1, <=3.12.5, malformed clauses): all correct. Mutations of the new code: forcing LF, dropping the blank line, reverting collect() to list the whole directory, emptying the POSIX exact list and adding windows to BUILT_PLATFORMS are each caught; the mutants named in the inline nit pass the full test_intel_ci.py.

Waiting on a fix: the conda test jobs install only what ci/config.yml test.requires lists. exai-bot's CI log at this head has Test Linux Python 3.11 and 3.14 failing on a typecheck test that needs mypy. That test (tests/test_repo_l0_typecheck.py) is not in the PR's base, so I could not reproduce the failure here. Today's main has it, and dokimi/requirements-test.txt there pins mypy==2.4.0 (line 15), so once this PR is merged into main the conda test jobs need mypy in test.requires.

I agree with @napetrov that the "broken" not in package.parts filter in ci/build.py:285 tests the whole absolute path. New evidence: deleting the filter leaves all 23 tests in test_intel_ci.py passing.

Open bot points I confirm:

  • I agree with exai-bot that ci/config.yml test.requires omits mypy, which dokimi/pyproject.toml lists in its dev extra. New evidence: today's main has tests/test_repo_l0_typecheck.py and pins mypy in requirements-test.txt, so the omission shows once this PR is merged into it.

Checked, no issues

  • Commits: all 13 carry a Signed-off-by.
  • Docs: the commands and flags in ci/README.md exist; the workflow is Linux only, as matrix.py and the README say; artifact names in the upload and download steps match.
  • Concurrency and collect(): the group equals ci.yml's, and collect() returns only the packages a run moved.
Bot points I checked that no longer apply at this head (5)
  • exai-bot's point on the shared concurrency group does not apply: intel-ci.yml uses ci.yml's pull_request.number || run_id, and a test pins the two equal.
  • exai-bot's point on stale packages in collect() does not apply to what it reports: collect() returns only the packages this run moved, and every workflow build job writes to a fresh output directory.
  • Copilot's point on the *.conda upload glob does not apply: conda-build from conda-forge writes .conda, and if-no-files-found: error fails loudly on any other format.
  • Copilot's point on the module-level import in test_conda_recipe.py does not apply: scripts/conda_forge_specs.py sits next to tests/ wherever the tests run.
  • Copilot's point on splitting entry points on ' = ' does not apply: the test compares with meta.yaml, so another spelling fails loudly instead of passing.

("<3.13", [12, 13], "3.13"),
("<=3.13", [13], "3.13"),
(">=3.10,!=3.12.*", [10, 12, 13], "3.12"),
("~=3.12", [11, 12], "3.11"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the ~= upper bound and the ==X.* branch of admits are not pinned.

Mutation check: replacing the ~= branch in ci/matrix.py with admitted = left >= right passes the full file (23 passed). So does replacing the ==/!= wildcard branch with one that only handles !=. The real function returns False for ~=3.12.1 on 3.13 and 3.14, and True for ==3.12.* on 3.12 only.

Suggestion: add two rows after line 107: ("~=3.12.1", [12, 13], "3.13"), and ("==3.12.*", [12, 13], "3.13"),.

main's typecheck tests run mypy (requirements-test.txt pins 2.4.0), and a
conda test cell installs only the package and ci/config.yml's list, so both
cells failed those tests. A test now checks the list against requirements-test.txt.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
build.py and matrix.py named MKL as the reason for wheel-requires and for
x86_64-only macOS; dokimi neither uses MKL nor builds for macOS.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
The pyproject table is named after this repo's own script, ci/build.py.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Nov 6, 2026

Copy link
Copy Markdown
Contributor
  • Ran: main 0e993b9 merged with ab77902 plus the commits below. On Linux, Python 3.13: the full suite, ruff, mypy, bandit (dokimi and ci), the benchmark gate, check_comment_blocks and check_growth. Also ci/build.py --python 3.11, then ci/hooks.py --python 3.11 on that conda package.
  • Pushed:
    • b801dde: the conda test cells install only ci/config.yml's list, and that list has no mypy. After a merge with main, every test_repo_l0_typecheck.py test fails with No module named mypy. Adding mypy ==2.4.0, as requirements-test.txt pins it, makes them pass in the 3.11 conda cell. A new test checks the list against requirements-test.txt.
    • f791e98: two script comments gave a library dokimi does not use as the reason.
    • 09ac3d0: build.py reads wheel-requires from [tool.ci-build], a table named after the script itself.
  • Left for the author: the PR body and the message of fd50205 describe CI systems this repository does not define. Please describe only the workflow, ci/ and the recipe. The squash message carries both.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants