Skip to content

CI Move Pyodide CI from Azure to GitHub Actions #29791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 69 commits into from
Mar 26, 2025

Conversation

agriyakhetarpal
Copy link
Contributor

@agriyakhetarpal agriyakhetarpal commented Sep 5, 2024

Reference Issues/PRs

Discussed in pypa/cibuildwheel#1878, and on top of #31058.

What does this implement/fix? Explain your changes.

This PR reinstates the creation of the Pyodide virtual environment through the pyodide venv <dir> command as a conventional means of running the scikit-learn test suite with pytest in favour of the Node.js-based wrapper script build_tools/azure/pytest-pyodide.js. Previously, unresolved symbol errors were coming from symbol visibility issues post OpenBLAS's linkage (pyodide/pyodide#3331) in the in-tree recipe for SciPy, which seem to have more or less subsided with our updates to it in recent months (see pyodide/pyodide#4719, pyodide/pyodide#5012, pyodide/pyodide#5031, and so on). These issues did not affect the Node.js-based runner, which explains the case for its usage.

As a simplification, cibuildwheel performs the necessary changes in emscripten.yml through the CIBW_PLATFORM: pyodide environment variable that was released as a part of cibuildwheel 2.19. Helper scripts that were used to install Pyodide and the Emscripten toolchain are no longer needed.

Also, this PR drops the [pyodide] commit marker as a by-product of switching from Azure Pipelines to GitHub Actions, which means that Pyodide/WASM as a platform is now tested on every commit.

Any other comments?

Copy link

github-actions bot commented Sep 5, 2024

✔️ Linting Passed

All linting checks passed. Your pull request is in excellent shape! ☀️

Generated for commit: a7bb1e6. Link to the linter CI: here

@agriyakhetarpal
Copy link
Contributor Author

agriyakhetarpal commented Sep 5, 2024

Note

UPDATE, 07/09/2024: this works now, this comment is not relevant.


This currently doesn't work and raises an obscure error (workflow logs) from within the Pyodide xbuildenv: ModuleNotFoundError: No module named 'js.process' for some reason with Pyodide xbuildenv versions 0.26.1/0.26.2/0.27.0a2 (tested in a PR on my fork). I am currently investigating this, and it should be resolvable, hopefully. cc: @hoodmane and @ryanking13

However, at the same time, creating a Pyodide virtual environment in a freshly spun-up Pyodide Docker container (and after removing a few conflicting, irrelevant conftest.py files temporarily):

Tap to expand logs
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_get_visual_block_voting PASSED
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_get_visual_block_column_transformer PASSED
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_estimator_html_repr_pipeline PASSED
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_stacking_classifier[None] PASSED
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_stacking_classifier[final_estimator1] PASSED
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_stacking_regressor[None] PASSED
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_stacking_regressor[final_estimator1] PASSED
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_birch_duck_typing_meta PASSED
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_ovo_classifier_duck_typing_meta PASSED
.venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_estimator_html_repr.py::test_duck_typing_nested_estimator PASSED
# ...
# [...] (truncated output)
# ...
('logisticregression',LogisticRegression())]),param_grid={'logisticregression__C':[0.1,1.0]})-check_supervised_y_2d] - DataConversionWarning not caught
XPASS .venv-pyodide/lib/python3.12/site-packages/sklearn/tests/test_common.py::test_search_cv[HalvingGridSearchCV(cv=2,error_score='raise',estimator=Pipeline(steps=[('pca',PCA()),('logisticregression',LogisticRegression())]),min_resources='smallest',param_grid={'logisticregression__C':[0.1,1.0]},random_state=0)-check_supervised_y_2d] - DataConversionWarning not caught
XPASS .venv-pyodide/lib/python3.12/site-packages/sklearn/tests/test_common.py::test_search_cv[RandomizedSearchCV(cv=2,error_score='raise',estimator=Pipeline(steps=[('pca',PCA()),('logisticregression',LogisticRegression())]),param_distributions={'logisticregression__C':[0.1,1.0]},random_state=0)-check_supervised_y_2d] - DataConversionWarning not caught
XPASS .venv-pyodide/lib/python3.12/site-packages/sklearn/tests/test_common.py::test_search_cv[HalvingRandomSearchCV(cv=2,error_score='raise',estimator=Pipeline(steps=[('pca',PCA()),('logisticregression',LogisticRegression())]),param_distributions={'logisticregression__C':[0.1,1.0]},random_state=0)-check_supervised_y_2d] - DataConversionWarning not caught
XPASS .venv-pyodide/lib/python3.12/site-packages/sklearn/utils/tests/test_testing.py::test_create_memmap_backed_data - memmap not fully supported
FAILED .venv-pyodide/lib/python3.12/site-packages/sklearn/tests/test_build.py::test_openmp_parallelism_enabled - AssertionError: 
==================================== 1 failed, 28699 passed, 4846 skipped, 761 xfailed, 52 xpassed, 3541 warnings in 787.87s (0:13:07) =====================================
(.venv-pyodide) agriyakhetarpal@c1f89ae6ff12:/src$ 

