Compare commits

...

3 Commits

Author SHA1 Message Date
Jonathan Coates 929debd382
Don't build the webside on Windows/Mac
It seems to stall on Mac, and unlike Windows, I don't have access to a
machine to debug it :/.
2024-04-24 21:49:49 +01:00
Jonathan Coates 4980b7355d
Don't share CharsetDecoders across threads
Fixes #1803
2024-04-24 21:19:30 +01:00
Jonathan Coates 925092add3
Fix build on Windows
- Force encoding to UTF-8
 - Fix npm not being found on the path
 - Test building common/web on OSX and Windows
2024-04-24 17:55:48 +01:00
8 changed files with 56 additions and 25 deletions

View File

@ -58,13 +58,13 @@ jobs:
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \; find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: 📤 Upload Jar - name: 📤 Upload Jar
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: CC-Tweaked name: CC-Tweaked
path: ./jars path: ./jars
- name: 📤 Upload coverage - name: 📤 Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v4
build-core: build-core:
strategy: strategy:
@ -81,24 +81,28 @@ jobs:
runs-on: ${{ matrix.uses }} runs-on: ${{ matrix.uses }}
steps: steps:
- name: Clone repository - name: 📥 Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Java - name: 📥 Set up Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 17
distribution: 'temurin' distribution: 'temurin'
- name: Setup Gradle - name: 📥 Setup Gradle
uses: gradle/actions/setup-gradle@v3 uses: gradle/actions/setup-gradle@v3
with: with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }} cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
- name: Run tests - name: ⚒️ Build
run: |
./gradlew --configure-on-demand :core:assemble
- name: 🧪 Run tests
run: | run: |
./gradlew --configure-on-demand :core:test ./gradlew --configure-on-demand :core:test
- name: Parse test reports - name: 🧪 Parse test reports
run: python3 ./tools/parse-reports.py run: python3 ./tools/parse-reports.py
if: ${{ failure() }} if: ${{ failure() }}

View File

