Skip to content

feat(ci,#17361): dotnet_preload_packages.py -- workaround #r nuget: en assemblies locales - #17828

Merged
myia-ai-01 merged 2 commits into
mainfrom
fix/17361-dotnet-preload-helper
Sep 25, 2026
Merged

myia-ai-01 merged 2 commits into
mainfrom
fix/17361-dotnet-preload-helper

Conversation

@jsboige

@jsboige jsboige commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Grain: DEEP/tooling — lane myia-po-2024:CoursIA-2 — prev: DEEP/notebook-python #17763

Ce que fait cette PR

Implémente le livrable 2 du RFC docs/reference/dotnet-restore-rfc-17361.md : le helper qui rend praticable la workaround du bug #r "nuget:".

Sur dotnet-interactive 1.0.617701 (pin cluster), le 2e #r "nuget:" d'une session kernel peut lever PackageRestoreResult..ctor ArgumentException — le bug est non déterministe (3 repros sur 3 le 2026-09-22, plus aucune sur les 2 re-tentatives du 2026-09-23, à cache chaud comme à cache froid). Quand il tombe, il tue la cellule sans contournement runtime : le seul chemin défensif est de ne plus appeler #r "nuget:" du tout et de référencer des assemblies locales.

// avant : #r "nuget: QuikGraph, 2.5.0"   ← 2e restore de la session, non déterministe
// après : #r "./_deps/QuikGraph.dll"     ← `#r` est résolu au parse-time : littéral obligatoire

scripts/ci/dotnet_preload_packages.py :

  1. résout le package dans le cache NuGet global (NUGET_PACKAGES, sinon ~/.nuget/packages) — hors ligne si le cache est déjà peuplé ;
  2. sinon le peuple par un dotnet restore de projet jetable utilisant PackageDownload et non PackageReference — c'est l'item NuGet prévu pour télécharger sans contrainte de compatibilité de framework, là où un PackageReference échouerait en NU1202 sur tout package ne ciblant pas le TFM du projet (rien n'est compilé ici) ;
  3. choisit le meilleur TFM disponible (net9.0 → netstandard2.0) ;
  4. copie les DLL à plat dans _deps/ et émet _deps/.NET-packages.json avec les lignes #r prêtes à coller.

Deux points que l'esquisse du RFC disait autrement

  • Copie à plat dans _deps/, et non .dotnet_packages/<pkg>/<ver>/ : #r est résolu au parse-time et exige un littéral relatif au notebook, dont la forme mesurée (c.790/c.803) est ./_deps/<Dll>.dll. Le RFC est corrigé dans le même commit.
  • Aucune résolution de dépendances transitives : un package qui en déclare (IKVM par exemple) doit les lister lui-même. Ce choix est délibéré — copier l'arbre entier noierait _deps/ et masquerait ce qui est réellement référencé.

Validation

Tests unitaires — 41/41 (python -m pytest scripts/tests/test_dotnet_preload_packages.py) :

41 passed in 0.27s

dotnet n'est jamais invoqué par la suite (le restorer est injecté) : aucun réseau, aucun SDK requis en CI. Couvre parse_spec, le tri SemVer, la résolution de cache, la sélection de TFM, les chemins de sortie, et les cinq formes d'échec de restore_via_dotnet.

Deux bugs réels attrapés par les tests pendant l'écriture (et corrigés) :

Bug Symptôme Correctif
Flag de prérelease inversé dans _version_key 1.0.0-rc1 triait au-dessus de 1.0.0, donc un Nom non épinglé pouvait résoudre vers une RC 0 if pre else 1
Diagnostic dotnet restore sur le mauvais flux -v quiet écrit ses erreurs sur stdout ; le message rendait (stderr vide) — inutilisable lire stdout+stderr

Exécutions réelles sur la machine (pas seulement des mocks) :

$ python scripts/ci/dotnet_preload_packages.py QuikGraph==2.5.0 CsvHelper==33.0.1
[OK] QuikGraph==2.5.0 -> 2.5.0 (1 assembly(ies))
[OK] CsvHelper==33.0.1 -> 33.0.1 (1 assembly(ies))

$ python scripts/ci/dotnet_preload_packages.py QuikGraph==2.5.0     # dest relatif
[OK] QuikGraph==2.5.0 -> 2.5.0 (1 assembly(ies))
     #r "./_deps/QuikGraph.dll"                                    # forme mesurée c.790

$ python scripts/ci/dotnet_preload_packages.py IKVM==8.15.0
[OK] IKVM==8.15.0 -> 8.15.0 (3 assembly(ies))
     #r "./_deps/IKVM.CoreLib.dll" / IKVM.Java.dll / IKVM.Runtime.dll

$ NUGET_PACKAGES=<vide> python scripts/ci/dotnet_preload_packages.py Serilog==3.1.1
[OK] Serilog==3.1.1 -> 3.1.1 (1 assembly(ies))                     # chemin `dotnet restore` sur cache VIDE

$ python scripts/ci/dotnet_preload_packages.py Paquet-Qui-N-Existe-Pas==1.0.0
[KO] ... : dotnet restore rc=1 : ... error NU1101: Package Paquet-Qui-N-Existe-Pas introuvable.
     Aucun package associé à cet ID n'existe dans la ou les sources suivantes : nuget.org
rc=1

Le dernier cas est la preuve du correctif de diagnostic : dotnet émet ses erreurs en français accentué, ce qui a aussi motivé le passage à encoding="utf-8", errors="replace" sur le subprocess.run — sans quoi un hôte cp1252 lève UnicodeDecodeError sur ce payload (classe #13140/#12811). Le hook check-subprocess-encoding passe (rc=0).

Périmètre

Fichier Nature
scripts/ci/dotnet_preload_packages.py nouveau — le helper
scripts/tests/test_dotnet_preload_packages.py nouveau — 41 cas
.gitignore _deps/ généralisé (seul scripts/notebook_tools/probes/_deps/ était couvert ; le helper écrit à côté du notebook)
docs/reference/dotnet-restore-rfc-17361.md le TODO: à implémenter et la mention « en attendant le helper » remplacés par l'usage réel

Pas de Closes : See #17361. Le livrable 3 du RFC n'est pas livré — il est réfuté par le second commit (voir plus bas) : il n'y a donc pas de conversion de masse à faire, et rien ne doit laisser croire que l'issue entière est résolue par un outil.

Réserve connue, dite plutôt que tue

scripts/tests en suite complète sur ce worktree : 6942 passed, 3 failed — les 3 échecs sont dans test_prune_merged_worktrees.py::TestEndToEnd (tests d'intégration qui créent de vrais worktrees et appellent gh). test_json_includes_required_keys, l'un des trois, repasse seul dans le même arbre (1 passed in 132.91s). Ces tests sont sensibles à l'état de la machine et à l'ordonnancement de la suite ; ce diff est purement additif (un module isolé, un fichier de test isolé), aucun de ces tests ne l'importe. Je le signale comme friction observée, pas comme régression imputée à cette PR.

Second commit : le livrable 3 du RFC est réfuté par la mesure

Le RFC prévoyait (livrable 3) de convertir tous les notebooks .net-csharp en #r local. Le pilot du 2026-09-25 montre que les deux formes ne sont pas interchangeables — donc ce livrable partait d'une prémisse fausse.

Notebook choisi parce que c'est le plus exposé du dépôt au bug : Search/Part2-CSP/CSP-1-Fundamentals-CSharp.ipynb fait 3 restores NuGet dans une seule cellule (cellule 3 : IKVM, IKVM.Image, IKVM.Image.runtime.win-x64) — exactement le déclencheur observé en c.760 (« 2ᵉ restore de la session »). Kernel .net-csharp, pin cluster 1.0.617701, po-2024.

Bras Cellule 3 Résultat Diagnostic
0 — original 3 × #r "nuget:" 19/19 OK, 0 erreur, 14,1 s —
A — converti 3 × #r "./_deps/…" (le helper de cette PR) 16/19, 3 erreurs (cellules 32, 34, 37) IKVM.Runtime.InternalException: Could not locate ikvm home path
C — mixte IKVM local + les 2 packages image en nuget: 15/19, 4 erreurs (cellule 3 et 32-37) IKVM.Image.targets(45,9): error MSB4036: Tâche "IkvmResolveNearestRuntimeIdentifier" introuvable

Le bras C est celui qui explique les deux autres : les #r "nuget:" ne sont pas de simples références, ce sont eux qui font restaurer à Microsoft.DotNet.Interactive.PackageManagement l'arbre IKVM complet. IKVM.MSBuild fournit la tâche MSBuild que IKVM.Image.targets invoque, et l'image any/any + win-x64 fournit le home que IKVM.Runtime cherche au premier type java.*. Scinder les #r casse ce graphe : le bras C tombe dès la cellule 3 sur la tâche manquante ; le bras A va plus loin et tombe au premier appel Java, faute de home.

Trois faits sortis de la mesure, tous versés dans le RFC :

  1. Le livrable 3 est réfuté pour la famille IKVM — précisément celle qui porte le plus de restores par cellule (Choco, Tweety, RDF.Net).
  2. IKVM.Image et IKVM.Image.runtime.win-x64 ne sont pas exprimables en #r local : leurs lib/<tfm>/ ne contiennent qu'un _._, la convention NuGet « TFM compatible, aucune assembly ». Le helper le dit correctement (aucune assembly dans …/lib) — ce n'est pas un défaut de l'outil, c'est la limite du remplacement.
  3. _deps/ étant gitignore, committer un notebook pédagogique converti échangerait un bug non déterministe contre une panne déterministe (« fichier introuvable ») pour tout clone.

Décision écrite dans le RFC : le helper reste un outil de réparation à la demande — à invoquer quand l'exception se produit, sur le notebook concerné — et pas une convention à généraliser. Le geste de masse est retiré des livrables.

Sous-produit : le bras 0 est une 4ᵉ non-reproduction du bug (3 restores dans une cellule, tous réussis), qui corrobore c.803 (cache chaud) et c.807 (cache froid) après les 3 repros de c.760.

Aucune exécution de notebook n'est committée : le pilot a tourné sur une copie (._probe17361.ipynb), supprimée depuis, et le notebook d'origine est resté byte-identique (git diff vide sur Search/Part2-CSP/).

Le 2e `#r "nuget:"` d'une session dotnet-interactive 1.0.617701 peut lever
`PackageRestoreResult..ctor ArgumentException` (mesure c.760, non
deterministe), ce qui tue la cellule sans contournement runtime. Le seul
chemin defensif est de ne plus appeler `#r "nuget:"` du tout, et de
referencer des assembly locales : `#r "./_deps/QuikGraph.dll"`.

`scripts/ci/dotnet_preload_packages.py` (livrable 2 du RFC
`docs/reference/dotnet-restore-rfc-17361.md`) resout le cache NuGet global
ou le peuple par un `dotnet restore` de projet jetable (`PackageDownload`,
sans contrainte de TFM), copie les DLL a plat dans `_deps/` et emet
`.NET-packages.json` avec les lignes `#r` pretes a coller.

- `scripts/tests/test_dotnet_preload_packages.py` : 41 cas, `dotnet` jamais
  invoque (restorer injecte).
- `.gitignore` : `_deps/` generalise (seul `scripts/notebook_tools/probes/`
  etait couvert) -- le helper ecrit a cote du notebook.
- RFC : le TODO d'esquisse et la mention « en attendant le helper » sont
  remplaces par l'usage reel.

See #17361
@github-actions

Copy link
Copy Markdown
Contributor

No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml).

Detector: python scripts/audit/detect_organ_duplication.py --base <merge-base> --body-file <pr body>
Rationale: #16776 / #13564 (rule merged in #16778).

@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-po-2024:CoursIA-2` voit ces signaux actifs sur les mergees du jour (UTC 2026-09-25) :

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

…esure

Pilot sur le notebook du depot le plus expose au bug (CSP-1-Fundamentals-CSharp,
3 restores NuGet dans une seule cellule), kernel .net-csharp, pin cluster
1.0.617701, po-2024. Trois bras :

  bras 0 -- 3 x #r "nuget:" (original)          : 19/19 OK, 14,1 s
  bras A -- 3 x #r local via le helper          : 16/19, "Could not locate
                                                   ikvm home path"
  bras C -- IKVM local + 2 packages image nuget : 15/19, MSB4036 "Tache
                                                   IkvmResolveNearestRuntimeIdentifier
                                                   introuvable"

Le bras C explique les deux autres : les #r "nuget:" ne sont pas de simples
references, ce sont eux qui font restaurer l'arbre IKVM complet -- IKVM.MSBuild
fournit la tache MSBuild que IKVM.Image.targets invoque, et l'image any/any +
win-x64 fournit le home que IKVM.Runtime cherche au premier type java.*.

Consequences ecrites dans le RFC : livrable 3 refute pour la famille IKVM ;
IKVM.Image / IKVM.Image.runtime.win-x64 non exprimables en #r local (lib/<tfm>/_._) ;
et _deps/ etant gitignore, convertir un notebook pedagogique echangerait un bug
non deterministe contre une panne deterministe pour tout clone.

Le helper reste un outil de reparation a la demande. Bras 0 = 4e non-reproduction
du bug (corrobore c.803/c.807).

See #17361
@github-actions

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #17828 (feat(ci,#17361): dotnet_preload_packages.py -- workaround #r nuget: en assemblies locales) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur main. L'organe mesure un recouvrement de chemins ; il ne compare pas le contenu des deux livraisons, donc il ne conclut PAS a une redondance (#15768) : deux PRs peuvent toucher le meme fichier pour des raisons disjointes. L'arbitrage reste a la lane ou au coordinateur.

@jsboige

jsboige commented Sep 25, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2023:CoursIA
pr: 17828
head: 654bc11
complete: true
body: read
comments-reviewed: 3
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: b0c06ec2a62694570c3707eb40fe32988bf5b860457deac70d0315379f812621
diff-files: 4
diff-additions: 789
diff-deletions: 8
checks: latest-wins-green
b0: clear
scope: pass
domain: not-applicable
verdict: READY
[/ADJOINT PREFLIGHT]

Premier dossier sur cette PR (jamais tamponnee). Verifications propres : b0 sonde en direct (check_unaddressed_nits rc=0, aucun nit non leve, aucun commentaire non evalue) ; pliage 43 jambes / 29 noms au head 654bc11, zero jambe non-verte apres deduplication latest-wins (14 jambes supersedees ecartees) ; mergeStateStatus CLEAN, MERGEABLE.
Domaine : PR d'outillage CI (script preload .NET + test + RFC docs + .gitignore) — aucun gate de domaine (lean/qc/training) applicable ; la jambe Scripts Tests est verte au pliage.
Tout commentaire posterieur perime ce tampon (re-stamp = commentaire neuf, jamais un PATCH).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants