mirror of
https://github.com/apple/pkl.git
synced 2026-06-28 00:06:22 +02:00
Add support for Windows (#492)
This adds support for Windows. The in-language path separator is still `/`, to ensure Pkl programs are cross-platform. Log lines are written using CRLF endings on Windows. Modules that are combined with `--module-output-separator` uses LF endings to ensure consistent rendering across platforms. `jpkl` does not work on Windows as a direct executable. However, it can work with `java -jar jpkl`. Additional details: * Adjust git settings for Windows * Add native executable for pkl cli * Add jdk17 windows Gradle check in CI * Adjust CI test reports to be staged within Gradle rather than by shell script. * Fix: encode more characters that are not safe Windows paths * Skip running tests involving symbolic links on Windows (these require administrator privileges to run). * Introduce custom implementation of `IoUtils.relativize` * Allow Gradle to initialize ExecutableJar `Property` values * Add Gradle flag to enable remote JVM debugging Co-authored-by: Philip K.F. Hölzenspies <holzensp@gmail.com>
This commit is contained in:
@@ -12,7 +12,7 @@ facts {
|
||||
}
|
||||
|
||||
["versionInfo"] {
|
||||
current.versionInfo.contains("macOS") || current.versionInfo.contains("Linux")
|
||||
current.versionInfo.contains("macOS") || current.versionInfo.contains("Linux") || current.versionInfo.contains("Windows")
|
||||
}
|
||||
|
||||
["commitId"] {
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// In all OSes, the directory separator is forward slash.
|
||||
res = import(#"..\basic\baseModule.pkl"#)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
–– Pkl Error ––
|
||||
Cannot find module `file:///$snippetsDir/input/errors/..%5Cbasic%5CbaseModule.pkl`.
|
||||
|
||||
x | res = import(#"..\basic\baseModule.pkl"#)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at invalidImportBackslashSep#res (file:///$snippetsDir/input/errors/invalidImportBackslashSep.pkl)
|
||||
|
||||
To resolve modules in nested directories, use `/` as the directory separator.
|
||||
|
||||
xxx | text = renderer.renderDocument(value)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at pkl.base#Module.output.text (pkl:base)
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
–– Pkl Error ––
|
||||
Cannot resolve dependency because file `/$snippetsDir/input/projects/badProjectDeps1/PklProject.deps.json` is malformed.
|
||||
Cannot resolve dependency because file `file:///$snippetsDir/input/projects/badProjectDeps1/PklProject.deps.json` is malformed.
|
||||
Run `pkl project resolve` to re-create this file.
|
||||
|
||||
x | import "@bird/Bird.pkl"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
–– Pkl Error ––
|
||||
Cannot resolve dependency because file `/$snippetsDir/input/projects/badProjectDeps2/PklProject.deps.json` is malformed.
|
||||
Cannot resolve dependency because file `file:///$snippetsDir/input/projects/badProjectDeps2/PklProject.deps.json` is malformed.
|
||||
Run `pkl project resolve` to re-create this file.
|
||||
|
||||
x | import "@bird/Bird.pkl"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
–– Pkl Error ––
|
||||
Cannot resolve dependency because file `PklProject.deps.json` is missing in project directory `/$snippetsDir/input/projects/missingProjectDeps`.
|
||||
Cannot resolve dependency because file `PklProject.deps.json` is missing in project directory `file:///$snippetsDir/input/projects/missingProjectDeps/`.
|
||||
|
||||
x | import "@birds/Bird.pkl"
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -74,11 +74,12 @@ class EvaluatorTest {
|
||||
|
||||
@Test
|
||||
fun `evaluate non-existing file`() {
|
||||
val file = File("/non/existing")
|
||||
val e = assertThrows<PklException> {
|
||||
evaluator.evaluate(file(File("/non/existing")))
|
||||
evaluator.evaluate(file(file))
|
||||
}
|
||||
assertThat(e)
|
||||
.hasMessageContaining("Cannot find module `file:///non/existing`.")
|
||||
.hasMessageContaining("Cannot find module `${file.toPath().toUri()}`.")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -92,13 +93,14 @@ class EvaluatorTest {
|
||||
|
||||
@Test
|
||||
fun `evaluate non-existing path`() {
|
||||
val path = "/non/existing".toPath()
|
||||
val e = assertThrows<PklException> {
|
||||
evaluator.evaluate(path("/non/existing".toPath()))
|
||||
evaluator.evaluate(path(path))
|
||||
}
|
||||
assertThat(e)
|
||||
.hasMessageContaining("Cannot find module `file:///non/existing`.")
|
||||
.hasMessageContaining("Cannot find module `${path.toUri()}`.")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `evaluate zip file system path`(@TempDir tempDir: Path) {
|
||||
val zipFile = createModulesZip(tempDir)
|
||||
|
||||
@@ -13,3 +13,6 @@ class LinuxLanguageSnippetTests
|
||||
|
||||
@Testable
|
||||
class AlpineLanguageSnippetTests
|
||||
|
||||
@Testable
|
||||
class WindowsLanguageSnippetTests
|
||||
|
||||
@@ -51,7 +51,7 @@ abstract class AbstractLanguageSnippetTestsEngine : InputOutputTestEngine() {
|
||||
else parent?.getProjectDir()
|
||||
|
||||
override fun expectedOutputFileFor(inputFile: Path): Path {
|
||||
val relativePath = inputDir.relativize(inputFile).toString()
|
||||
val relativePath = IoUtils.relativize(inputFile, inputDir).toString()
|
||||
val stdoutPath =
|
||||
if (relativePath.matches(hiddenExtensionRegex)) relativePath.dropLast(4)
|
||||
else relativePath.dropLast(3) + "pcf"
|
||||
@@ -62,12 +62,12 @@ abstract class AbstractLanguageSnippetTestsEngine : InputOutputTestEngine() {
|
||||
// disable SHA verification for packages
|
||||
IoUtils.setTestMode()
|
||||
}
|
||||
|
||||
|
||||
override fun afterAll() {
|
||||
packageServer.close()
|
||||
}
|
||||
|
||||
protected fun String.stripFilePaths() = replace(snippetsDir.toString(), "/\$snippetsDir")
|
||||
|
||||
protected fun String.stripFilePaths() = replace(snippetsDir.toUri().toString(), "file:///\$snippetsDir/")
|
||||
|
||||
protected fun String.stripLineNumbers() = replace(lineNumberRegex) { result ->
|
||||
// replace line number with equivalent number of 'x' characters to keep formatting intact
|
||||
@@ -82,6 +82,11 @@ abstract class AbstractLanguageSnippetTestsEngine : InputOutputTestEngine() {
|
||||
|
||||
protected fun String.stripStdlibLocationSha(): String =
|
||||
replace("https://github.com/apple/pkl/blob/${Release.current().commitId()}/stdlib/", "https://github.com/apple/pkl/blob/\$commitId/stdlib/")
|
||||
|
||||
protected fun String.withUnixLineEndings(): String {
|
||||
return if (System.lineSeparator() == "\r\n") replace("\r\n", "\n")
|
||||
else this
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageSnippetTestsEngine : AbstractLanguageSnippetTestsEngine() {
|
||||
@@ -143,7 +148,7 @@ class LanguageSnippetTestsEngine : AbstractLanguageSnippetTestsEngine() {
|
||||
.stripVersionCheckErrorMessage()
|
||||
}
|
||||
|
||||
val stderr = logWriter.toString()
|
||||
val stderr = logWriter.toString().withUnixLineEndings()
|
||||
|
||||
return (success && stderr.isBlank()) to (output + stderr).stripFilePaths().stripWebsite().stripStdlibLocationSha()
|
||||
}
|
||||
@@ -216,7 +221,7 @@ abstract class AbstractNativeLanguageSnippetTestsEngine : AbstractLanguageSnippe
|
||||
val process = builder.start()
|
||||
return try {
|
||||
val (out, err) = listOf(process.inputStream, process.errorStream)
|
||||
.map { it.reader().readText() }
|
||||
.map { it.reader().readText().withUnixLineEndings() }
|
||||
val success = process.waitFor() == 0 && err.isBlank()
|
||||
success to (out + err)
|
||||
.stripFilePaths()
|
||||
@@ -254,3 +259,8 @@ class AlpineLanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngin
|
||||
override val pklExecutablePath: Path = rootProjectDir.resolve("pkl-cli/build/executable/pkl-alpine-linux-amd64")
|
||||
override val testClass: KClass<*> = AlpineLanguageSnippetTests::class
|
||||
}
|
||||
|
||||
class WindowsLanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() {
|
||||
override val pklExecutablePath: Path = rootProjectDir.resolve("pkl-cli/build/executable/pkl-windows-amd64.exe")
|
||||
override val testClass: KClass<*> = WindowsLanguageSnippetTests::class
|
||||
}
|
||||
|
||||
@@ -181,11 +181,11 @@ class SecurityManagersTest {
|
||||
rootDir
|
||||
)
|
||||
|
||||
manager.checkResolveModule(URI("file:///foo/bar/baz.pkl"))
|
||||
manager.checkReadResource(URI("file:///foo/bar/baz.pkl"))
|
||||
manager.checkResolveModule(Path.of("/foo/bar/baz.pkl").toUri())
|
||||
manager.checkReadResource(Path.of("/foo/bar/baz.pkl").toUri())
|
||||
|
||||
manager.checkResolveModule(URI("file:///foo/bar/qux/../baz.pkl"))
|
||||
manager.checkReadResource(URI("file:///foo/bar/qux/../baz.pkl"))
|
||||
manager.checkResolveModule(Path.of("/foo/bar/qux/../baz.pkl").toUri())
|
||||
manager.checkReadResource(Path.of("/foo/bar/qux/../baz.pkl").toUri())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -233,17 +233,17 @@ class SecurityManagersTest {
|
||||
)
|
||||
|
||||
assertThrows<SecurityManagerException> {
|
||||
manager.checkResolveModule(URI("file:///foo/baz.pkl"))
|
||||
manager.checkResolveModule(Path.of("/foo/baz.pkl").toUri())
|
||||
}
|
||||
assertThrows<SecurityManagerException> {
|
||||
manager.checkReadResource(URI("file:///foo/baz.pkl"))
|
||||
manager.checkReadResource(Path.of("/foo/baz.pkl").toUri())
|
||||
}
|
||||
|
||||
assertThrows<SecurityManagerException> {
|
||||
manager.checkResolveModule(URI("file:///foo/bar/../baz.pkl"))
|
||||
manager.checkResolveModule(Path.of("/foo/bar/../baz.pkl").toUri())
|
||||
}
|
||||
assertThrows<SecurityManagerException> {
|
||||
manager.checkReadResource(URI("file:///foo/bar/../baz.pkl"))
|
||||
manager.checkReadResource(Path.of("/foo/bar/../baz.pkl").toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class ModuleKeysTest {
|
||||
file.writeString("age = 40")
|
||||
|
||||
val uri = file.toUri()
|
||||
val key = ModuleKeys.file(uri, file.toAbsolutePath())
|
||||
val key = ModuleKeys.file(uri)
|
||||
|
||||
assertThat(key.uri).isEqualTo(uri)
|
||||
assertThat(key.isCached).isTrue
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.pkl.commons.test.FileTestUtils
|
||||
import org.pkl.commons.test.PackageServer
|
||||
import org.pkl.commons.toPath
|
||||
import org.pkl.core.http.HttpClient
|
||||
import org.pkl.core.PklException
|
||||
import org.pkl.core.SecurityManagers
|
||||
@@ -34,7 +35,7 @@ class ProjectDependenciesResolverTest {
|
||||
|
||||
@Test
|
||||
fun resolveDependencies() {
|
||||
val project2Path = Path.of(javaClass.getResource("project2/PklProject")!!.path)
|
||||
val project2Path = javaClass.getResource("project2/PklProject")!!.toURI().toPath()
|
||||
val project = Project.loadFromPath(project2Path)
|
||||
val packageResolver = PackageResolver.getInstance(SecurityManagers.defaultManager, httpClient, null)
|
||||
val deps = ProjectDependenciesResolver(project, packageResolver, System.out.writer()).resolve()
|
||||
@@ -72,7 +73,7 @@ class ProjectDependenciesResolverTest {
|
||||
|
||||
@Test
|
||||
fun `fails if project declares a package with an incorrect checksum`() {
|
||||
val projectPath = Path.of(javaClass.getResource("badProjectChecksum/PklProject")!!.path)
|
||||
val projectPath = javaClass.getResource("badProjectChecksum/PklProject")!!.toURI().toPath()
|
||||
val project = Project.loadFromPath(projectPath)
|
||||
val packageResolver = PackageResolver.getInstance(SecurityManagers.defaultManager, httpClient, null)
|
||||
val e = assertThrows<PklException> {
|
||||
|
||||
@@ -137,7 +137,7 @@ class ProjectTest {
|
||||
@Test
|
||||
fun `evaluate project module -- invalid checksum`() {
|
||||
PackageServer().use { server ->
|
||||
val projectDir = Path.of(javaClass.getResource("badProjectChecksum2/")!!.path)
|
||||
val projectDir = Path.of(javaClass.getResource("badProjectChecksum2/")!!.toURI())
|
||||
val project = Project.loadFromPath(projectDir.resolve("PklProject"))
|
||||
val httpClient = HttpClient.builder()
|
||||
.addCertificates(FileTestUtils.selfSignedCertificate)
|
||||
|
||||
@@ -117,70 +117,69 @@ class IoUtilsTest {
|
||||
|
||||
@Test
|
||||
fun `relativize file URLs`() {
|
||||
// perhaps URI("") would be a more precise result
|
||||
assertThat(
|
||||
IoUtils.relativize(
|
||||
URI("file://foo/bar/baz.pkl"),
|
||||
URI("file://foo/bar/baz.pkl")
|
||||
URI("file:///foo/bar/baz.pkl"),
|
||||
URI("file:///foo/bar/baz.pkl")
|
||||
)
|
||||
).isEqualTo(URI("baz.pkl"))
|
||||
|
||||
assertThat(
|
||||
IoUtils.relativize(
|
||||
URI("file://foo/bar/baz.pkl"),
|
||||
URI("file://foo/bar/qux.pkl")
|
||||
URI("file:///foo/bar/baz.pkl"),
|
||||
URI("file:///foo/bar/qux.pkl")
|
||||
)
|
||||
).isEqualTo(URI("baz.pkl"))
|
||||
|
||||
assertThat(
|
||||
IoUtils.relativize(
|
||||
URI("file://foo/bar/baz.pkl"),
|
||||
URI("file://foo/bar/")
|
||||
URI("file:///foo/bar/baz.pkl"),
|
||||
URI("file:///foo/bar/")
|
||||
)
|
||||
).isEqualTo(URI("baz.pkl"))
|
||||
|
||||
assertThat(
|
||||
IoUtils.relativize(
|
||||
URI("file://foo/bar/baz.pkl"),
|
||||
URI("file://foo/bar")
|
||||
URI("file:///foo/bar/baz.pkl"),
|
||||
URI("file:///foo/bar")
|
||||
)
|
||||
).isEqualTo(URI("bar/baz.pkl"))
|
||||
|
||||
// URI.relativize() returns an absolute URI here
|
||||
assertThat(
|
||||
IoUtils.relativize(
|
||||
URI("file://foo/bar/baz.pkl"),
|
||||
URI("file://foo/qux/")
|
||||
URI("file:///foo/bar/baz.pkl"),
|
||||
URI("file:///foo/qux/")
|
||||
)
|
||||
).isEqualTo(URI("../bar/baz.pkl"))
|
||||
|
||||
assertThat(
|
||||
IoUtils.relativize(
|
||||
URI("file://foo/bar/baz.pkl"),
|
||||
URI("file://foo/qux/qux2/")
|
||||
URI("file:///foo/bar/baz.pkl"),
|
||||
URI("file:///foo/qux/qux2/")
|
||||
)
|
||||
).isEqualTo(URI("../../bar/baz.pkl"))
|
||||
|
||||
assertThat(
|
||||
IoUtils.relativize(
|
||||
URI("file://foo/bar/baz.pkl"),
|
||||
URI("file://foo/qux/qux2")
|
||||
URI("file:///foo/bar/baz.pkl"),
|
||||
URI("file:///foo/qux/qux2")
|
||||
)
|
||||
).isEqualTo(URI("../bar/baz.pkl"))
|
||||
|
||||
assertThat(
|
||||
IoUtils.relativize(
|
||||
URI("file://foo/bar/baz.pkl"),
|
||||
URI("file://qux/qux2/")
|
||||
URI("file:///foo/bar/baz.pkl"),
|
||||
URI("file:///qux/qux2/")
|
||||
)
|
||||
).isEqualTo(URI("file://foo/bar/baz.pkl"))
|
||||
).isEqualTo(URI("../../foo/bar/baz.pkl"))
|
||||
|
||||
assertThat(
|
||||
IoUtils.relativize(
|
||||
URI("file://foo/bar/baz.pkl"),
|
||||
URI("https://foo/bar/baz.pkl")
|
||||
URI("file:///foo/bar/baz.pkl"),
|
||||
URI("https:///foo/bar/baz.pkl")
|
||||
)
|
||||
).isEqualTo(URI("file://foo/bar/baz.pkl"))
|
||||
).isEqualTo(URI("file:///foo/bar/baz.pkl"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -343,7 +342,7 @@ class IoUtilsTest {
|
||||
val file3 = tempDir.resolve("base1/dir2/foo.pkl").createParentDirectories().createFile()
|
||||
|
||||
val uri = file2.toUri()
|
||||
val key = ModuleKeys.file(uri, file2)
|
||||
val key = ModuleKeys.file(uri)
|
||||
|
||||
assertThat(IoUtils.resolve(FakeSecurityManager, key, URI("..."))).isEqualTo(file1.toUri())
|
||||
assertThat(IoUtils.resolve(FakeSecurityManager, key, URI(".../foo.pkl"))).isEqualTo(file1.toUri())
|
||||
|
||||
@@ -4,3 +4,4 @@ org.pkl.core.MacAarch64LanguageSnippetTestsEngine
|
||||
org.pkl.core.LinuxAmd64LanguageSnippetTestsEngine
|
||||
org.pkl.core.LinuxAarch64LanguageSnippetTestsEngine
|
||||
org.pkl.core.AlpineLanguageSnippetTestsEngine
|
||||
org.pkl.core.WindowsLanguageSnippetTestsEngine
|
||||
|
||||
Reference in New Issue
Block a user