diff --git a/.github/actions/windows-reg-import/action.yml b/.github/actions/windows-reg-import/action.yml new file mode 100755 index 0000000000..728a735beb --- /dev/null +++ b/.github/actions/windows-reg-import/action.yml @@ -0,0 +1,39 @@ +name: windows-reg-import +description: Import a .reg file and verify those registry values (best-effort) +inputs: + reg-file: + description: "Path to the .reg file" + required: true +runs: + using: "composite" + steps: + - name: Attempt to import custom registry (best-effort) + shell: pwsh + run: | + try { + $regFile = Join-Path $env:GITHUB_WORKSPACE "${{ inputs.reg-file }}" + if (-not (Test-Path $regFile)) { + Write-Warning "Registry file not found (skipping): $regFile" + } else { + Write-Host "Importing registry from $regFile (attempting, non-fatal)" + reg import $regFile 2>&1 | ForEach-Object { Write-Host $_ } + } + } catch { + Write-Warning "Registry import failed (ignored): $_" + } + + - name: Attempt to verify registry values (best-effort) + shell: pwsh + run: | + try { + $acp = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage' -Name ACP -ErrorAction Stop).ACP + Write-Host "ACP = $acp" + } catch { + Write-Warning "Failed to read ACP (ignored): $_" + } + try { + $eb = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Nls\MUILanguagePreferences' -Name EnableBetaUnicode -ErrorAction Stop).EnableBetaUnicode + Write-Host "EnableBetaUnicode = $eb" + } catch { + Write-Warning "Failed to read EnableBetaUnicode (ignored): $_" + } diff --git a/.github/ci/windows/custom.reg b/.github/ci/windows/custom.reg new file mode 100755 index 0000000000..cbc0213c5c Binary files /dev/null and b/.github/ci/windows/custom.reg differ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1fd97e03b..99dad0bb91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -746,6 +746,11 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Import custom registry and verify + uses: ./.github/actions/windows-reg-import + with: + reg-file: .github/ci/windows/custom.reg + continue-on-error: true - uses: VirtusLab/scala-cli-setup@v1 with: jvm: "temurin:17" @@ -778,6 +783,11 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Import custom registry and verify + uses: ./.github/actions/windows-reg-import + with: + reg-file: .github/ci/windows/custom.reg + continue-on-error: true - name: Set up Python uses: actions/setup-python@v6 with: @@ -819,6 +829,11 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Import custom registry and verify + uses: ./.github/actions/windows-reg-import + with: + reg-file: .github/ci/windows/custom.reg + continue-on-error: true - name: Set up Python uses: actions/setup-python@v6 with: @@ -860,6 +875,11 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Import custom registry and verify + uses: ./.github/actions/windows-reg-import + with: + reg-file: .github/ci/windows/custom.reg + continue-on-error: true - name: Set up Python uses: actions/setup-python@v6 with: @@ -901,6 +921,11 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Import custom registry and verify + uses: ./.github/actions/windows-reg-import + with: + reg-file: .github/ci/windows/custom.reg + continue-on-error: true - name: Set up Python uses: actions/setup-python@v6 with: @@ -942,6 +967,11 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Import custom registry and verify + uses: ./.github/actions/windows-reg-import + with: + reg-file: .github/ci/windows/custom.reg + continue-on-error: true - name: Set up Python uses: actions/setup-python@v6 with: @@ -1518,6 +1548,11 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Import custom registry and verify + uses: ./.github/actions/windows-reg-import + with: + reg-file: .github/ci/windows/custom.reg + continue-on-error: true - uses: VirtusLab/scala-cli-setup@v1 with: jvm: "temurin:17" @@ -1824,6 +1859,11 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Import custom registry and verify + uses: ./.github/actions/windows-reg-import + with: + reg-file: .github/ci/windows/custom.reg + continue-on-error: true - uses: VirtusLab/scala-cli-setup@v1 with: jvm: "temurin:17" diff --git a/build.mill.scala b/build.mill.scala index 0f6a0513de..5c2c888bbf 100644 --- a/build.mill.scala +++ b/build.mill.scala @@ -126,6 +126,10 @@ object integration extends CliIntegration { Deps.jgit, Deps.jsoup ) + override def forkEnv: T[Map[String, String]] = super.forkEnv() ++ Seq( + "JAVA_TOOL_OPTIONS" -> "-Dfile.encoding=UTF-8", + "BLOOP_JAVA_OPTS" -> "-Dfile.encoding=UTF-8 -Xmx512m" + ) } object docker extends CliIntegrationDocker { object test extends ScalaCliTests { diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala old mode 100644 new mode 100755 index 39b841120f..fee62cf62d --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -4,9 +4,9 @@ import com.eed3si9n.expecty.Expecty.expect import java.io.{ByteArrayOutputStream, File} import java.nio.charset.Charset +import java.nio.charset.StandardCharsets.UTF_8 import scala.cli.integration.util.DockerServer -import scala.io.Codec import scala.jdk.CollectionConverters.* import scala.util.Properties @@ -1057,31 +1057,130 @@ abstract class RunTestDefinitions } test("UTF-8") { - val message = "Hello from TestÅÄÖåäö" - val fileName = "TestÅÄÖåäö.scala" - val inputs = TestInputs( + def utf8tag = "ÅÄÖåäöΔЖ" + val classname = s"Q$utf8tag" + val basename = classname // if (Properties.isWin) s"Test_ascii" else classname + + val fileName = s"$basename.scala" + val message = s"Hello $classname" + val utfPropnames = Seq("file.encoding", "sun.jnu.encoding", "native.encoding") + val utfProps = utfPropnames.map(s => s"-D$s=UTF-8") + val utfOptions = utfProps ++ Seq("-Dtest.scala-cli.debug-charset-issue=true") + + def cliOptions = utfOptions.flatMap(opt => Seq("--java-opt", opt)) + + def code = + """ +object MAINCLASS { + def props(s: String): String = Option(sys.props(s)).getOrElse("") + val fileName = "FILENAME" + val utfPropnames = Seq("file.encoding", "sun.jnu.encoding", "native.encoding", "java.runtime.version") + utfPropnames.foreach { (str: String) => System.err.println(s"$str = ${props(str)}") } + if (sys.props("os.name").toLowerCase.contains("windows")) { + import scala.sys.process._ + System.err.println(s"chcp.com code-page: ${"chcp.com".!!.trim}") + try { + val codepage = "reg query 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage' /v ACP".!!.trim + System.err.println(s"registry code-page: ${codepage.replaceAll("[^0-9]", "")}") + } catch { + case _: Throwable => + } + } + import java.nio.charset.Charset + System.err.println(s"Charset.defaultCharset: ${Charset.defaultCharset}") + System.err.println(s"fileName Chars: ${fileName.map(c => f"U+$c%04x").mkString(" ")}") + def main(args: Array[String]): Unit = { + print("""" + message + """") // no newline needed here + } +} +""".trim + val scriptContents = + code.replaceAll("MAINCLASS", classname).replaceAll("FILENAME", fileName) + // System.err.printf("%s\n", scriptContents) + + val inputs = TestInputs( os.rel / fileName -> - s"""object TestÅÄÖåäö { - | def main(args: Array[String]): Unit = { - | println("$message") - | } - |} - |""".stripMargin + scriptContents ) + + val jvmDir = os.pwd.toString.replace('\\', '/').replaceAll("/out/.*", "") + "/jvm" + + // the build jvm respects system code page, temurin:17 does not + val testCli = if Properties.isWin && TestUtil.cli.contains("-jar") then + val i = TestUtil.cli.indexOf("-jar") + val (left, right) = TestUtil.cli.splitAt(i) + val arr: Array[String] = (left ++ utfOptions ++ right).toArray + arr(0) = s"$jvmDir/bin/java.exe" + arr.toSeq + else + TestUtil.cli ++ cliOptions + + if (false && Properties.isWin) { // TODO: not sure how enable this in debug or verbose mode + def props(s: String): String = Option(sys.props(s)).getOrElse("") + utfPropnames.foreach(s => System.err.println(s"$s = ${props(s)}")) + System.err.println(s"Charset.defaultCharset: ${Charset.defaultCharset}") + if (sys.props("os.name").toLowerCase.contains("windows")) { + import scala.sys.process.* + System.err.println(s"chcp.com code-page: ${"chcp.com".!!.trim}") + try { + val codepage = + "reg query 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage' /v ACP".!!.trim + System.err.println(s"registry code-page: ${codepage.replaceAll("[^0-9]", "")}") + } + catch { + case _: Throwable => + } + } + System.err.println(s"[DEBUG] fileName string: [$fileName]") + System.err.println(s"[DEBUG] fileName.length: ${fileName.length}") + System.err.println(s"[DEBUG] Chars: ${fileName.map(c => f"U+$c%04x").mkString(" ")}") + } + System.err.println(s""" + os.proc( + ${testCli.mkString(" ")}, + ${extraOptions.mkString(" ")}, + ${fileName.replace('\\', '/')} +)""".trim) + val runenv = if (Properties.isWin) + Map( + "JAVA_TOOL_OPTIONS" -> "-Dfile.encoding=UTF-8", + "JAVA_HOME" -> jvmDir + ) + else + Map( + "JAVA_TOOL_OPTIONS" -> "-Dfile.encoding=UTF-8" + ) + inputs.fromRoot { root => + System.err.println("================== pre os.proc.call") val res = os.proc( - TestUtil.cli, - "-Dtest.scala-cli.debug-charset-issue=true", - "run", + testCli, extraOptions, fileName ) - .call(cwd = root) - if (res.out.text(Codec.default).trim != message) { - pprint.err.log(res.out.text(Codec.default).trim) - pprint.err.log(message) + .call( + cwd = root, + check = false, + env = runenv + ) + System.err.println(s"===== post os.proc.call stdout:\n[${res.out}]") + + val stdoutbytes = res.out.bytes + val utf8str = new String(stdoutbytes, UTF_8).trim + + pprint.err.log(utf8str) + pprint.err.log(message) + + if (utf8str != message) { + val expectbytes = message.getBytes(UTF_8) + val expectMsg = expectbytes.map("%02x".format(_)).mkString(" ") + val stdoutMsg = stdoutbytes.map("%02x".format(_)).mkString(" ") + pprint.err.log("stdout bytes:" + stdoutMsg) + pprint.err.log("expect bytes:" + expectMsg) } - expect(res.out.text(Codec.default).trim == message) + expect(utf8str == message) + // bogus failure on Windows occurs only in mill test environment + expect(Properties.isWin || utf8str == message) } } @@ -2457,4 +2556,5 @@ abstract class RunTestDefinitions processes.foreach { case (p, _) => expect(p.exitCode() == 0) } } } + }