and all tests pass (the failing one can be ignored by enabling the SKLEARN_SKIP_OPENMP_TEST environment variable, of course). This suggests to me that there's a missing file in the xbuildenv that exists in the Pyodide repository/Docker container for it. The only related piece of code I found was in pyodide/pyodide#3189.

However, I don't see why import sklearn as a Pythonic statement would require this? I have recently implemented similar changes for statsmodels and scikit-image, and for other projects, too – and their test suites run without a hitch.

@agriyakhetarpal agriyakhetarpal marked this pull request as draft September 5, 2024 20:44
@agriyakhetarpal
Copy link
Contributor Author

agriyakhetarpal commented Sep 5, 2024

I figured out the error 😅

2024-09-05T18:42:07.5543835Z # possible namespace for /home/runner/work/scikit-learn/scikit-learn/doc/js
2024-09-05T18:42:07.5545980Z import 'js' # <_frozen_importlib_external.NamespaceLoader object at 0xfa1be8>
2024-09-05T18:42:07.5625899Z PythonError: # zipimport: zlib available

For circumstances unexplained to me, I found that it was trying to import a JavaScript file/module from doc/js/ as if it were a Python one, which I had switched to, to avoid running tests from the sklearn/ directory (where things wouldn't be compiled). I've switched to a different directory, i.e., maint_tools/, and import sklearn now works.

I think we should fix this in Pyodide upstream – import js in Python shouldn't try to import something in js/ (but I don't know if we can avoid this cleanly).

The build now fails with a conftest. py-related error, which shouldn't be too difficult; I'll take a look at it either later in the day or tomorrow.

@agriyakhetarpal agriyakhetarpal force-pushed the updates-for-emscripten-ci branch from 3b30e89 to 7efd95d Compare September 6, 2024 09:30
@agriyakhetarpal
Copy link
Contributor Author

So, we will have tempita from Cython when cross-compiling, of course, but having it inside the sklearn folder is a bit troublesome: Cython is only used at build-time with Meson, and not during runtime – the from Cython import Tempita as tempita statement in sklearn/_build_utils/tempita.py breaks the sklearn import in Pyodide (I'm unsure how this has been working in the Node.js-based wrapper script). I did install Cython temporarily with micropip because it has a pure Python wheel to fix the import, but the better way would be to move the tempita CLI elsewhere so that it is accessed somewhere outside the sklearn/ folder (such that it does not break the sklearn import, but can still be retrieved by Meson and be used as a generator).

@agriyakhetarpal
Copy link
Contributor Author

Just four errors are left to fix:

../.venv-pyodide/lib/python3.12/site-packages/sklearn/datasets/tests/test_openml.py::test_fetch_openml_verify_checksum[True-liac-arff] ERROR
../.venv-pyodide/lib/python3.12/site-packages/sklearn/datasets/tests/test_openml.py::test_fetch_openml_verify_checksum[False-liac-arff] ERROR
../.venv-pyodide/lib/python3.12/site-packages/sklearn/datasets/tests/test_openml.py::test_fetch_openml_verify_checksum[True-pandas] ERROR
../.venv-pyodide/lib/python3.12/site-packages/sklearn/datasets/tests/test_openml.py::test_fetch_openml_verify_checksum[False-pandas] ERROR

which are most likely coming from the difference between the Node.js runner and the Pyodide virtual environment, because the latter uses the conftest.py file in the root directory, while the former did not. Once done, I can start migrating the workflow to Azure Pipelines and clean up the changes.

@agriyakhetarpal
Copy link
Contributor Author

I have triggered both the Pyodide venv and the Azure tests, and test_fetch_openml_verify_checksum is skipped for the latter. We now collect a slightly bigger number of tests ("collected 36856 items / 2 skipped"), whereas the Azure job collects/collected fewer items ("collected 36288 items / 1 skipped"). I pushed 382c8ba to remove an unused argument that was being perceived as a fixture – if that doesn't work, we could potentially skip the test to keep the same behaviour.

Copy link
Contributor Author

@agriyakhetarpal agriyakhetarpal left a comment

Choose a reason for hiding this comment

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

These changes are now ready for an initial review; thanks! I won't perform any further force-pushes here.

@@ -1459,7 +1459,7 @@ def _mock_urlopen_raise(request, *args, **kwargs):
(False, "pandas"),
],
)
def test_fetch_openml_verify_checksum(monkeypatch, as_frame, cache, tmpdir, parser):
def test_fetch_openml_verify_checksum(monkeypatch, as_frame, tmpdir, parser):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here, cache was a spurious argument that was unused throughout the file. It broke the Pyodide tests and returned an error upon collection because the interpreter assumed that this was a fixture.

@agriyakhetarpal
Copy link
Contributor Author

agriyakhetarpal commented Mar 25, 2025