@ -29,9 +29,9 @@ ## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it. In order to develop CC: Tweaked, you'll need to download the source code and then run it.
- Make sure you've got the following software installed: - Make sure you've got the following software installed:
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium]. - Java Development Kit (JDK). This can be downloaded from [Adoptium].
- [Git](https://git-scm.com/). - [Git](https://git-scm.com/).
- If you want to work on documentation, [NodeJS][node]. - [NodeJS][node].
- Download CC: Tweaked's source code: - Download CC: Tweaked's source code:
``` ```

View File

@ -46,7 +46,7 @@ abstract class NpmInstall : DefaultTask() {
@TaskAction @TaskAction
fun install() { fun install() {
project.exec { project.exec {
commandLine("npm", "ci") commandLine(ProcessHelpers.getExecutable("npm"), "ci")
workingDir = projectRoot.get().asFile workingDir = projectRoot.get().asFile
} }
} }
@ -59,6 +59,6 @@ fun install() {
abstract class NpxExecToDir : ExecToDir() { abstract class NpxExecToDir : ExecToDir() {
init { init {
dependsOn(NpmInstall.TASK_NAME) dependsOn(NpmInstall.TASK_NAME)
executable = "npx" executable = ProcessHelpers.getExecutable("npx")
} }
} }

View File

@ -9,6 +9,7 @@
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
internal object ProcessHelpers { internal object ProcessHelpers {
fun startProcess(vararg command: String): Process { fun startProcess(vararg command: String): Process {
@ -34,7 +35,7 @@ fun captureLines(vararg command: String): List<String> {
val process = startProcess(*command) val process = startProcess(*command)
process.outputStream.close() process.outputStream.close()
val out = BufferedReader(InputStreamReader(process.inputStream)).use { reader -> val out = BufferedReader(InputStreamReader(process.inputStream, StandardCharsets.UTF_8)).use { reader ->
reader.lines().filter { it.isNotEmpty() }.toList() reader.lines().filter { it.isNotEmpty() }.toList()
} }
ProcessGroovyMethods.closeStreams(process) ProcessGroovyMethods.closeStreams(process)
@ -46,6 +47,28 @@ fun onPath(name: String): Boolean {
val path = System.getenv("PATH") ?: return false val path = System.getenv("PATH") ?: return false
return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() } return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() }
} }
/**
* Search for an executable on the `PATH` if required.
*
* [Process]/[ProcessBuilder] does not handle all executable file extensions on Windows (such as `.com). When on
* Windows, this function searches `PATH` and `PATHEXT` for an executable matching [name].
*/
fun getExecutable(name: String): String {
if (!System.getProperty("os.name").lowercase().contains("windows")) return name
val path = (System.getenv("PATH") ?: return name).split(File.pathSeparator)
val pathExt = (System.getenv("PATHEXT") ?: return name).split(File.pathSeparator)
for (pathEntry in path) {
for (ext in pathExt) {
val resolved = File(pathEntry, name + ext)
if (resolved.exists()) return resolved.getAbsolutePath()
}
}
return name
}
} }
internal fun Process.waitForOrThrow(message: String) { internal fun Process.waitForOrThrow(message: String) {

View File

@ -2,7 +2,7 @@
# #
# SPDX-License-Identifier: MPL-2.0 # SPDX-License-Identifier: MPL-2.0
org.gradle.jvmargs=-Xmx3G org.gradle.jvmargs=-Xmx3G -Dfile.encoding=UTF-8
org.gradle.parallel=true org.gradle.parallel=true
kotlin.stdlib.default.dependency=false kotlin.stdlib.default.dependency=false

View File

@ -29,7 +29,7 @@
* @see dan200.computercraft.core.apis.HTTPAPI#websocket On how to open a websocket. * @see dan200.computercraft.core.apis.HTTPAPI#websocket On how to open a websocket.
*/ */
public class WebsocketHandle { public class WebsocketHandle {
private static final CharsetDecoder DECODER = StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPLACE); private static final ThreadLocal<CharsetDecoder> DECODER = ThreadLocal.withInitial(() -> StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPLACE));
private final IAPIEnvironment environment; private final IAPIEnvironment environment;
private final String address; private final String address;
@ -87,7 +87,7 @@ public final void send(Coerced<ByteBuffer> message, Optional<Boolean> binary) th
websocket.sendBinary(text); websocket.sendBinary(text);
} else { } else {
try { try {
websocket.sendText(DECODER.decode(text).toString()); websocket.sendText(DECODER.get().decode(text).toString());
} catch (CharacterCodingException e) { } catch (CharacterCodingException e) {
// This shouldn't happen, but worth mentioning. // This shouldn't happen, but worth mentioning.
throw new LuaException("Message is not valid UTF8"); throw new LuaException("Message is not valid UTF8");

View File

@ -213,16 +213,16 @@ end
-- Existing settings will be merged with any pre-existing ones. Conflicting -- Existing settings will be merged with any pre-existing ones. Conflicting
-- entries will be overwritten, but any others will be preserved. -- entries will be overwritten, but any others will be preserved.
-- --
-- @tparam[opt] string sPath The file to load from, defaulting to `.settings`. -- @tparam[opt=".settings"] string path The file to load from.
-- @treturn boolean Whether settings were successfully read from this -- @treturn boolean Whether settings were successfully read from this
-- file. Reasons for failure may include the file not existing or being -- file. Reasons for failure may include the file not existing or being
-- corrupted. -- corrupted.
-- --
-- @see settings.save -- @see settings.save
-- @changed 1.87.0 `sPath` is now optional. -- @changed 1.87.0 `path` is now optional.
function load(sPath) function load(path)
expect(1, sPath, "string", "nil") expect(1, path, "string", "nil")
local file = fs.open(sPath or ".settings", "r") local file = fs.open(path or ".settings", "r")
if not file then if not file then
return false return false
end end
@ -255,14 +255,14 @@ end
-- This will entirely overwrite the pre-existing file. Settings defined in the -- This will entirely overwrite the pre-existing file. Settings defined in the
-- file, but not currently loaded will be removed. -- file, but not currently loaded will be removed.
-- --
-- @tparam[opt] string sPath The path to save settings to, defaulting to `.settings`. -- @tparam[opt=".settings"] string path The path to save settings to.
-- @treturn boolean If the settings were successfully saved. -- @treturn boolean If the settings were successfully saved.
-- --
-- @see settings.load -- @see settings.load
-- @changed 1.87.0 `sPath` is now optional. -- @changed 1.87.0 `path` is now optional.
function save(sPath) function save(path)
expect(1, sPath, "string", "nil") expect(1, path, "string", "nil")
local file = fs.open(sPath or ".settings", "w") local file = fs.open(path or ".settings", "w")
if not file then if not file then
return false return false
end end

View File

@ -68,6 +68,10 @@ public void remapClass(String from, String to) {
} }
private @Nullable Path findUnmappedFile(String name) { private @Nullable Path findUnmappedFile(String name) {
// For some odd reason, we try to resolve the generated lambda classes. This includes classes called
// things like <linit>lambda, which is an invalid path on Windows. Detect those, and abort early.
if (name.indexOf("<") >= 0) return null;
return classpath.stream().map(x -> x.resolve(name)).filter(Files::exists).findFirst().orElse(null); return classpath.stream().map(x -> x.resolve(name)).filter(Files::exists).findFirst().orElse(null);
} }