@@ -2,10 +2,10 @@ package scala.cli.packaging
22
33import java .io .File
44
5- import scala .annotation .tailrec
65import scala .build .internal .{ManifestJar , Runner }
76import scala .build .internals .ConsoleUtils .ScalaCliConsole .warnPrefix
8- import scala .build .internals .EnvVar
7+ import scala .build .internals .MsvcEnvironment
8+ import scala .build .internals .MsvcEnvironment .*
99import scala .build .{Build , Logger , Positioned , coursierVersion }
1010import scala .cli .errors .GraalVMNativeImageError
1111import scala .cli .graal .{BytecodeProcessor , TempCache }
@@ -37,95 +37,6 @@ object NativeImage {
3737 nativeImage
3838 }
3939
40- private def vcVersions = Seq (" 2022" , " 2019" , " 2017" )
41- private def vcEditions = Seq (" Enterprise" , " Community" , " BuildTools" )
42- private lazy val vcVarsCandidates : Iterable [String ] =
43- EnvVar .Misc .vcVarsAll.valueOpt ++ {
44- for {
45- isX86 <- Seq (false , true )
46- version <- vcVersions
47- edition <- vcEditions
48- } yield {
49- val programFiles = if (isX86) " Program Files (x86)" else " Program Files"
50- """ C:\""" + programFiles + """ \Microsoft Visual Studio\""" + version + " \\ " + edition + """ \VC\Auxiliary\Build\vcvars64.bat"""
51- }
52- }
53-
54- private def vcvarsOpt : Option [os.Path ] =
55- vcVarsCandidates
56- .iterator
57- .map(os.Path (_, os.pwd))
58- .filter(os.exists(_))
59- .take(1 )
60- .toList
61- .headOption
62-
63- private def runFromVcvarsBat (
64- command : Seq [String ],
65- vcvars : os.Path ,
66- workingDir : os.Path ,
67- logger : Logger
68- ): Int = {
69- logger.debug(s " Using vcvars script $vcvars" )
70- val escapedCommand = command.map {
71- case s if s.contains(" " ) => " \" " + s + " \" "
72- case s => s
73- }
74- // chcp 437 sometimes needed, see https://github.com/oracle/graal/issues/2522
75- // but must save and restore existing code page afterwards
76- val script =
77- s """ @echo off
78- |rem Save current code page
79- |for /f "tokens=2 delims=:." %%A in ('chcp') do set "OLDCP=%%A"
80- |@echo on
81- |set OLDCP=%OLDCP: =%
82- |chcp 437
83- |@call " $vcvars"
84- |if %errorlevel% neq 0 exit /b %errorlevel%
85- |@call ${escapedCommand.mkString(" " )}
86- |rem Restore original code page
87- |chcp %OLDCP% >nul
88- | """ .stripMargin
89- logger.debug(s " Native image script: ' $script' " )
90- val scriptPath = workingDir / " run-native-image.bat"
91- logger.debug(s " Writing native image script at $scriptPath" )
92- os.write.over(scriptPath, script.getBytes, createFolders = true )
93-
94- val finalCommand = Seq (" cmd" , " /c" , scriptPath.toString)
95- logger.debug(s " Running $finalCommand" )
96- val res = os.proc(finalCommand).call(
97- cwd = os.pwd,
98- check = false ,
99- stdin = os.Inherit ,
100- stdout = os.Inherit
101- )
102- logger.debug(s " Command $finalCommand exited with exit code ${res.exitCode}" )
103-
104- res.exitCode
105- }
106-
107- private lazy val mountedDrives : String = {
108- val str = " HKEY_LOCAL_MACHINE/SYSTEM/MountedDevices" .replace('/' , '\\ ' )
109- val queryDrives = s " reg query $str"
110- val lines = os.proc(" cmd" , " /c" , queryDrives).call().out.lines()
111- val dosDevices = lines.filter { s =>
112- s.contains(" DosDevices" )
113- }.map { s =>
114- s.replaceAll(" .DosDevices." , " " ).replaceAll(" :.*" , " " )
115- }
116- dosDevices.mkString
117- }
118- private def availableDriveLetter (): Char = {
119-
120- @ tailrec
121- def helper (from : Char ): Char =
122- if (from > 'Z' ) sys.error(" Cannot find free drive letter" )
123- else if (mountedDrives.contains(from)) helper((from + 1 ).toChar)
124- else from
125-
126- helper('D' )
127- }
128-
12940 /** Alias currentHome to the root of a drive, so that its files can be accessed with shorter paths
13041 * (hopefully not going above the ~260 char limit of some Windows apps, such as cl.exe).
13142 *
@@ -139,39 +50,17 @@ object NativeImage {
13950 )(
14051 f : os.Path => T
14152 ): T =
142- // not sure about the 180 limit, we might need to lower it
14353 if (Properties .isWin && currentHome.toString.length >= 180 ) {
144- val driveLetter = availableDriveLetter()
145- // aliasing the parent dir, as it seems GraalVM native-image (as of 22.0.0)
146- // isn't fine with being put at the root of a drive - it tries to look for
147- // things like 'D:lib' (missing '\') at some point.
148- val from = currentHome / os.up
149- val drivePath = os.Path (s " $driveLetter: " + " \\ " )
150- val newHome = drivePath / currentHome.last
151- logger.debug(s " Aliasing $from to $drivePath" )
152- val setupCommand = s """ subst $driveLetter: " $from" """
153- val disableScript = s """ subst $driveLetter: /d """
154-
155- os.proc(" cmd" , " /c" , setupCommand).call(stdin = os.Inherit , stdout = os.Inherit )
156- try f(newHome)
157- finally {
158- val res = os.proc(" cmd" , " /c" , disableScript).call(
159- stdin = os.Inherit ,
160- stdout = os.Inherit ,
161- check = false
162- )
163- if (res.exitCode == 0 )
164- logger.debug(s " Unaliased $drivePath" )
165- else if (os.exists(drivePath)) {
166- // ignore errors?
167- logger.debug(s " Unaliasing attempt exited with exit code ${res.exitCode}" )
168- throw new os.SubprocessException (res)
54+ val (driveLetter, newHome) = getShortenedPath(currentHome, logger)
55+ val savedCodepage : String = getCodePage(logger)
56+ val result =
57+ try
58+ f(newHome)
59+ finally {
60+ unaliasDriveLetter(driveLetter)
61+ setCodePage(savedCodepage)
16962 }
170- else
171- logger.debug(
172- s " Failed to unalias $drivePath which seems not to exist anymore, ignoring it "
173- )
174- }
63+ result
17564 }
17665 else
17766 f(currentHome)
@@ -249,10 +138,16 @@ object NativeImage {
249138 }
250139 else (processedClassPath, Seq [os.Path ](), Seq [String ]())
251140
141+ def stripSuffixIgnoreCase (s : String , suffix : String ): String =
142+ if (s.toLowerCase.endsWith(suffix.toLowerCase))
143+ s.substring(0 , s.length - suffix.length)
144+ else
145+ s
146+
252147 try {
253148 val args = extraOptions ++ scala3extraOptions ++ Seq (
254149 s " -H:Path= ${dest / os.up}" ,
255- s " -H:Name= ${dest.last.stripSuffix( " .exe" )}" , // FIXME Case-insensitive strip suffix?
150+ s " -H:Name= ${stripSuffixIgnoreCase( dest.last, " .exe" )}" , // Case-insensitive strip suffix
256151 " -cp" ,
257152 classPath.map(_.toString).mkString(File .pathSeparator),
258153 mainClass
@@ -265,10 +160,11 @@ object NativeImage {
265160
266161 val exitCode =
267162 if Properties .isWin then
268- vcvarsOpt match {
269- case Some (vcvars) => runFromVcvarsBat(command, vcvars, nativeImageWorkDir, logger)
270- case None => Runner .run(command, logger).waitFor()
271- }
163+ MsvcEnvironment .msvcNativeImageProcess(
164+ command = command,
165+ workingDir = nativeImageWorkDir,
166+ logger = logger
167+ )
272168 else Runner .run(command, logger).waitFor()
273169 if exitCode == 0 then {
274170 val actualDest =
0 commit comments