Skip to content

Feat/python npm packaging #430

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

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

jnorthrup
Copy link

No description provided.

google-labs-jules bot and others added 8 commits June 5, 2025 03:26
…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.
fix: Resolve ClassNotFoundException for .kts scripts by ensuring pack…
This commit fixes a ClassNotFoundException that occurred when running KTS scripts. The issue was caused by your script class not being packaged into the scriplet.jar during compilation.

The fix involves combining your script code and the wrapper code into a single string before compilation. This ensures that kotlinc treats them as a single compilation unit and packages all necessary classes into the resulting JAR.

Specifically, the changes are:
- In `JarArtifactCreator.kt`:
  - For KTS scripts, your script content and the generated wrapper content are now concatenated into a single string.
  - This combined string is written to a single temporary .kt file, which is then passed to the Kotlin compiler.
- In `Templates.kt`:
  - `createWrapperForScript` now ensures the wrapper code has the correct package declaration, consistent with your script.

This approach should resolve the ClassNotFoundException and allow KTS scripts to be compiled and executed correctly.
Fix: Resolve ClassNotFoundException for KTS 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`.
feat: Update internal Kotlin version to 2.1.21-embedded
This change introduces the necessary files and build modifications to enable
you to package kscript for Python (pip) and JavaScript (npm) environments.

Key changes include:

-   Added Python wrapper script (`wrappers/kscript_py_wrapper.py`) to execute kscript.jar.
-   Added `setup.py` to define the Python package, making `kscript` available as a console script.
-   Added Node.js wrapper script (`wrappers/kscript_js_wrapper.js`) to execute kscript.jar.
-   Added `package.json` to define the NPM package, making `kscript` available via the bin field.
-   Modified `build.gradle.kts`:
    -   To copy `kscript.jar` into the `wrappers/` directory.
    -   To include `setup.py`, `package.json`, and the `wrappers/` directory (containing the JAR and wrapper scripts) in the main kscript distribution zip.
-   Added basic test scripts (`test/test_python_wrapper.py`, `test/test_js_wrapper.js`) to verify the functionality of the wrapper scripts with a sample kscript (`examples/test_wrapper.kts`).
-   Updated `README.adoc` with a new section detailing how you can build and install the Python and NPM packages from the distributed files.

This provides a foundation for you if you wish to integrate kscript into Python or Node.js workflows by building the packages themselves from the kscript release distribution.
I've upgraded the Gradle wrapper to use Gradle version 8.14.1.
This updates the `distributionUrl` in
`gradle/wrapper/gradle-wrapper.properties` and ensures associated
wrapper scripts are consistent.

Using a more recent Gradle version helps with performance,
compatibility with modern JDKs and Kotlin, and access to new
Gradle features.
@jnorthrup jnorthrup marked this pull request as draft June 5, 2025 23:44
jnorthrup and others added 7 commits June 5, 2025 19:44
Updated README.adoc to provide more current information and accurately
reflect the project's state in 2024.

Key changes include:

- Introduction: Replaced outdated "Good News about Kotlin 1.4 scripting"
  section with a more timeless description of kscript's value.
  Removed a very dated (2017) conference link.
- Installation:
    - Added a note clarifying that package manager versions (SDKMAN,
      Homebrew, Scoop) may not be the absolute latest, and recommending
      the "Build it yourself" section for cutting-edge use.
    - Updated the "Build it yourself" section to encourage its use for
      modern Kotlin/Java versions (e.g., Kotlin 2.2.0+, Java 21+),
      and to note JDK requirements.
- Script Configuration / Annotations:
    - Added a cautionary note regarding the age of the
      `kscript-annotations:1.5` artifact and potential compatibility
      considerations with modern Kotlin 2.x.
    - Clarified that kscript historically used v1.5 internally.
- Feature Descriptions:
    - Verified that no unmerged/non-existent features like Proguard
      were mentioned (none were, so no changes needed on this point).
- How to contribute?:
    - Removed outdated, specific YouTrack issue links from 2017-2019.
    - Replaced with general guidance to use the kscript GitHub issue
      tracker and the official JetBrains YouTrack for IDE/Kotlin issues.
- FAQ:
    - Updated the answer comparing Kotlin and Python scripting performance
      to be more nuanced and cover JVM startup/compilation overhead vs.
      peak performance.

These changes aim to improve clarity, set realistic expectations for you,
and ensure the documentation is more helpful and up-to-date.
This commit introduces the new command-line option
`--export-to-gradle-project <script> <output_dir>`, which allows you
to generate a complete, standalone Gradle project from an existing
kscript file.

This "last mile toolkit" feature facilitates graduating a kscript into
a more formal project structure, ready for further development, testing,
and packaging using standard Gradle workflows.

Key functionalities implemented in `ProjectGenerator.kt`:
- Parses the input kscript for dependencies, Maven repositories,
  package name, and entry point using kscript's internal resolvers.
- Creates the specified output directory.
- Determines project properties (group, name, version), intelligently
  deriving the group and main class from script annotations or defaults.
- Generates `settings.gradle.kts` with the project name.
- Generates a comprehensive `build.gradle.kts` including:
    - Kotlin JVM and Application plugins (using kscript's own Kotlin
      version, e.g., 2.2.0-RC2, and targeting Java 21).
    - Project group and version.
    - Maven Central and any custom repositories from the script.
    - Kotlin standard library and all dependencies from the script.
    - Application main class configuration.
    - Standard Kotlin compiler options and Java toolchain settings.
- Creates the standard Maven/Gradle directory structure:
    - `src/main/kotlin/[package_path]`
    - `src/main/resources`
    - `src/test/kotlin/[package_path]`
    - `src/test/resources`
- Transforms the original kscript content by:
    - Removing the shebang and kscript-specific file-level annotations.
    - Adding an appropriate package declaration.
    - Saves the result as a `.kt` file within `src/main/kotlin/[package_path]`.
- Generates a `.gitignore` file with common Kotlin/Gradle patterns.
- Copies and configures the Gradle Wrapper (`gradlew`, `gradlew.bat`,
  `gradle/wrapper/*`) from kscript's own project, ensuring the
  generated project uses a consistent and recent Gradle version (e.g., 8.14.1).

The command-line interface in `Kscript.kt` and option parsing in
`OptionsUtils.kt` have been updated to support this new feature.
This commit enhances the `--export-to-gradle-project` feature by allowing kscripts to define their own target Maven coordinates (groupId, artifactId, version) via a new `@file:ProjectCoordinates` annotation.

Key changes:

1.  **Annotation Parsing (`Script.kt`, `Parser.kt`, `model/ProjectCoordinates.kt`):**
    - I introduced a new data class `model.ProjectCoordinates` to hold `group`, `artifact`, and `version`.
    - The `Script` model now includes an optional `projectCoordinates` field.
    - I updated `LineParser.kt` and `Parser.kt` to recognize and parse `@file:ProjectCoordinates(group="...", artifact="...", version="...")` annotations from script files.
    - The parsed coordinates are stored in the `Script` object via `ResolutionContext` and `SectionResolver`.

2.  **Project Generation (`generator/ProjectGenerator.kt`):**
    - The `exportToGradleProject` function now retrieves any `ProjectCoordinates` defined in the script.
    - These script-defined coordinates are prioritized when setting:
        - `group` in `build.gradle.kts`.
        - `version` in `build.gradle.kts`.
        - `rootProject.name` in `settings.gradle.kts` (derived from the artifactId).
    - Fallback logic (using script package name, output directory name, or defaults) is retained if the annotation or specific attributes are missing.
    - The package path for source files (`src/main/kotlin/...`, `src/test/kotlin/...`) and the `package` declaration in the generated `.kt` file are now based on the `effectiveProjectGroup` (derived from the annotation or fallbacks).
    - Default `mainClassName` derivation also uses `effectiveProjectGroup`.

This makes the project generation feature more declarative, allowing the script itself to be the source of truth for its intended Maven identity when being "graduated" into a full Gradle project.
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.

1 participant