kscript icon indicating copy to clipboard operation
kscript copied to clipboard

fix: Resolve ClassNotFoundException for .kts scripts by ensuring pack…

Open jnorthrup opened this issue 8 months ago • 0 comments

…age alignment

This commit addresses a 'missing linkage' issue where kscript's wrapper could fail to load the main class compiled from a .kts script, resulting in a ClassNotFoundException.

Problem Analysis:

  1. For .kts scripts without an explicit package declaration, kscript internally assigns a default package (e.g., kscript.scriplet).
  2. A wrapper class (e.g., Main_ScriptName.kt) is generated to provide a standard main method entry point. This wrapper attempts to load the compiled .kts script's class using reflection, qualified with the assigned package name (e.g., kscript.scriplet.ScriptName).
  3. However, the original .kts file content (without an explicit package statement) was written to a temporary file and compiled by kotlinc. kotlinc would place such a class in the default (unnamed) package.
  4. This mismatch (wrapper expecting kscript.scriplet.ScriptName, but class actually being ScriptName in the default package) caused the ClassNotFoundException.

Solution Implemented:

The JarArtifactCreator.create() method has been modified. Before a .kts script's content is written to a temporary file for compilation, the logic now checks:

  • If it's a .kts file.
  • If kscript has determined a package name for it (either parsed or defaulted).
  • If the script content itself does not already start with a package declaration.

If these conditions are met, the determined package declaration (e.g., package kscript.scriplet;) is prepended to the script content. This ensures that kotlinc compiles the .kts script's class into the same package that the wrapper expects, resolving the ClassNotFoundException.

Further Considerations for Full Robustness (Future Work):

While this commit fixes a critical classloading issue for .kts scripts, another area related to classloading and "missing linkage" has been identified, particularly for scripts packaged using the --package option:

  • Fat JAR Classpath Conflicts: The --package option uses Gradle to create a fat JAR. The current Gradle template uses DuplicatesStrategy.INCLUDE. This can lead to runtime issues (e.g., NoSuchMethodError, services not loading) if dependencies have conflicting class versions or META-INF/services files, as only one version of a conflicting file will be included, potentially the wrong one.
  • Recommendation: For more robust packaged scripts, the Gradle template should be updated to use a dedicated fat JAR plugin like com.github.johnrengelman.shadow, which offers better strategies for dependency conflict resolution and resource merging.

This fix provides a significant improvement in the reliable execution of .kts files. Further work on the packaging mechanism can enhance robustness for distributed scripts.

jnorthrup avatar Jun 05 '25 03:06 jnorthrup