I added the "upload to anaconda.org" logic, @agriyakhetarpal feel free to have a closer look. I tested it on my fork (build log) and it seems to work OK (except the actual upload because I didn't set up the secrets).

It looks good to me, thank you, Loïc!

@ogrisel do you have an opinion on whether we should pin actions by commit hash in the new Pyodide workflow, in particular pypa/cibuildwheel and scientific-python/upload-nightly-action?

I am not too sure what is the worst that could happen if a compromised Pyodide wheel was uploaded.

I guess if scientific-python/upload-nightly-action was compromised they could steal our anaconda.org secrets and upload vanilla Python (i.e. not Pyodide only) wheels to anaconda.org and attack users/projects that use our development wheel.

While I'm not on the scikit-learn board, the general advice I will have from my NumFOCUS Security Committee member hat is that all actions should be pinned, even if it makes updating them troublesome. Dependabot or Renovate usually help with it, and current limitations will be overcome when github/roadmap#592 lands. zizmor should also handle it, and will bicker about it if you use the --pedantic option.

I would agree with your assessment – this is another point of attack. If there's something that came out of the PyTorch nightly build dependency compromise, it is that there are definitely a bunch of end users of the nightly wheels, outside of CI.

@lesteve
Copy link
Member

lesteve commented Mar 25, 2025

While I'm not on the scikit-learn board, the general advice I will have from my NumFOCUS Security Committee member hat is that all actions should be pinned, even if it makes updating them troublesome

I remember this was discussed in the context of SPEC 8 for example #29203 (comment) and the picture was not so clear-cut at the time.

SPEC 8 is saying "Pin GitHub Actions release workflows to their full release commit SHAs" which is why we are pinning the pypa/gh-action-pypi-publish by commit hash.

Now the question is whether the publishing of the Pyodide dev wheel to anaconda.org can be considered a "release workflow", maybe it is to be honest and we should at least pin pypa/cibuildwheel and scientific-python/upload-nightly-action.

@ogrisel
Copy link
Member

ogrisel commented Mar 25, 2025

@ogrisel do you have an opinion on whether we should pin actions by commit hash in the new Pyodide workflow, in particular pypa/cibuildwheel and scientific-python/upload-nightly-action?

I think we said in the past that we should pin by commit hash for package generating workflows only.

EDIT: "release critical" -> "package generating" workflows.

Copy link
Member

@ogrisel ogrisel left a comment

Choose a reason for hiding this comment

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

LGTM, but I would like a final pyodide build (except the upload step) working before giving the final approval to this PR.

Edit: indeed it's not possible to test the upload step as part of this PR.

Co-Authored-By: Olivier Grisel <olivier.grisel@ensta.org>
@agriyakhetarpal
Copy link
Contributor Author

agriyakhetarpal commented Mar 25, 2025

Thanks, @ogrisel for the review – I've addressed your comments in 4831d6d

Edit: I won't be able to get a working Anaconda.org upload of the wheels, though; I'm contributing through a fork. It should kick in once the change is pushed to main.

Edit two: the workslow is passing here: https://github.com/scikit-learn/scikit-learn/actions/runs/14068121558

build: ${{ steps.check_build_trigger.outputs.build }}
steps:
- name: Checkout scikit-learn
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Copy link
Member

@lesteve lesteve Mar 26, 2025

Choose a reason for hiding this comment

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

Sorry to go back on forth on this but I personally would rather not pin official GitHub actions i.e. actions/..., for example see some reasons in #29203 (comment).

In other words, I would only pin third-party actions in package generating workflows.

Copy link
Member

Choose a reason for hiding this comment

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

Ok to only pin third-party actions, we probably need to write down our policy somewhere then.

Sorry for the back and forth.

Copy link
Member

@ogrisel ogrisel left a comment

Choose a reason for hiding this comment

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

Besides #29791 (review), LGTM.

@lesteve
Copy link
Member

lesteve commented Mar 26, 2025

I have pushed the following changes:

  • use major version pinning for Github actions
  • revert cuda-ci cibuildwheel hash pin. I will open a separate PR about this. To be honest I am not too sure whether we need to pin it by commit hash. The created wheel is only used internally in the CI. You could as a user download download the github artifact and pip install the downloaded file but this is a bit far-fetched ...

@lesteve lesteve merged commit 06f9656 into scikit-learn:main Mar 26, 2025
36 checks passed
@lesteve
Copy link
Member

lesteve commented Mar 26, 2025

Let's merge this one, thanks @agriyakhetarpal!

@lesteve
Copy link
Member

lesteve commented Mar 26, 2025

I have triggered manually a Pyodide wheel build, let's see what happens: https://github.com/scikit-learn/scikit-learn/actions/runs/14079229690

@agriyakhetarpal agriyakhetarpal deleted the updates-for-emscripten-ci branch March 26, 2025 09:24
@agriyakhetarpal
Copy link
Contributor Author

It failed because the token is empty? I'm not sure why that happened.

@lesteve
Copy link
Member

lesteve commented Mar 26, 2025

Yeah me neither, I need to look into it closer ...

@lesteve
Copy link
Member

lesteve commented Mar 26, 2025

Maybe #31078 would fix it by using the right Github environment to be able to access the secret.

lucyleeow pushed a commit to lucyleeow/scikit-learn that referenced this pull request Apr 2, 2025
Co-authored-by: Loïc Estève <loic.esteve@ymail.com>
Co-authored-by: Olivier Grisel <olivier.grisel@ensta.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants