From 8df8557cc1601168774d6afdd6249a27908f516b Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Wed, 24 Dec 2025 09:42:39 +0100 Subject: [PATCH 1/3] Add GraalVM native image compilation support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Gradle native image plugin configuration with tracing agent - Add lsp-simulator.sh to exercise LSP operations for reflection tracing - Add build-native.sh for self-contained native builds - Add GitHub Actions workflow for multi-platform releases (linux-amd64, linux-arm64, darwin-arm64) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 Signed-off-by: Paolo Di Tommaso --- .github/workflows/build-native.yml | 105 +++++++++++ .gitignore | 3 +- build-native.sh | 240 +++++++++++++++++++++++++ build.gradle | 68 ++++++++ lsp-simulator.sh | 271 +++++++++++++++++++++++++++++ 5 files changed, 686 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-native.yml create mode 100755 build-native.sh create mode 100755 lsp-simulator.sh diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml new file mode 100644 index 00000000..16692d9b --- /dev/null +++ b/.github/workflows/build-native.yml @@ -0,0 +1,105 @@ +name: Build Native Image + +on: + push: + branches: + - '*' + - '!refs/tags/.*' + tags-ignore: + - '*' + pull_request: + types: [opened, reopened, synchronize] + branches: + - '*' + - '!refs/tags/.*' + tags-ignore: + - '*' + +env: + GRAALVM_VERSION: '21' + GRAALVM_DISTRIBUTION: 'graalvm' + JAVA_VERSION: '21' + +jobs: + build: + name: Build ${{ matrix.platform }} + runs-on: ${{ matrix.os }} + if: "!contains(github.event.head_commit.message, '[skip ci]')" + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + platform: linux-amd64 + - os: ubuntu-24.04-arm + platform: linux-arm64 + - os: macos-14 + platform: darwin-arm64 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup GraalVM + uses: graalvm/setup-graalvm@v1 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.GRAALVM_DISTRIBUTION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build native image + run: | + chmod +x ./gradlew ./lsp-simulator.sh ./build-native.sh + ./build-native.sh + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: nf-language-server-${{ matrix.platform }} + path: build/dist/nf-language-server-${{ matrix.platform }}.tar.gz + retention-days: 7 + + release: + name: Create Release + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Prepare release assets + run: | + mkdir -p release + find artifacts -name "*.tar.gz" -exec cp {} release/ \; + cd release + sha256sum *.tar.gz > checksums.txt + cat checksums.txt + + - name: Extract version from tag + id: version + run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: Release ${{ steps.version.outputs.VERSION }} + draft: false + prerelease: ${{ contains(github.ref, '-rc') || contains(github.ref, '-beta') || contains(github.ref, '-alpha') }} + generate_release_notes: true + files: | + release/*.tar.gz + release/checksums.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 192221b4..a5fdfe87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .gradle/ -build/ \ No newline at end of file +.idea/ +build/ diff --git a/build-native.sh b/build-native.sh new file mode 100755 index 00000000..6cbc4c54 --- /dev/null +++ b/build-native.sh @@ -0,0 +1,240 @@ +#!/bin/bash +set -euo pipefail + +# +# Build script for GraalVM native image of nf-language-server +# Supports: linux/amd64, linux/arm64, darwin/arm64 (Apple Silicon) +# +# Usage: +# ./build-native.sh [options] +# +# Options: +# --skip-test Skip testing the native binary +# --skip-package Skip packaging the binary +# --help, -h Show this help message +# + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Colors for output (disabled in CI) +if [[ -t 1 ]] && [[ -z "${CI:-}" ]]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + NC='\033[0m' +else + RED='' + GREEN='' + YELLOW='' + NC='' +fi + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check for required tools +check_requirements() { + log_info "Checking requirements..." + + # Check for Java + if ! command -v java &> /dev/null; then + log_error "Java is not installed or not in PATH" + exit 1 + fi + + JAVA_VERSION=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | cut -d'.' -f1) + if [[ "$JAVA_VERSION" -lt 17 ]]; then + log_error "Java 17+ is required, found version $JAVA_VERSION" + exit 1 + fi + + # Check for native-image + if ! command -v native-image &> /dev/null; then + # Try to find it in JAVA_HOME + if [[ -n "${JAVA_HOME:-}" ]] && [[ -x "$JAVA_HOME/bin/native-image" ]]; then + export PATH="$JAVA_HOME/bin:$PATH" + elif [[ -n "${GRAALVM_HOME:-}" ]] && [[ -x "$GRAALVM_HOME/bin/native-image" ]]; then + export PATH="$GRAALVM_HOME/bin:$PATH" + export JAVA_HOME="$GRAALVM_HOME" + else + log_error "native-image not found. Please install GraalVM with native-image support." + log_error "You can use SDKMAN: sdk install java 21.0.1-graal && sdk use java 21.0.1-graal" + exit 1 + fi + fi + + # Verify native-image works + if ! native-image --version &> /dev/null; then + log_error "native-image is not working correctly" + exit 1 + fi + + log_info "Using Java: $(java -version 2>&1 | head -1)" + log_info "Using native-image: $(native-image --version 2>&1 | head -1)" +} + +# Detect platform +detect_platform() { + OS="$(uname -s | tr '[:upper:]' '[:lower:]')" + ARCH="$(uname -m)" + + case "$ARCH" in + x86_64|amd64) + ARCH="amd64" + ;; + aarch64|arm64) + ARCH="arm64" + ;; + *) + log_error "Unsupported architecture: $ARCH" + exit 1 + ;; + esac + + case "$OS" in + linux) + PLATFORM="linux-$ARCH" + BINARY_EXT="" + ;; + darwin) + PLATFORM="darwin-$ARCH" + BINARY_EXT="" + ;; + mingw*|msys*|cygwin*) + PLATFORM="windows-$ARCH" + BINARY_EXT=".exe" + ;; + *) + log_error "Unsupported OS: $OS" + exit 1 + ;; + esac + + log_info "Detected platform: $PLATFORM" +} + +# Build native image (includes shadow jar, tracing agent, and native compilation) +build_native_image() { + log_info "Building native image (this includes JAR build and tracing agent)..." + ./gradlew nativeCompile --no-configuration-cache --no-daemon + + local BINARY_PATH="build/native/nativeCompile/nf-language-server${BINARY_EXT}" + if [[ -f "$BINARY_PATH" ]]; then + log_info "Native image built successfully: $BINARY_PATH" + ls -lh "$BINARY_PATH" + else + log_error "Native image build failed - binary not found" + exit 1 + fi +} + +# Test the native binary +test_native_binary() { + log_info "Testing native binary..." + + local BINARY_PATH="build/native/nativeCompile/nf-language-server${BINARY_EXT}" + local OUTPUT + + OUTPUT=$(./lsp-simulator.sh | "$BINARY_PATH" 2>&1 | head -20) + + if echo "$OUTPUT" | grep -q '"id":1,"result"'; then + log_info "Native binary test passed - LSP initialize succeeded" + else + log_error "Native binary test failed" + echo "$OUTPUT" + exit 1 + fi +} + +# Package the binary +package_binary() { + log_info "Packaging binary for $PLATFORM..." + + local BINARY_PATH="build/native/nativeCompile/nf-language-server${BINARY_EXT}" + local DIST_DIR="build/dist" + local ARCHIVE_NAME="nf-language-server-$PLATFORM" + + mkdir -p "$DIST_DIR" + + if [[ "$OS" == "darwin" ]] || [[ "$OS" == "linux" ]]; then + tar -czvf "$DIST_DIR/$ARCHIVE_NAME.tar.gz" -C "build/native/nativeCompile" "nf-language-server${BINARY_EXT}" + log_info "Created: $DIST_DIR/$ARCHIVE_NAME.tar.gz" + else + # Windows - create zip + (cd "build/native/nativeCompile" && zip -r "../../../$DIST_DIR/$ARCHIVE_NAME.zip" "nf-language-server${BINARY_EXT}") + log_info "Created: $DIST_DIR/$ARCHIVE_NAME.zip" + fi +} + +# Main build process +main() { + local SKIP_TEST=false + local SKIP_PACKAGE=false + + # Parse arguments + while [[ $# -gt 0 ]]; do + case $1 in + --skip-test) + SKIP_TEST=true + shift + ;; + --skip-package) + SKIP_PACKAGE=true + shift + ;; + --help|-h) + echo "Usage: $0 [options]" + echo "" + echo "Build GraalVM native image for nf-language-server" + echo "" + echo "Options:" + echo " --skip-test Skip testing the native binary" + echo " --skip-package Skip packaging the binary" + echo " --help, -h Show this help message" + echo "" + echo "Requirements:" + echo " - GraalVM 21+ with native-image" + echo " - Use SDKMAN: sdk install java 21.0.1-graal" + exit 0 + ;; + *) + log_error "Unknown option: $1" + exit 1 + ;; + esac + done + + log_info "Starting native image build..." + echo "" + + check_requirements + detect_platform + build_native_image + + if [[ "$SKIP_TEST" != "true" ]]; then + test_native_binary + fi + + if [[ "$SKIP_PACKAGE" != "true" ]]; then + package_binary + fi + + echo "" + log_info "Build completed successfully!" + log_info "Binary: build/native/nativeCompile/nf-language-server${BINARY_EXT}" + if [[ "$SKIP_PACKAGE" != "true" ]]; then + log_info "Package: build/dist/nf-language-server-$PLATFORM.tar.gz" + fi +} + +main "$@" diff --git a/build.gradle b/build.gradle index ef997800..6171ce5a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'com.gradleup.shadow' version '8.3.5' + id 'org.graalvm.buildtools.native' version '0.10.4' id 'application' id 'groovy' id 'java' @@ -93,6 +94,7 @@ processResources { jar { dependsOn buildSpec + duplicatesStrategy = DuplicatesStrategy.EXCLUDE from("$buildDir/generated") { include 'definitions.json' into 'spec' @@ -106,3 +108,69 @@ application { shadowJar { archiveVersion = '' } + +task generateNativeImageConfig(type: Exec) { + description = 'Generate native-image configuration using tracing agent' + group = 'native' + + dependsOn shadowJar + + def agentOutputDir = file("$buildDir/native-image-agent") + def simulateScript = file("$projectDir/lsp-simulator.sh") + + inputs.file(shadowJar.archiveFile) + inputs.file(simulateScript) + outputs.dir(agentOutputDir) + + doFirst { + agentOutputDir.mkdirs() + } + + commandLine 'bash', '-c', """ + ${simulateScript.absolutePath} | java \\ + -agentlib:native-image-agent=config-output-dir=${agentOutputDir.absolutePath} \\ + -cp ${shadowJar.archiveFile.get().asFile.absolutePath} \\ + nextflow.lsp.NextflowLanguageServer + """ +} + +tasks.named('nativeCompile') { + dependsOn generateNativeImageConfig +} + +graalvmNative { + agent { + defaultMode = 'standard' + builtinCallerFilter = true + builtinHeuristicFilter = true + enableExperimentalPredefinedClasses = false + enableExperimentalUnsafeAllocationTracing = false + trackReflectionMetadata = true + metadataCopy { + inputTaskNames.add('run') + outputDirectories.add("$buildDir/native-image-agent") + mergeWithExisting = true + } + } + binaries { + main { + imageName = 'nf-language-server' + mainClass = 'nextflow.lsp.NextflowLanguageServer' + buildArgs.addAll([ + '--no-fallback', + '--enable-url-protocols=http,https', + '-H:+ReportExceptionStackTraces', + '--report-unsupported-elements-at-runtime', + '--initialize-at-build-time=org.slf4j', + '--initialize-at-build-time=org.apache.groovy', + '--initialize-at-build-time=groovy', + '--initialize-at-build-time=org.codehaus.groovy', + '--initialize-at-build-time=groovyjarjarantlr4', + '--initialize-at-build-time=java.beans', + '--initialize-at-build-time=com.sun.beans', + "-H:ConfigurationFileDirectories=$buildDir/native-image-agent" + ]) + } + } + toolchainDetection = false +} diff --git a/lsp-simulator.sh b/lsp-simulator.sh new file mode 100755 index 00000000..fd227006 --- /dev/null +++ b/lsp-simulator.sh @@ -0,0 +1,271 @@ +#!/bin/bash +# +# lsp-simulator.sh - LSP Client Simulation Script +# +# PURPOSE: +# This script simulates an LSP (Language Server Protocol) client by sending +# a sequence of JSON-RPC messages to the language server via stdin. It is +# primarily used for: +# +# 1. GraalVM Native Image Tracing: When run with the native-image-agent, +# this script exercises all major LSP operations to capture reflection +# and resource usage for native compilation. +# +# 2. Testing: Validates that the language server responds correctly to +# standard LSP requests. +# +# USAGE: +# # Direct execution (for testing) +# ./lsp-simulator.sh | java -jar language-server.jar +# +# # With GraalVM tracing agent (for native-image config generation) +# ./lsp-simulator.sh | java \ +# -agentlib:native-image-agent=config-output-dir=build/native-image-agent \ +# -jar language-server.jar +# +# # Testing the native binary +# ./lsp-simulator.sh | ./build/native/nativeCompile/nf-language-server +# +# LSP OPERATIONS COVERED: +# This script exercises the following LSP methods to ensure comprehensive +# coverage for native image compilation: +# +# Lifecycle: +# - initialize : Establish connection and exchange capabilities +# - initialized : Signal client is ready +# - shutdown : Request graceful shutdown +# - exit : Terminate the server process +# +# Document Synchronization: +# - textDocument/didOpen : Open a document for editing +# - textDocument/didClose : Close a document +# +# Language Features: +# - textDocument/hover : Get hover information at position +# - textDocument/completion : Get completion suggestions +# - textDocument/definition : Go to definition +# - textDocument/references : Find all references +# - textDocument/documentSymbol : List symbols in document +# - textDocument/formatting : Format entire document +# - textDocument/semanticTokens/full: Get semantic highlighting tokens +# - textDocument/codeLens : Get code lens annotations +# - textDocument/documentLink : Get clickable links in document +# - textDocument/rename : Rename a symbol +# - textDocument/prepareCallHierarchy: Prepare call hierarchy +# +# Workspace Features: +# - workspace/symbol : Search for symbols across workspace +# +# TEST DOCUMENT: +# The script opens a sample Nextflow script containing: +# - A process definition (FOO) with input/output declarations +# - A workflow block that invokes the process +# This exercises parsing, symbol resolution, and most LSP features. +# +# NOTE: +# The sleep delays between messages ensure the server has time to process +# each request. This is especially important when running with the tracing +# agent to capture all reflection calls. +# + +set -euo pipefail + +# ============================================================================= +# LSP Message Helper +# ============================================================================= +# Sends an LSP message with proper Content-Length header. +# LSP uses a simple HTTP-like protocol with Content-Length header followed +# by the JSON-RPC payload. +# +# Format: +# Content-Length: \r\n +# \r\n +# +# +send_message() { + local content="$1" + local length=${#content} + printf "Content-Length: %d\r\n\r\n%s" "$length" "$content" +} + +# ============================================================================= +# STEP 1: Initialize Connection +# ============================================================================= +# The initialize request is sent as the first request from client to server. +# It provides client capabilities and workspace information. +# The server responds with its capabilities. +# +send_message '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":1234,"capabilities":{"textDocument":{"hover":{"contentFormat":["markdown","plaintext"]},"completion":{"completionItem":{"snippetSupport":true}},"definition":{},"references":{},"documentSymbol":{},"formatting":{},"semanticTokens":{"requests":{"full":true}}}},"rootUri":"file:///Users/pditommaso/Projects/language-server","workspaceFolders":[{"uri":"file:///Users/pditommaso/Projects/language-server","name":"language-server"}]}}' + +sleep 0.5 + +# ============================================================================= +# STEP 2: Initialized Notification +# ============================================================================= +# Sent from client to server after receiving the initialize response. +# Signals that the client is ready to receive requests/notifications. +# +send_message '{"jsonrpc":"2.0","method":"initialized","params":{}}' + +sleep 0.5 + +# ============================================================================= +# STEP 3: Open a Document +# ============================================================================= +# Notifies the server that a document was opened. The server will parse +# the document and may send diagnostics back. +# +# Test document: A simple Nextflow script with a process and workflow +# +send_message '{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf","languageId":"nextflow","version":1,"text":"#!/usr/bin/env nextflow\n\nprocess FOO {\n input:\n val x\n\n output:\n val y\n\n script:\n \"\"\"\n echo hello\n \"\"\"\n}\n\nworkflow {\n FOO(Channel.of(1,2,3))\n}\n"}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 4: Hover Request +# ============================================================================= +# Request hover information at a specific position (line 3, char 4 = "input") +# Returns documentation, type info, or other relevant details. +# +send_message '{"jsonrpc":"2.0","id":2,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"},"position":{"line":3,"character":4}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 5: Completion Request +# ============================================================================= +# Request code completion suggestions at a position (line 17, char 8) +# This is inside the workflow block where process calls are made. +# +send_message '{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"},"position":{"line":17,"character":8}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 6: Go to Definition +# ============================================================================= +# Request the definition location for a symbol at position (line 17, char 4) +# This points to the FOO process call in the workflow. +# +send_message '{"jsonrpc":"2.0","id":4,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"},"position":{"line":17,"character":4}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 7: Find References +# ============================================================================= +# Find all references to a symbol at position (line 2, char 8 = "FOO") +# Includes the declaration itself when includeDeclaration is true. +# +send_message '{"jsonrpc":"2.0","id":5,"method":"textDocument/references","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"},"position":{"line":2,"character":8},"context":{"includeDeclaration":true}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 8: Document Symbols +# ============================================================================= +# Request all symbols (processes, workflows, functions, variables) in the document. +# Used for outline view and breadcrumbs in IDEs. +# +send_message '{"jsonrpc":"2.0","id":6,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 9: Document Formatting +# ============================================================================= +# Request to format the entire document according to specified options. +# Returns a list of text edits to apply. +# +send_message '{"jsonrpc":"2.0","id":7,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"},"options":{"tabSize":4,"insertSpaces":true}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 10: Semantic Tokens +# ============================================================================= +# Request semantic tokens for syntax highlighting beyond basic tokenization. +# Provides information about token types (function, variable, type, etc.) +# and modifiers (declaration, definition, readonly, etc.) +# +send_message '{"jsonrpc":"2.0","id":8,"method":"textDocument/semanticTokens/full","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 11: Code Lens +# ============================================================================= +# Request code lens annotations - actionable contextual information +# displayed inline with the code (e.g., "Run | Debug" for processes). +# +send_message '{"jsonrpc":"2.0","id":9,"method":"textDocument/codeLens","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 12: Document Links +# ============================================================================= +# Request clickable links in the document (URLs, file references, etc.) +# Each link has a target URI and can be resolved on click. +# +send_message '{"jsonrpc":"2.0","id":10,"method":"textDocument/documentLink","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 13: Workspace Symbol Search +# ============================================================================= +# Search for symbols across the entire workspace by name. +# Useful for "Go to Symbol in Workspace" functionality. +# +send_message '{"jsonrpc":"2.0","id":11,"method":"workspace/symbol","params":{"query":"FOO"}}' + +sleep 0.5 + +# ============================================================================= +# STEP 14: Rename Symbol +# ============================================================================= +# Request to rename a symbol at position (line 2, char 8 = "FOO") +# Returns a workspace edit with all changes needed across files. +# +send_message '{"jsonrpc":"2.0","id":12,"method":"textDocument/rename","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"},"position":{"line":2,"character":8},"newName":"BAR"}}' + +sleep 0.5 + +# ============================================================================= +# STEP 15: Prepare Call Hierarchy +# ============================================================================= +# Prepare call hierarchy at position (line 2, char 8 = "FOO" process) +# Returns call hierarchy items that can be used for incoming/outgoing calls. +# +send_message '{"jsonrpc":"2.0","id":13,"method":"textDocument/prepareCallHierarchy","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"},"position":{"line":2,"character":8}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 16: Close Document +# ============================================================================= +# Notify server that the document is being closed. +# Server can release associated resources. +# +send_message '{"jsonrpc":"2.0","method":"textDocument/didClose","params":{"textDocument":{"uri":"file:///Users/pditommaso/Projects/language-server/test.nf"}}}' + +sleep 0.5 + +# ============================================================================= +# STEP 17: Shutdown Request +# ============================================================================= +# Request the server to shut down. Server should return success and +# prepare for exit but not terminate yet. +# +send_message '{"jsonrpc":"2.0","id":99,"method":"shutdown","params":null}' + +sleep 0.5 + +# ============================================================================= +# STEP 18: Exit Notification +# ============================================================================= +# Notify server to exit. This should be sent after shutdown response +# is received. Server terminates after receiving this. +# +send_message '{"jsonrpc":"2.0","method":"exit","params":null}' From 2b06d60669bbb665d236cd342d27f0701e32d606 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Wed, 24 Dec 2025 10:32:31 +0100 Subject: [PATCH 2/3] Update runners and fix platform naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use macos-latest-large for macos-amd64 (Intel) - Use macos-latest-xlarge for macos-arm64 (Apple Silicon) - Change platform prefix from darwin- to macos- - Add conf/native-image to configuration directories 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/build-native.yml | 6 ++++-- build-native.sh | 2 +- build.gradle | 2 +- conf/native-image/reflect-config.json | 12 ++++++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 conf/native-image/reflect-config.json diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index 16692d9b..0239af13 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -33,8 +33,10 @@ jobs: platform: linux-amd64 - os: ubuntu-24.04-arm platform: linux-arm64 - - os: macos-14 - platform: darwin-arm64 + - os: macos-latest-large + platform: macos-amd64 + - os: macos-latest-xlarge + platform: macos-arm64 steps: - name: Checkout repository diff --git a/build-native.sh b/build-native.sh index 6cbc4c54..2d53cf3f 100755 --- a/build-native.sh +++ b/build-native.sh @@ -107,7 +107,7 @@ detect_platform() { BINARY_EXT="" ;; darwin) - PLATFORM="darwin-$ARCH" + PLATFORM="macos-$ARCH" BINARY_EXT="" ;; mingw*|msys*|cygwin*) diff --git a/build.gradle b/build.gradle index 6171ce5a..265241d5 100644 --- a/build.gradle +++ b/build.gradle @@ -168,7 +168,7 @@ graalvmNative { '--initialize-at-build-time=groovyjarjarantlr4', '--initialize-at-build-time=java.beans', '--initialize-at-build-time=com.sun.beans', - "-H:ConfigurationFileDirectories=$buildDir/native-image-agent" + "-H:ConfigurationFileDirectories=$buildDir/native-image-agent,$projectDir/conf/native-image" ]) } } diff --git a/conf/native-image/reflect-config.json b/conf/native-image/reflect-config.json new file mode 100644 index 00000000..d9a1163a --- /dev/null +++ b/conf/native-image/reflect-config.json @@ -0,0 +1,12 @@ +[ + { + "name": "[Ljava.lang.reflect.AccessFlag;", + "allPublicMethods": true, + "allPublicFields": true + }, + { + "name": "java.lang.reflect.AccessFlag", + "allPublicMethods": true, + "allPublicFields": true + } +] From ee11c67c9acfcdfb92f35af181f2355067f16419 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Wed, 24 Dec 2025 11:10:01 +0100 Subject: [PATCH 3/3] Simplify native build and update configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename binary to nlsp - Use macos-intel and macos-silicon platform names - Remove packaging from build script (upload raw binary instead) - Simplify detect_platform function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/build-native.yml | 8 ++-- build-native.sh | 65 +++--------------------------- build.gradle | 2 +- 3 files changed, 10 insertions(+), 65 deletions(-) diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index 0239af13..906f24f5 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -34,9 +34,9 @@ jobs: - os: ubuntu-24.04-arm platform: linux-arm64 - os: macos-latest-large - platform: macos-amd64 + platform: macos-intel - os: macos-latest-xlarge - platform: macos-arm64 + platform: macos-silicon steps: - name: Checkout repository @@ -61,14 +61,14 @@ jobs: uses: actions/upload-artifact@v4 with: name: nf-language-server-${{ matrix.platform }} - path: build/dist/nf-language-server-${{ matrix.platform }}.tar.gz + path: build/native/nativeCompile/nlsp retention-days: 7 release: name: Create Release needs: build runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') + if: contains(github.event.head_commit.message, '[release]') permissions: contents: write diff --git a/build-native.sh b/build-native.sh index 2d53cf3f..9fcd51d2 100755 --- a/build-native.sh +++ b/build-native.sh @@ -3,14 +3,13 @@ set -euo pipefail # # Build script for GraalVM native image of nf-language-server -# Supports: linux/amd64, linux/arm64, darwin/arm64 (Apple Silicon) +# Supports: linux/amd64, linux/arm64, macos/amd64, macos/arm64 (Apple Silicon) # # Usage: # ./build-native.sh [options] # # Options: # --skip-test Skip testing the native binary -# --skip-package Skip packaging the binary # --help, -h Show this help message # @@ -86,32 +85,12 @@ check_requirements() { # Detect platform detect_platform() { OS="$(uname -s | tr '[:upper:]' '[:lower:]')" - ARCH="$(uname -m)" - - case "$ARCH" in - x86_64|amd64) - ARCH="amd64" - ;; - aarch64|arm64) - ARCH="arm64" - ;; - *) - log_error "Unsupported architecture: $ARCH" - exit 1 - ;; - esac case "$OS" in - linux) - PLATFORM="linux-$ARCH" - BINARY_EXT="" - ;; - darwin) - PLATFORM="macos-$ARCH" + linux|darwin) BINARY_EXT="" ;; mingw*|msys*|cygwin*) - PLATFORM="windows-$ARCH" BINARY_EXT=".exe" ;; *) @@ -119,8 +98,6 @@ detect_platform() { exit 1 ;; esac - - log_info "Detected platform: $PLATFORM" } # Build native image (includes shadow jar, tracing agent, and native compilation) @@ -128,7 +105,7 @@ build_native_image() { log_info "Building native image (this includes JAR build and tracing agent)..." ./gradlew nativeCompile --no-configuration-cache --no-daemon - local BINARY_PATH="build/native/nativeCompile/nf-language-server${BINARY_EXT}" + local BINARY_PATH="build/native/nativeCompile/nlsp${BINARY_EXT}" if [[ -f "$BINARY_PATH" ]]; then log_info "Native image built successfully: $BINARY_PATH" ls -lh "$BINARY_PATH" @@ -142,7 +119,7 @@ build_native_image() { test_native_binary() { log_info "Testing native binary..." - local BINARY_PATH="build/native/nativeCompile/nf-language-server${BINARY_EXT}" + local BINARY_PATH="build/native/nativeCompile/nlsp${BINARY_EXT}" local OUTPUT OUTPUT=$(./lsp-simulator.sh | "$BINARY_PATH" 2>&1 | head -20) @@ -156,30 +133,10 @@ test_native_binary() { fi } -# Package the binary -package_binary() { - log_info "Packaging binary for $PLATFORM..." - - local BINARY_PATH="build/native/nativeCompile/nf-language-server${BINARY_EXT}" - local DIST_DIR="build/dist" - local ARCHIVE_NAME="nf-language-server-$PLATFORM" - - mkdir -p "$DIST_DIR" - - if [[ "$OS" == "darwin" ]] || [[ "$OS" == "linux" ]]; then - tar -czvf "$DIST_DIR/$ARCHIVE_NAME.tar.gz" -C "build/native/nativeCompile" "nf-language-server${BINARY_EXT}" - log_info "Created: $DIST_DIR/$ARCHIVE_NAME.tar.gz" - else - # Windows - create zip - (cd "build/native/nativeCompile" && zip -r "../../../$DIST_DIR/$ARCHIVE_NAME.zip" "nf-language-server${BINARY_EXT}") - log_info "Created: $DIST_DIR/$ARCHIVE_NAME.zip" - fi -} # Main build process main() { local SKIP_TEST=false - local SKIP_PACKAGE=false # Parse arguments while [[ $# -gt 0 ]]; do @@ -188,10 +145,6 @@ main() { SKIP_TEST=true shift ;; - --skip-package) - SKIP_PACKAGE=true - shift - ;; --help|-h) echo "Usage: $0 [options]" echo "" @@ -199,7 +152,6 @@ main() { echo "" echo "Options:" echo " --skip-test Skip testing the native binary" - echo " --skip-package Skip packaging the binary" echo " --help, -h Show this help message" echo "" echo "Requirements:" @@ -225,16 +177,9 @@ main() { test_native_binary fi - if [[ "$SKIP_PACKAGE" != "true" ]]; then - package_binary - fi - echo "" log_info "Build completed successfully!" - log_info "Binary: build/native/nativeCompile/nf-language-server${BINARY_EXT}" - if [[ "$SKIP_PACKAGE" != "true" ]]; then - log_info "Package: build/dist/nf-language-server-$PLATFORM.tar.gz" - fi + log_info "Binary: build/native/nativeCompile/nlsp${BINARY_EXT}" } main "$@" diff --git a/build.gradle b/build.gradle index 265241d5..1310face 100644 --- a/build.gradle +++ b/build.gradle @@ -154,7 +154,7 @@ graalvmNative { } binaries { main { - imageName = 'nf-language-server' + imageName = 'nlsp' mainClass = 'nextflow.lsp.NextflowLanguageServer' buildArgs.addAll([ '--no-fallback',