11import { createHash } from 'node:crypto' ;
22import { existsSync , mkdirSync , readFileSync , rmSync , writeFileSync } from 'node:fs' ;
3- import { isAbsolute , join , resolve } from 'node:path' ;
3+ import { isAbsolute , join , relative , resolve } from 'node:path' ;
44import { CommandError , runCommand } from '../process/exec' ;
5+ import { assertJavaArguments } from '../process/java-arguments' ;
56import type { BuildPlan , CommandSpec , PreparedProject , RunConfig } from '../core/types' ;
67
78const projectFiles = [ 'settings.gradle' , 'settings.gradle.kts' , 'build.gradle' , 'build.gradle.kts' ] ;
@@ -52,23 +53,20 @@ function commandSpecification(config: RunConfig, workspace: string, discovery =
5253 throw new Error ( `找不到 Gradle settings 或 build 文件:${ config . cwd } ` ) ;
5354 }
5455 validateBuildArguments ( config . buildArgs ) ;
55- const target = discovery ? ':' : targetPath ( config . module ) ;
56+ if ( ! discovery ) targetPath ( config . module ) ;
5657 const paths = metadataPaths ( workspace ) ;
5758 const task = discovery ? `${ paths . task } _discover` : paths . task ;
5859 const build = discovery ? 'none' : config . build ;
5960 const args = [
60- '--no-daemon' , '--console=plain' , '--no-configuration-cache' , '-I' , paths . script ,
61+ '--no-daemon' , '--console=plain' , '--no-configuration-cache' , '-I' , relative ( config . cwd , paths . script ) ,
6162 ...config . buildArgs ,
62- `-DjavaRun.root=${ resolve ( config . cwd ) } ` ,
63- `-DjavaRun.target=${ target } ` ,
64- `-DjavaRun.output=${ discovery ? join ( resolve ( workspace ) , 'gradle-projects.json' ) : paths . output } ` ,
6563 `-DjavaRun.metadataTask=${ task } ` ,
6664 `-DjavaRun.build=${ build } ` ,
6765 `-DjavaRun.includeTests=${ discovery ? false : config . includeTests } ` ,
6866 `-DjavaRun.discover=${ discovery } ` ,
6967 ] ;
7068 if ( build === 'none' ) args . push ( '-Porg.gradle.java.installations.auto-download=false' ) ;
71- args . push ( `${ target === ':' ? ':' : ` ${ target } :` } ${ task } ` ) ;
69+ args . push ( `: ${ task } ` ) ;
7270 return { command : gradleCommand ( config ) , args, cwd : config . cwd , stage : discovery ? 'Gradle 项目选择' : 'Gradle 项目准备' } ;
7371}
7472
@@ -82,25 +80,30 @@ export function planGradle(config: RunConfig, workspace: string): BuildPlan {
8280 tool : 'gradle' ,
8381 commands : [ commandSpecification ( config , workspace ) ] ,
8482 notes : [
83+ `目标 Gradle 项目:${ targetPath ( config . module ) } ` ,
8584 config . build === 'auto'
8685 ? 'Gradle 任务图准备目标项目和运行依赖,不执行测试'
8786 : '不构建源码,要求目标项目和运行依赖已有可用产物' ,
8887 '主类、运行类路径和 Java 工具链将在执行 Gradle 后解析' ,
88+ '项目根、目标模块和输出路径由临时脚本读取 UTF-8 请求,不经 Java 原生命令行编码转换' ,
8989 ] ,
9090 } ;
9191}
9292
9393const initScript = String . raw `
9494import groovy.json.JsonOutput
95+ import groovy.json.JsonSlurper
9596import org.gradle.api.GradleException
9697import org.gradle.api.plugins.JavaApplication
9798import org.gradle.api.plugins.JavaPluginExtension
9899import org.gradle.api.tasks.SourceSetContainer
99100import org.gradle.jvm.toolchain.JavaToolchainService
100101
102+ def javaRunRequest = new JsonSlurper().parseText(new String('@JAVA_RUN_REQUEST@'.decodeBase64(), 'UTF-8'))
103+
101104gradle.projectsEvaluated {
102105 // init script 也会进入辅助构建,元数据任务只注册到请求的主构建
103- def requestedRoot = new File(System.getProperty('javaRun. root') ).canonicalFile
106+ def requestedRoot = new File(javaRunRequest. root).canonicalFile
104107 if (gradle.rootProject.projectDir.canonicalFile != requestedRoot) {
105108 return
106109 }
@@ -113,14 +116,14 @@ gradle.projectsEvaluated {
113116 }.sort { a, b -> a.path <=> b.path }.collect {
114117 [value: it.path, label: it.path + '(入口待解析)']
115118 }
116- def output = new File(System.getProperty('javaRun. output') )
119+ def output = new File(javaRunRequest. output)
117120 output.parentFile.mkdirs()
118121 output.setText(JsonOutput.toJson(candidates), 'UTF-8')
119122 }
120123 }
121124 return
122125 }
123- def requestedPath = System.getProperty('javaRun. target')
126+ def requestedPath = javaRunRequest. target
124127 def target = gradle.rootProject.findProject(requestedPath)
125128 if (target == null) {
126129 throw new GradleException("找不到 Gradle 项目:" + requestedPath + ",请用 --module 指定实际项目路径")
@@ -133,7 +136,7 @@ gradle.projectsEvaluated {
133136 def includeTests = Boolean.parseBoolean(System.getProperty('javaRun.includeTests'))
134137 def selected = sourceSets.getByName(includeTests ? 'test' : 'main')
135138 def included = includeTests ? [sourceSets.getByName('test'), sourceSets.getByName('main')] : [selected]
136- target.tasks.register(taskName) {
139+ def metadataTask = target.tasks.register(taskName) {
137140 if (System.getProperty('javaRun.build') == 'auto') {
138141 dependsOn selected.classesTaskName
139142 dependsOn selected.runtimeClasspath.buildDependencies
@@ -155,7 +158,7 @@ gradle.projectsEvaluated {
155158 }
156159 def mainClass = application == null ? null : application.mainClass.orNull
157160 def jvmArgs = application == null ? [] : application.applicationDefaultJvmArgs.collect { it.toString() }
158- def output = new File(System.getProperty('javaRun. output') )
161+ def output = new File(javaRunRequest. output)
159162 output.parentFile.mkdirs()
160163 output.setText(JsonOutput.toJson([
161164 directory: target.projectDir.absolutePath,
@@ -167,9 +170,35 @@ gradle.projectsEvaluated {
167170 ]), 'UTF-8')
168171 }
169172 }
173+ if (target != gradle.rootProject) {
174+ // 根任务固定为 ASCII,目标模块的名称只从 UTF-8 请求读取
175+ gradle.rootProject.tasks.register(taskName) {
176+ dependsOn metadataTask
177+ }
178+ }
170179}
171180` . trimStart ( ) ;
172181
182+ function initializationScript ( config : RunConfig , workspace : string , discovery = false ) : string {
183+ const request = {
184+ root : resolve ( config . cwd ) ,
185+ target : discovery ? ':' : targetPath ( config . module ) ,
186+ output : discovery ? join ( resolve ( workspace ) , 'gradle-projects.json' ) : metadataPaths ( workspace ) . output ,
187+ } ;
188+ const encoded = Buffer . from ( JSON . stringify ( request ) , 'utf8' ) . toString ( 'base64' ) ;
189+ return initScript . replace ( '@JAVA_RUN_REQUEST@' , encoded ) ;
190+ }
191+
192+ async function executeGradle ( spec : CommandSpec ) : Promise < void > {
193+ const java = process . env . JAVA_HOME
194+ ? join ( process . env . JAVA_HOME , 'bin' , process . platform === 'win32' ? 'java.exe' : 'java' )
195+ : 'java' ;
196+ await assertJavaArguments ( java , spec . args , spec . cwd ) ;
197+ console . error ( `java-run:${ spec . stage } ` ) ;
198+ const result = await runCommand ( spec , { capture : true } ) ;
199+ if ( result . exitCode !== 0 ) throw new CommandError ( spec , result ) ;
200+ }
201+
173202function readProject ( output : string ) : PreparedProject {
174203 let metadata : unknown ;
175204 try {
@@ -210,12 +239,10 @@ export async function prepareGradle(config: RunConfig, workspace: string): Promi
210239 const plan = planGradle ( config , workspace ) ;
211240 const paths = metadataPaths ( workspace ) ;
212241 mkdirSync ( resolve ( workspace ) , { recursive : true } ) ;
213- writeFileSync ( paths . script , initScript ) ;
242+ writeFileSync ( paths . script , initializationScript ( config , workspace ) ) ;
214243 rmSync ( paths . output , { force : true } ) ;
215244 const spec = plan . commands [ 0 ] ! ;
216- console . error ( `java-run:${ spec . stage } ` ) ;
217- const result = await runCommand ( spec , { capture : true } ) ;
218- if ( result . exitCode !== 0 ) throw new CommandError ( spec , result ) ;
245+ await executeGradle ( spec ) ;
219246 return readProject ( paths . output ) ;
220247}
221248
@@ -229,11 +256,9 @@ export async function discoverGradleProjects(config: RunConfig, workspace: strin
229256 const paths = metadataPaths ( workspace ) ;
230257 const output = join ( resolve ( workspace ) , 'gradle-projects.json' ) ;
231258 mkdirSync ( resolve ( workspace ) , { recursive : true } ) ;
232- writeFileSync ( paths . script , initScript ) ;
259+ writeFileSync ( paths . script , initializationScript ( config , workspace , true ) ) ;
233260 rmSync ( output , { force : true } ) ;
234- console . error ( `java-run:${ spec . stage } ` ) ;
235- const result = await runCommand ( spec , { capture : true } ) ;
236- if ( result . exitCode !== 0 ) throw new CommandError ( spec , result ) ;
261+ await executeGradle ( spec ) ;
237262 const candidates : unknown = JSON . parse ( readFileSync ( output , 'utf8' ) ) ;
238263 if ( ! Array . isArray ( candidates ) || candidates . some ( candidate =>
239264 ! candidate || typeof candidate . value !== 'string' || ! candidate . value . startsWith ( ':' ) || typeof candidate . label !== 'string' ) ) {
0 commit comments