#r "nuget: IKVM, 8.15.0"
#r "nuget: IKVM.Image, 8.15.0"- IKVM, 8.15.0
- IKVM.Image, 8.15.0
dotnet-interactiveNavigation : <- Tweety-1-Setup | Index serie
L’Epic #4667 (port C# / IKVM de la serie Tweety) a un verrou structurel (axe 2, non resolu en Phase 0.5) :
Le runtime IKVM s’initialise-t-il dans
dotnet-interactive?
Les précédentes tentatives (cf. notebook Sudoku-11-Choco-CSharp.ipynb avant #4711) butaient sur deux verrous :
Could not load file or assembly 'System.Text.Json, Version=8.0.0.5' - conflit d’assembly entre IKVM 8.15 et la version fournie par dotnet-interactive.Could not locate ikvm home path - IKVM 8.15 ne consulte pas la variable d’env IKVM_HOME ; il lit AppContext["IKVM.Home"].Le pattern NuGet + AppContext.SetData deploye par les notebooks C# existants (Tweety-2-Basic-Logics-Csharp, Tweety-3-Dung-Csharp, etc.) utilise un DLL pre-compile via un projet MSBuild <IkvmReference> distinct (dotnet-build/build-TweetyShade.csproj). Ce pattern repose donc sur un pipeline de build externe au notebook.
Question discriminante de Phase 1 : peut-on obtenir un runtime IKVM fonctionnel dans dotnet-interactive sans pre-compilation (juste #r "nuget: ..." + configuration AppContext) ?
Pour repondre a la question, on utilise un artefact minimal : un JAR Java 8 (compile avec javac --release 8, class major 52) contenant deux méthodes statiques triviales :
Ce JAR est converti en DLL .NET via ikvmc (MSBuild <IkvmReference>) une seule fois en avance, et le DLL resultant (probe-trivial.dll) est place a cote de ce notebook.
Ce qu’on cherche a valider :
dotnet-interactive (Question 1 de ai-01, axe 2).Ce qu’on ne cherche PAS a valider ici :
Tweety-X-Csharp.ipynb déjà operationnels).| Étape | Commande | Lieu |
|---|---|---|
| 1. Compilation Java 8 | javac --release 8 Probe.java |
/tmp/ikvm-probe-jar/ (sandbox) |
| 2. Creation JAR | jar cf probe-trivial.jar probe/Probe.class |
/tmp/ikvm-probe-jar/ |
| 3. Compilation .NET | dotnet build sur csproj avec <IkvmReference> |
/tmp/ikvm-probe-jar/csproj-build/ |
| 4. DLL place a cote du notebook | cp probe-trivial.dll _probes/ |
MyIA.AI.Notebooks/SymbolicAI/Tweety/_probes/ |
Aucun JAR Java n’est commite dans le depot (.gitignore ligne *.jar ou convention libs/ non respectee : on ne commit qu’un artefact .NET).
Cette cellule restaure les paquets NuGet IKVM et assemble le IKVM_HOME qui contient les binaires OpenJDK (any/any + le RID de la machine, par exemple win-x64 ou linux-x64) fusionnes avec les DLL .NET IKVM (IKVM.CoreLib, IKVM.Java, IKVM.Runtime, IKVM.ByteCode). Duree : ~30-60 s la première fois (cache NuGet vide), instantanee ensuite.
using System.IO;
// RID de la machine courante (win-x64, linux-x64, osx-arm64...) : IKVM.Image tire l'image native de chaque plateforme.
string ikvmVer = "8.15.0", ikvmRid = (OperatingSystem.IsWindows() ? "win" : OperatingSystem.IsMacOS() ? "osx" : "linux") + "-" + System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
string nugetRoot = Environment.GetEnvironmentVariable("NUGET_PACKAGES")
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
// Sources des binaires
string ikvmBaseAny = Path.Combine(nugetRoot, "ikvm.image", ikvmVer, "ikvm", "any", "any");
string ikvmArchDir = Path.Combine(nugetRoot, "ikvm.image.runtime." + ikvmRid, ikvmVer, "ikvm", "any", ikvmRid);
string ikvmRuntimeNet = Path.Combine(nugetRoot, "ikvm", ikvmVer, "runtimes", "win", "lib", "net8.0");
string ikvmByteCode = Path.Combine(nugetRoot, "ikvm.bytecode", "9.3.4", "lib", "net8.0");
// Destination : IKVM_HOME coherent (OpenJDK layout)
string ikvmHome = Path.Combine(Path.GetTempPath(), "ikvm-home-" + ikvmVer + "-" + ikvmRid);
void IkvmCopyMerge(string src, string dst)
{
foreach (var d in Directory.GetDirectories(src, "*", SearchOption.AllDirectories))
Directory.CreateDirectory(d.Replace(src, dst));
foreach (var f in Directory.GetFiles(src, "*", SearchOption.AllDirectories))
{
var t = f.Replace(src, dst);
Directory.CreateDirectory(Path.GetDirectoryName(t)!);
File.Copy(f, t, overwrite: true);
}
}
// 1. Fusion any/any + RID de la machine (binaires OpenJDK + tzdb.dat)
if (Directory.Exists(ikvmBaseAny) && Directory.Exists(ikvmArchDir))
{
Directory.CreateDirectory(ikvmHome);
IkvmCopyMerge(ikvmBaseAny, ikvmHome);
IkvmCopyMerge(ikvmArchDir, ikvmHome);
}
// 2. Copie des 4 DLL .NET IKVM dans ikvmHome/lib/ (le runtime les cherche la)
foreach (var dll in new[] { "IKVM.CoreLib.dll", "IKVM.Java.dll", "IKVM.Runtime.dll" })
{
var src = Path.Combine(ikvmRuntimeNet, dll);
var dst = Path.Combine(ikvmHome, "lib", dll);
if (File.Exists(src)) File.Copy(src, dst, overwrite: true);
}
// IKVM.ByteCode est une dependance transitive (v9.3.4) NON dans ikvm/8.15.0
var bcSrc = Path.Combine(ikvmByteCode, "IKVM.ByteCode.dll");
var bcDst = Path.Combine(ikvmHome, "lib", "IKVM.ByteCode.dll");
if (File.Exists(bcSrc)) File.Copy(bcSrc, bcDst, overwrite: true);
// 3. Declarer IKVM_HOME via AppContext
AppContext.SetData("IKVM.Home", ikvmHome);
// 4. Diagnostics
bool tzdbOk = File.Exists(Path.Combine(ikvmHome, "lib", "tzdb.dat"));
bool javDll = File.Exists(Path.Combine(ikvmHome, "lib", "IKVM.Java.dll"));
bool rtDll = File.Exists(Path.Combine(ikvmHome, "lib", "IKVM.Runtime.dll"));
bool bcDll = File.Exists(Path.Combine(ikvmHome, "lib", "IKVM.ByteCode.dll"));
bool ikvmProps = File.Exists(Path.Combine(ikvmHome, "bin", "ikvm.properties"));
Console.WriteLine($"IKVM_HOME = {ikvmHome}");
Console.WriteLine($" bin/ikvm.properties: {ikvmProps}");
Console.WriteLine($" lib/tzdb.dat: {tzdbOk}");
Console.WriteLine($" lib/IKVM.CoreLib.dll: {File.Exists(Path.Combine(ikvmHome, "lib", "IKVM.CoreLib.dll"))}");
Console.WriteLine($" lib/IKVM.Java.dll: {javDll}");
Console.WriteLine($" lib/IKVM.Runtime.dll: {rtDll}");
Console.WriteLine($" lib/IKVM.ByteCode.dll: {bcDll}")IKVM_HOME = /tmp/ikvm-home-8.15.0-linux-x64
bin/ikvm.properties: True
lib/tzdb.dat: True
lib/IKVM.CoreLib.dll: True
lib/IKVM.Java.dll: True
lib/IKVM.Runtime.dll: True
lib/IKVM.ByteCode.dll: True
Truc critique : IKVM.Java.dll chargee via NuGet ne suffit pas ; il faut amorcer le runtime en appelant une méthode Java (ici java.lang.System.getProperty("java.version")). Sans ce bootstrap, les classes chargees ulterieurement echouent avec TypeInitializationException: The type initializer for '<Module>' threw an exception (la JVM IKVM n’a pas demarre).
// Bootstrap : appel a java.lang.System pour amorcer la JVM IKVM
var sysType = Type.GetType("java.lang.System, IKVM.Java");
if (sysType == null) throw new InvalidOperationException("IKVM.Java non chargee");
var getProp = sysType.GetMethod("getProperty", new[] { typeof(string) });
var javaVer = (string)getProp!.Invoke(null, new object[] { "java.version" })!;
Console.WriteLine($"JVM IKVM amorcee : java.version = {javaVer}");JVM IKVM amorcee : java.version = 1.8.0_472-b08
On reference le DLL probe-trivial.dll produit hors-ligne par ikvmc. Ce DLL contient la classe probe.Probe recompilee depuis Probe.class (Java 8, class major 52).
Deux méthodes statiques : hello() retourne la version Java detectee par IKVM ; sqrt2() calcule Math.sqrt(2) en bytecode Java execute par la JVM IKVM. Si ces deux appels reussissent, Phase 1 axe 2 est VERIFIEE : le runtime IKVM est operationnel dans dotnet-interactive.
// Invocation directe via namespace importe (pattern idem Tweety-2-Basic-Logics-Csharp)
var helloResult = Probe.hello();
var sqrt2Result = Probe.sqrt2();
Console.WriteLine($"probe.Probe.hello() = {helloResult}");
Console.WriteLine($"probe.Probe.sqrt2() = {sqrt2Result}");
Console.WriteLine();
Console.WriteLine("=== PHASE 1 (axe 2) VALIDEE ===");
Console.WriteLine("Le runtime IKVM s'initialise dans dotnet-interactive et execute du bytecode Java 8.");
Console.WriteLine("Le pattern est reproductible : NuGet + IKVM_HOME + DLL pre-compile par ikvmc.");
Console.WriteLine("La voie notebook est VIABLE pour les JARs Java 8 (Tweety 1.12, etc.) - Cf Phase 0.5.");
Console.WriteLine("Voir issue #4667 pour le verdict complet.");probe.Probe.hello() = IKVM-OK via 1.8.0_472-b08
probe.Probe.sqrt2() = 1.4142135623730951
=== PHASE 1 (axe 2) VALIDEE ===
Le runtime IKVM s'initialise dans dotnet-interactive et execute du bytecode Java 8.
Le pattern est reproductible : NuGet + IKVM_HOME + DLL pre-compile par ikvmc.
La voie notebook est VIABLE pour les JARs Java 8 (Tweety 1.12, etc.) - Cf Phase 0.5.
Voir issue #4667 pour le verdict complet.
| Question Phase 1 axe 2 | Verdict |
|---|---|
Le runtime IKVM s’initialise-t-il dans dotnet-interactive ? |
OUI (Bootstrap java.lang.System.getProperty reussit, version Java reportee) |
| Un JAR Java 8 peut-il etre execute dans ce runtime ? | OUI (probe.Probe.hello() et sqrt2() repondent) |
| Le pattern est-il reproductible par un autre agent ? | OUI |
IKVM.ByteCode.dll v9.3.4 est une dépendance runtime non documentee dans le paquet ikvm 8.15.0. Sans elle, TypeInitializer echoue silencieusement avec Could not load file or assembly 'IKVM.ByteCode, Version=9.3.4.0'. Le paquet ikvm.bytecode se restaure automatiquement comme dépendance transitive de IKVM 8.15.0 mais il faut le copier a la main dans IKVM_HOME/lib/ (sinon le runtime ne le trouve pas via AppContext["IKVM.Home"] seul).
Le bootstrap explicite est obligatoire : appeler une méthode Java (même triviale comme getProperty) amorce la JVM IKVM. Sans bootstrap, les appels ulterieurs echouent en TypeInitializationException bien que la classe soit chargee.
La voie csproj + <IkvmReference> reste recommandee pour la production (Tweety-C# notebooks, Sudoku-11-Choco, etc.) parce qu’elle (a) integre la compilation au build .NET standard, (b) preserve les metadonnees cross-module (crucial pour les fat-jars Maven shades), (c) beneficie du cache NuGet pour les dependances transitives comme IKVM.ByteCode.
Le runtime nu ne suffit pas sans DSL : les 4+1 DLL .NET (IKVM.CoreLib/Java/Runtime + IKVM.ByteCode) doivent etre co-localisees avec l’assembly appele (le repertoire de sortie bin/Release/net8.0/ dans le pattern po-2025, ou IKVM_HOME/lib/ si on définit AppContext). Le test critique : si IKVM.Java.dll est introuvable, l’erreur survient au moment de l’Invoke - pas avant.
Tweety-X-Csharp sans pre-compiler un DLL separe - il suffit d’integrer le pattern <IkvmReference> dans un mini-csproj execute en pre-build, OU de reutiliser les DLLs déjà produits (org.tweetyproject.tweety-pl.dll, -dung.dll, -aspic.dll).Tweety-X-Csharp qui chargent directement un JAR minimal. Le pattern est explicite, documente, et reproductible.release=11). Phase 2 livrera les notebooks dedies Modal/Default/QBF/DL - la voie est ouverte.Reprenez les exercices des notebooks C# existants (Tweety-2-Basic-Logics-Csharp, etc.) et adaptez-les pour demarrer d’un JAR Java 8 fraichement compile :
Addition.java avec une classe Arith exposant static int add(int a, int b). Compilez avec javac --release 8, empaquetez, convertissez via ikvmc, et appelez depuis un notebook C# dans ce kernel.Probe.greet(String name) retournant "Bonjour " + name. Verifiez que les chaînes Unicode (avec accents) survivent au round-trip Java -> IKVM -> .NET.<IkvmReference> sur un JAR du dossier libs/ (par exemple commons-math-2.2.jar, Java 5 = IKVM-chargeable) et appelez org.apache.commons.math.util.MathUtils.factorial(5).