fix: Resolve ClassNotFoundException for .kts scripts by ensuring pack…#427
Open
jnorthrup wants to merge 2 commits intokscripting:masterfrom
Open
fix: Resolve ClassNotFoundException for .kts scripts by ensuring pack…#427jnorthrup wants to merge 2 commits intokscripting:masterfrom
jnorthrup wants to merge 2 commits intokscripting:masterfrom
Conversation
…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.
This commit updates the primary Kotlin version used by kscript for its own
build and for the Gradle scripts it generates (e.g., for --idea and --package)
from 1.7.21 to 2.1.21-embedded.
Changes include:
1. **`build.gradle.kts`:**
* The `kotlinVersion` property has been changed to "2.1.21-embedded".
* The `kotlin("jvm")` plugin version has been updated to "2.1.21-embedded".
* Dependencies on `org.jetbrains.kotlin:*` artifacts were already
parameterized to use `kotlinVersion`, so they will automatically adopt
the new version.
2. **`GradleTemplates.kt`:**
* Verified that generated Gradle scripts for `--idea` and `--package`
already use `KotlinVersion.CURRENT` to dynamically set their
Kotlin plugin and `kotlin-script-runtime` versions. This ensures
they will use the new "2.1.21-embedded" version.
* Updated `kotlin-stdlib` declarations within these templates to also
explicitly use the dynamic `kotlinVersion` for consistency and clarity.
No other hardcoded references to the old Kotlin version were found in kscript's
core operational code that required changes for this update. Runtime version
information displayed to you (e.g., via `kscript --version`) should
dynamically reflect this new version through `BuildConfig.KOTLIN_VERSION`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…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:
.ktsscripts without an explicitpackagedeclaration, kscript internally assigns a default package (e.g.,kscript.scriplet).Main_ScriptName.kt) is generated to provide a standardmainmethod entry point. This wrapper attempts to load the compiled.ktsscript's class using reflection, qualified with the assigned package name (e.g.,kscript.scriplet.ScriptName)..ktsfile content (without an explicit package statement) was written to a temporary file and compiled bykotlinc.kotlincwould place such a class in the default (unnamed) package.kscript.scriplet.ScriptName, but class actually beingScriptNamein the default package) caused theClassNotFoundException.Solution Implemented:
The
JarArtifactCreator.create()method has been modified. Before a.ktsscript's content is written to a temporary file for compilation, the logic now checks:.ktsfile.packagedeclaration.If these conditions are met, the determined package declaration (e.g.,
package kscript.scriplet;) is prepended to the script content. This ensures thatkotlinccompiles the.ktsscript'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
.ktsscripts, another area related to classloading and "missing linkage" has been identified, particularly for scripts packaged using the--packageoption:--packageoption uses Gradle tocreate a fat JAR. The current Gradle template uses
DuplicatesStrategy.INCLUDE. This can lead to runtime issues(e.g.,
NoSuchMethodError, services not loading) if dependencieshave conflicting class versions or
META-INF/servicesfiles, as onlyone version of a conflicting file will be included, potentially the
wrong one.
template should be updated to use a dedicated fat JAR plugin like
com.github.johnrengelman.shadow, which offers better strategies fordependency 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.