@@ -18,71 +18,41 @@ object Jvm extends CoursierSupport {
18
18
* Runs a JVM subprocess with the given configuration and returns a
19
19
* [[os.CommandResult ]] with it's aggregated output and error streams
20
20
*/
21
- def callSubprocess (
21
+ def call (
22
22
mainClass : String ,
23
- classPath : Agg [os.Path ],
24
- jvmArgs : Seq [String ] = Seq .empty,
25
- envArgs : Map [String , String ] = Map .empty,
26
- mainArgs : Seq [String ] = Seq .empty,
27
- workingDir : os.Path = null ,
28
- streamOut : Boolean = true ,
29
- check : Boolean = true
30
- )(implicit ctx : Ctx ): CommandResult = {
31
-
32
- val commandArgs =
33
- Vector (javaExe) ++
34
- jvmArgs ++
35
- Vector (" -cp" , classPath.iterator.mkString(java.io.File .pathSeparator), mainClass) ++
36
- mainArgs
37
-
38
- val workingDir1 = Option (workingDir).getOrElse(ctx.dest)
39
- os.makeDir.all(workingDir1)
40
-
41
- os.proc(commandArgs)
42
- .call(
43
- cwd = workingDir1,
44
- env = envArgs,
45
- check = check,
46
- stdout = if (streamOut) os.Inherit else os.Pipe
47
- )
48
- }
49
-
50
- /**
51
- * Runs a JVM subprocess with the given configuration and returns a
52
- * [[os.CommandResult ]] with it's aggregated output and error streams
53
- */
54
- def callSubprocess (
55
- mainClass : String ,
56
- classPath : Agg [os.Path ],
23
+ classPath : Iterable [os.Path ],
57
24
jvmArgs : Seq [String ],
58
- envArgs : Map [String , String ],
59
25
mainArgs : Seq [String ],
60
- workingDir : os.Path ,
61
- streamOut : Boolean
62
- )(implicit ctx : Ctx ): CommandResult = {
63
- callSubprocess(mainClass, classPath, jvmArgs, envArgs, mainArgs, workingDir, streamOut, true )
64
- }
26
+ env : Map [String , String ] = null ,
27
+ cwd : os.Path = null ,
28
+ stdin : ProcessInput = Pipe ,
29
+ stdout : ProcessOutput = Pipe ,
30
+ stderr : ProcessOutput = os.Inherit ,
31
+ mergeErrIntoOut : Boolean = false ,
32
+ timeout : Long = - 1 ,
33
+ check : Boolean = true ,
34
+ propagateEnv : Boolean = true ,
35
+ timeoutGracePeriod : Long = 100 ,
36
+ useCpPassingJar : Boolean = false
37
+ ): os.CommandResult = {
65
38
66
- /**
67
- * Resolves a tool to a path under the currently used JDK (if known).
68
- */
69
- def jdkTool (toolName : String ): String = {
70
- sys.props
71
- .get(" java.home" )
72
- .map(h =>
73
- if (isWin) new File (h, s " bin \\ ${toolName}.exe " )
74
- else new File (h, s " bin/ ${toolName}" )
75
- )
76
- .filter(f => f.exists())
77
- .fold(toolName)(_.getAbsolutePath())
39
+ val commandArgs = jvmCommandArgs(javaExe, mainClass, jvmArgs, classPath, mainArgs, useCpPassingJar)
78
40
41
+ os.call(
42
+ commandArgs,
43
+ env,
44
+ cwd,
45
+ stdin,
46
+ stdout,
47
+ stderr,
48
+ mergeErrIntoOut,
49
+ timeout,
50
+ check,
51
+ propagateEnv,
52
+ timeoutGracePeriod
53
+ )
79
54
}
80
55
81
- def javaExe : String = jdkTool(" java" )
82
-
83
- def defaultBackgroundOutputs (outputDir : os.Path ): Option [(ProcessOutput , ProcessOutput )] =
84
- Some ((outputDir / " stdout.log" , outputDir / " stderr.log" ))
85
-
86
56
/**
87
57
* Runs a JVM subprocess with the given configuration and streams
88
58
* it's stdout and stderr to the console.
@@ -99,125 +69,94 @@ object Jvm extends CoursierSupport {
99
69
* This might help with long classpaths on OS'es (like Windows)
100
70
* which only supports limited command-line length
101
71
*/
102
- def runSubprocess (
72
+ def spawn (
103
73
mainClass : String ,
104
- classPath : Agg [os.Path ],
105
- jvmArgs : Seq [String ] = Seq .empty,
106
- envArgs : Map [String , String ] = Map .empty,
107
- mainArgs : Seq [String ] = Seq .empty,
108
- workingDir : os.Path = null ,
109
- background : Boolean = false ,
110
- useCpPassingJar : Boolean = false ,
111
- runBackgroundLogToConsole : Boolean = false
112
- )(implicit ctx : Ctx ): Unit = {
113
- runSubprocessWithBackgroundOutputs(
114
- mainClass,
115
- classPath,
116
- jvmArgs,
117
- envArgs,
118
- mainArgs,
119
- workingDir,
120
- if (! background) None
121
- else if (runBackgroundLogToConsole) {
122
- val pwd0 = os.Path (java.nio.file.Paths .get(" ." ).toAbsolutePath)
123
- // Hack to forward the background subprocess output to the Mill server process
124
- // stdout/stderr files, so the output will get properly slurped up by the Mill server
125
- // and shown to any connected Mill client even if the current command has completed
126
- Some (
127
- (
128
- os.PathAppendRedirect (pwd0 / " .." / ServerFiles .stdout),
129
- os.PathAppendRedirect (pwd0 / " .." / ServerFiles .stderr)
130
- )
131
- )
132
- } else Jvm .defaultBackgroundOutputs(ctx.dest),
133
- useCpPassingJar
134
- )
74
+ classPath : Iterable [os.Path ],
75
+ jvmArgs : Seq [String ],
76
+ mainArgs : Seq [String ],
77
+ env : Map [String , String ] = null ,
78
+ cwd : os.Path = null ,
79
+ stdin : ProcessInput = Pipe ,
80
+ stdout : ProcessOutput = Pipe ,
81
+ stderr : ProcessOutput = os.Inherit ,
82
+ mergeErrIntoOut : Boolean = false ,
83
+ propagateEnv : Boolean = true ,
84
+ useCpPassingJar : Boolean = false
85
+ ): os.SubProcess = {
86
+
87
+ val commandArgs = jvmCommandArgs(javaExe, mainClass, jvmArgs, classPath, mainArgs, useCpPassingJar)
88
+ os.spawn(commandArgs, env, cwd, stdin, stdout, stderr, mergeErrIntoOut, propagateEnv)
135
89
}
136
90
137
- // bincompat shim
138
- def runSubprocess (
91
+ def spawnClassloader (
92
+ classPath : Iterable [os.Path ],
93
+ sharedPrefixes : Seq [String ],
94
+ isolated : Boolean = true
95
+ ): java.net.URLClassLoader = {
96
+ mill.api.ClassLoader .create(
97
+ classPath.iterator.map(_.toNIO.toUri.toURL).toVector,
98
+ if (isolated) null else getClass.getClassLoader,
99
+ sharedPrefixes = sharedPrefixes
100
+ )()
101
+ }
102
+
103
+ def callClassloader [T ](
104
+ classPath : Iterable [os.Path ],
105
+ sharedPrefixes : Seq [String ],
106
+ isolated : Boolean = true
107
+ )(f : ClassLoader => T ): T = {
108
+ val oldClassloader = Thread .currentThread().getContextClassLoader
109
+ val newClassloader = spawnClassloader(classPath, sharedPrefixes, isolated)
110
+ Thread .currentThread().setContextClassLoader(newClassloader)
111
+ try {
112
+ f(newClassloader)
113
+ } finally {
114
+ Thread .currentThread().setContextClassLoader(oldClassloader)
115
+ newClassloader.close()
116
+ }
117
+ }
118
+
119
+ private def jvmCommandArgs (
120
+ javaExe : String ,
139
121
mainClass : String ,
140
- classPath : Agg [os.Path ],
141
122
jvmArgs : Seq [String ],
142
- envArgs : Map [ String , String ],
123
+ classPath : Iterable [os. Path ],
143
124
mainArgs : Seq [String ],
144
- workingDir : os.Path ,
145
- background : Boolean ,
146
125
useCpPassingJar : Boolean
147
- )(implicit ctx : Ctx ): Unit =
148
- runSubprocess(
149
- mainClass,
150
- classPath,
151
- jvmArgs,
152
- envArgs,
153
- mainArgs,
154
- workingDir,
155
- background,
156
- useCpPassingJar
157
- )
158
-
159
- /**
160
- * Runs a JVM subprocess with the given configuration and streams
161
- * it's stdout and stderr to the console.
162
- * @param mainClass The main class to run
163
- * @param classPath The classpath
164
- * @param jvmArgs Arguments given to the forked JVM
165
- * @param envArgs Environment variables used when starting the forked JVM
166
- * @param workingDir The working directory to be used by the forked JVM
167
- * @param backgroundOutputs If the subprocess should run in the background, a Tuple of ProcessOutputs containing out and err respectively. Specify None for nonbackground processes.
168
- * @param useCpPassingJar When `false`, the `-cp` parameter is used to pass the classpath
169
- * to the forked JVM.
170
- * When `true`, a temporary empty JAR is created
171
- * which contains a `Class-Path` manifest entry containing the actual classpath.
172
- * This might help with long classpaths on OS'es (like Windows)
173
- * which only supports limited command-line length
174
- */
175
- def runSubprocessWithBackgroundOutputs (
176
- mainClass : String ,
177
- classPath : Agg [os.Path ],
178
- jvmArgs : Seq [String ] = Seq .empty,
179
- envArgs : Map [String , String ] = Map .empty,
180
- mainArgs : Seq [String ] = Seq .empty,
181
- workingDir : os.Path = null ,
182
- backgroundOutputs : Option [Tuple2 [ProcessOutput , ProcessOutput ]] = None ,
183
- useCpPassingJar : Boolean = false
184
- )(implicit ctx : Ctx ): Unit = {
185
-
186
- val cp =
187
- if (useCpPassingJar && ! classPath.iterator.isEmpty) {
126
+ ): Vector [String ] = {
127
+ val classPath2 =
128
+ if (useCpPassingJar && classPath.nonEmpty) {
188
129
val passingJar = os.temp(prefix = " run-" , suffix = " .jar" , deleteOnExit = false )
189
- ctx.log.debug(
190
- s " Creating classpath passing jar ' ${passingJar}' with Class-Path: ${classPath.iterator.map(
191
- _.toNIO.toUri().toURL().toExternalForm()
192
- ).mkString(" " )}"
193
- )
194
130
createClasspathPassingJar(passingJar, classPath)
195
131
Agg (passingJar)
196
- } else {
197
- classPath
198
- }
132
+ } else classPath
133
+
134
+ Vector (javaExe) ++
135
+ jvmArgs ++
136
+ Vector (" -cp" , classPath2.iterator.mkString(java.io.File .pathSeparator), mainClass) ++
137
+ mainArgs
138
+ }
139
+
140
+ /**
141
+ * Resolves a tool to a path under the currently used JDK (if known).
142
+ */
143
+ def jdkTool (toolName : String ): String = {
144
+ sys.props
145
+ .get(" java.home" )
146
+ .map(h =>
147
+ if (isWin) new File (h, s " bin \\ ${toolName}.exe " )
148
+ else new File (h, s " bin/ ${toolName}" )
149
+ )
150
+ .filter(f => f.exists())
151
+ .fold(toolName)(_.getAbsolutePath())
199
152
200
- val cpArgument = if (cp.nonEmpty) {
201
- Vector (" -cp" , cp.iterator.mkString(java.io.File .pathSeparator))
202
- } else Seq .empty
203
- val mainClassArgument = if (mainClass.nonEmpty) {
204
- Seq (mainClass)
205
- } else Seq .empty
206
- val args =
207
- Vector (javaExe) ++
208
- jvmArgs ++
209
- cpArgument ++
210
- mainClassArgument ++
211
- mainArgs
212
-
213
- ctx.log.debug(s " Run subprocess with args: ${args.map(a => s " ' ${a}' " ).mkString(" " )}" )
214
-
215
- if (backgroundOutputs.nonEmpty)
216
- spawnSubprocessWithBackgroundOutputs(args, envArgs, workingDir, backgroundOutputs)
217
- else
218
- runSubprocess(args, envArgs, workingDir)
219
153
}
220
154
155
+ def javaExe : String = jdkTool(" java" )
156
+
157
+ def defaultBackgroundOutputs (outputDir : os.Path ): Option [(ProcessOutput , ProcessOutput )] =
158
+ Some ((outputDir / " stdout.log" , outputDir / " stderr.log" ))
159
+
221
160
/**
222
161
* Runs a generic subprocess and waits for it to terminate. If process exited with non-zero code, exception
223
162
* will be thrown. If you want to manually handle exit code, check [[runSubprocessWithResult ]]
0 commit comments