33import * as VSCode from 'vscode' ;
44import * as Path from 'path' ;
55import * as FS from 'fs' ;
6+ import * as Crypto from 'crypto' ;
67import PortFinder from 'portfinder' ;
78import * as Net from 'net' ;
89import * as CommonsCommands from './commands' ;
@@ -25,6 +26,7 @@ const p2c = P2C.createConverter(undefined, false, false);
2526PortFinder . basePort = 45556 ;
2627
2728const LOG_RESOLVE_VM_ARG_PREFIX = '-Xlog:jni+resolve=' ;
29+ const LOG_AOT_VM_ARG_PREFIX = '-Xlog:aot' ;
2830const DEBUG_ARG = '-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=y' ;
2931
3032export interface ActivatorOptions {
@@ -123,6 +125,62 @@ function findJdtEmbeddedJRE(): string | undefined{
123125 }
124126}
125127
128+ const JAVA_25_VERSION = 25 ;
129+
130+ function hashString ( value : string ) : string {
131+ return Crypto . createHash ( 'sha256' ) . update ( value ) . digest ( 'hex' ) . substring ( 0 , 12 ) ;
132+ }
133+
134+ function getAotCachePath ( extensionPath : string , jvm : JVM ) : string {
135+ const javaHomeHash = hashString ( jvm . getJavaHome ( ) ) ;
136+ return Path . join ( extensionPath , 'language-server' , `spring-boot-ls_${ javaHomeHash } .aot` ) ;
137+ }
138+
139+ function prepareCdsArgs ( options : ActivatorOptions , context : VSCode . ExtensionContext , jvm : JVM ) : CdsResult {
140+ if ( ! options . workspaceOptions . get ( "cds.enabled" ) ) {
141+ return { cdsArgs : [ ] } ;
142+ }
143+
144+ if ( jvm . getMajorVersion ( ) < JAVA_25_VERSION ) {
145+ VSCode . window . showInformationMessage (
146+ 'Spring Boot Language Server: CDS is enabled but requires Java 25 or later. ' +
147+ `Current Java version is ${ jvm . getMajorVersion ( ) } . Starting without CDS.`
148+ ) ;
149+ return { cdsArgs : [ ] } ;
150+ }
151+
152+ const aotCachePath = getAotCachePath ( context . extensionPath , jvm ) ;
153+ const cdsArgs : string [ ] = [ ] ;
154+
155+ if ( FS . existsSync ( aotCachePath ) ) {
156+ options . clientOptions . outputChannel . appendLine ( `CDS: Using existing AOT cache: ${ aotCachePath } ` ) ;
157+ cdsArgs . push ( `-XX:AOTCache=${ aotCachePath } ` ) ;
158+ } else {
159+ options . clientOptions . outputChannel . appendLine ( `CDS: No AOT cache found, will record cache on this run: ${ aotCachePath } ` ) ;
160+ cdsArgs . push ( `-XX:AOTCacheOutput=${ aotCachePath } ` ) ;
161+ }
162+
163+ cdsArgs . push ( `${ LOG_AOT_VM_ARG_PREFIX } =off` ) ;
164+
165+ return { cdsArgs } ;
166+ }
167+
168+ interface CdsResult {
169+ cdsArgs : string [ ] ;
170+ }
171+
172+ function addCdsArgs ( vmArgs : string [ ] , cdsResult ?: CdsResult ) : void {
173+ if ( ! cdsResult ?. cdsArgs . length ) {
174+ return ;
175+ }
176+ for ( const arg of cdsResult . cdsArgs ) {
177+ if ( arg . startsWith ( LOG_AOT_VM_ARG_PREFIX ) && hasVmArg ( LOG_AOT_VM_ARG_PREFIX , vmArgs ) ) {
178+ continue ;
179+ }
180+ vmArgs . push ( arg ) ;
181+ }
182+ }
183+
126184export function activate ( options : ActivatorOptions , context : VSCode . ExtensionContext ) : Thenable < LanguageClient > {
127185 if ( options . CONNECT_TO_LS ) {
128186 return VSCode . window . showInformationMessage ( "Start language server" )
@@ -155,30 +213,33 @@ export function activate(options: ActivatorOptions, context: VSCode.ExtensionCon
155213
156214 clientOptions . outputChannel . appendLine ( "isJavaEightOrHigher => true" ) ;
157215
216+ const cdsResult = prepareCdsArgs ( options , context , jvm ) ;
217+
158218 if ( process . env [ 'SPRING_LS_USE_SOCKET' ] ) {
159- return setupLanguageClient ( context , createServerOptionsForPortComm ( options , context , jvm ) , options ) ;
219+ return setupLanguageClient ( context , createServerOptionsForPortComm ( options , context , jvm , cdsResult ) , options ) ;
160220 } else {
161- return setupLanguageClient ( context , createServerOptions ( options , context , jvm ) , options ) ;
221+ return setupLanguageClient ( context , createServerOptions ( options , context , jvm , undefined , cdsResult ) , options ) ;
162222 }
163223 } ) ;
164224 }
165225}
166226
167- function createServerOptions ( options : ActivatorOptions , context : VSCode . ExtensionContext , jvm : JVM , port ? : number ) : Executable {
227+ function createServerOptions ( options : ActivatorOptions , context : VSCode . ExtensionContext , jvm : JVM , port ?: number , cdsResult ?: CdsResult ) : Executable {
168228 const executable : Executable = Object . create ( null ) ;
169229 const execOptions : ExecutableOptions = Object . create ( null ) ;
170230 execOptions . env = Object . assign ( process . env ) ;
171231 execOptions . cwd = context . extensionPath
172232 executable . options = execOptions ;
173233 executable . command = jvm . getJavaExecutable ( ) ;
174234 const vmArgs = prepareJvmArgs ( options , context , jvm , port ) ;
235+ addCdsArgs ( vmArgs , cdsResult ) ;
175236 addCpAndLauncherToJvmArgs ( vmArgs , options , context ) ;
176237 executable . args = vmArgs ;
177238 return executable ;
178239
179240}
180241
181- function createServerOptionsForPortComm ( options : ActivatorOptions , context : VSCode . ExtensionContext , jvm : JVM ) : ServerOptions {
242+ function createServerOptionsForPortComm ( options : ActivatorOptions , context : VSCode . ExtensionContext , jvm : JVM , cdsResult ?: CdsResult ) : ServerOptions {
182243 return ( ) =>
183244 new Promise ( ( resolve ) => {
184245 PortFinder . getPort ( ( err , port ) => {
@@ -195,6 +256,7 @@ function createServerOptionsForPortComm(options: ActivatorOptions, context: VSCo
195256 cwd : context . extensionPath
196257 } ;
197258 const args = prepareJvmArgs ( options , context , jvm , port ) ;
259+ addCdsArgs ( args , cdsResult ) ;
198260 if ( options . explodedLsJarData ) {
199261 const explodedLsJarData = options . explodedLsJarData ;
200262 const lsRoot = Path . resolve ( context . extensionPath , explodedLsJarData . lsLocation ) ;
0 commit comments