From a29fba8afd2a4896d54211c1f79fec456301957c Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Mon, 27 Nov 2023 20:08:58 +0100 Subject: [PATCH 1/2] chore: restructure `Platform` --- .../terasology/launcher/LauncherInitTask.java | 10 +- .../terasology/launcher/game/GameService.java | 28 ++-- .../terasology/launcher/game/GameStarter.java | 6 +- .../terasology/launcher/platform/Arch.java | 10 ++ .../org/terasology/launcher/platform/OS.java | 10 ++ .../launcher/platform/Platform.java | 132 ++++++++++++++++++ .../UnsupportedPlatformException.java | 15 ++ .../launcher/util/LauncherDirectoryUtils.java | 1 + .../terasology/launcher/util/Platform.java | 82 ----------- .../launcher/game/TestGameStarter.java | 15 +- 10 files changed, 202 insertions(+), 107 deletions(-) create mode 100644 src/main/java/org/terasology/launcher/platform/Arch.java create mode 100644 src/main/java/org/terasology/launcher/platform/OS.java create mode 100644 src/main/java/org/terasology/launcher/platform/Platform.java create mode 100644 src/main/java/org/terasology/launcher/platform/UnsupportedPlatformException.java delete mode 100644 src/main/java/org/terasology/launcher/util/Platform.java diff --git a/src/main/java/org/terasology/launcher/LauncherInitTask.java b/src/main/java/org/terasology/launcher/LauncherInitTask.java index e50ad6f00..0b047498b 100644 --- a/src/main/java/org/terasology/launcher/LauncherInitTask.java +++ b/src/main/java/org/terasology/launcher/LauncherInitTask.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import org.terasology.launcher.game.GameManager; import org.terasology.launcher.model.LauncherVersion; +import org.terasology.launcher.platform.UnsupportedPlatformException; import org.terasology.launcher.repositories.CombinedRepository; import org.terasology.launcher.settings.LauncherSettingsValidator; import org.terasology.launcher.settings.Settings; @@ -24,7 +25,7 @@ import org.terasology.launcher.util.LauncherDirectoryUtils; import org.terasology.launcher.util.LauncherManagedDirectory; import org.terasology.launcher.util.LauncherStartFailedException; -import org.terasology.launcher.util.Platform; +import org.terasology.launcher.platform.Platform; import java.io.IOException; import java.net.URI; @@ -114,18 +115,17 @@ protected LauncherConfiguration call() { releaseRepository); } catch (LauncherStartFailedException e) { logger.warn("Could not configure launcher."); + } catch (UnsupportedPlatformException e) { + logger.error("Unsupported OS or architecture: {}", e.getMessage()); } return null; } - private Platform getPlatform() { + private Platform getPlatform() throws UnsupportedPlatformException { logger.trace("Init Platform..."); updateMessage(I18N.getLabel("splash_checkOS")); final Platform platform = Platform.getPlatform(); - if (!platform.isLinux() && !platform.isMac() && !platform.isWindows()) { - logger.warn("Detected unexpected platform: {}", platform); - } logger.debug("Platform: {}", platform); return platform; } diff --git a/src/main/java/org/terasology/launcher/game/GameService.java b/src/main/java/org/terasology/launcher/game/GameService.java index a8221bf23..c33c3fd66 100644 --- a/src/main/java/org/terasology/launcher/game/GameService.java +++ b/src/main/java/org/terasology/launcher/game/GameService.java @@ -8,6 +8,7 @@ import javafx.concurrent.Worker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.terasology.launcher.platform.UnsupportedPlatformException; import org.terasology.launcher.settings.Settings; import java.io.IOException; @@ -49,19 +50,20 @@ public class GameService extends Service { public GameService() { setExecutor(Executors.newSingleThreadExecutor( - new ThreadFactoryBuilder() - .setNameFormat("GameService-%d") - .setDaemon(true) - .setUncaughtExceptionHandler(this::exceptionHandler) - .build() + new ThreadFactoryBuilder() + .setNameFormat("GameService-%d") + .setDaemon(true) + .setUncaughtExceptionHandler(this::exceptionHandler) + .build() )); } /** * Start a new game process with these settings. + * * @param gameInstallation the directory under which we will find libs/Terasology.jar, also used as the process's - * working directory - * @param settings supplies other settings relevant to configuring a process + * working directory + * @param settings supplies other settings relevant to configuring a process */ @SuppressWarnings("checkstyle:HiddenField") public void start(GameInstallation gameInstallation, Settings settings) { @@ -115,7 +117,7 @@ public void restart() { * This class's configuration fields must be set before this is called. * * @throws com.google.common.base.VerifyException when fields are unset - * @throws RuntimeException when required files in the game directory are missing or inaccessible + * @throws RuntimeException when required files in the game directory are missing or inaccessible */ @Override protected RunGameTask createTask() throws GameVersionNotSupportedException { @@ -128,19 +130,23 @@ protected RunGameTask createTask() throws GameVersionNotSupportedException { settings.userJavaParameters.get(), settings.userGameParameters.get(), settings.logLevel.get()); - } catch (IOException e) { + } catch (IOException | UnsupportedPlatformException e) { throw new RuntimeException("Error using this as a game directory: " + gamePath, e); } return new RunGameTask(starter); } - /** After a task completes, reset to ready for the next. */ + /** + * After a task completes, reset to ready for the next. + */ @Override protected void succeeded() { reset(); // Ready to go again! } - /** Checks to see if the failure left any exceptions behind, then resets to ready. */ + /** + * Checks to see if the failure left any exceptions behind, then resets to ready. + */ @Override protected void failed() { // "Uncaught" exceptions from javafx's Task are actually caught and kept in a property, diff --git a/src/main/java/org/terasology/launcher/game/GameStarter.java b/src/main/java/org/terasology/launcher/game/GameStarter.java index 75e87b67c..097dc7452 100644 --- a/src/main/java/org/terasology/launcher/game/GameStarter.java +++ b/src/main/java/org/terasology/launcher/game/GameStarter.java @@ -7,8 +7,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; +import org.terasology.launcher.platform.UnsupportedPlatformException; import org.terasology.launcher.util.JavaHeapSize; -import org.terasology.launcher.util.Platform; +import org.terasology.launcher.platform.Platform; import java.io.IOException; import java.nio.file.Path; @@ -38,7 +39,8 @@ final class GameStarter implements Callable { * @param logLevel the minimum level of log events Terasology will include on its output stream to us */ GameStarter(GameInstallation gameInstallation, Path gameDataDirectory, JavaHeapSize heapMin, JavaHeapSize heapMax, - List javaParams, List gameParams, Level logLevel) throws IOException, GameVersionNotSupportedException { + List javaParams, List gameParams, Level logLevel) + throws IOException, GameVersionNotSupportedException, UnsupportedPlatformException { Semver engineVersion = gameInstallation.getEngineVersion(); var gamePath = gameInstallation.getPath(); diff --git a/src/main/java/org/terasology/launcher/platform/Arch.java b/src/main/java/org/terasology/launcher/platform/Arch.java new file mode 100644 index 000000000..2ba512535 --- /dev/null +++ b/src/main/java/org/terasology/launcher/platform/Arch.java @@ -0,0 +1,10 @@ +// Copyright 2023 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.launcher.platform; + +public enum Arch { + X64, + X86, + ARM64 +} diff --git a/src/main/java/org/terasology/launcher/platform/OS.java b/src/main/java/org/terasology/launcher/platform/OS.java new file mode 100644 index 000000000..2adc5324b --- /dev/null +++ b/src/main/java/org/terasology/launcher/platform/OS.java @@ -0,0 +1,10 @@ +// Copyright 2023 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.launcher.platform; + +public enum OS { + WINDOWS, + MAC, + LINUX +} diff --git a/src/main/java/org/terasology/launcher/platform/Platform.java b/src/main/java/org/terasology/launcher/platform/Platform.java new file mode 100644 index 000000000..aedf4a42a --- /dev/null +++ b/src/main/java/org/terasology/launcher/platform/Platform.java @@ -0,0 +1,132 @@ +// Copyright 2023 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.launcher.platform; + +import com.google.common.base.Objects; +import com.google.common.collect.Sets; + +import java.util.Locale; +import java.util.Set; + +/** + * A simplified representation of a computer platform as `os` and `arch` + */ +public final class Platform { + + final private OS os; + final private Arch arch; + + //TODO(skaldarnar): I'm not fully settled on how to model the Platform. I thought splitting this up into two enums + // for OS and Architecture would help with more type-safe construction of these values, simplify comparison to + // select the right JRE for a game, etc. + // + // On the other hand, the set of supported platforms is so limited, and continuing on a non-supported platform + // does not make much sense. So, maybe it is better to have a rather restrictive and explicit enum in the form of + // WINDOWS_X64 + // LINUX_X64 + // I'm adding the architecture to this list, as I hope that we'll be able to support old Intel and new M1 Macs at + // some point in the future, adding the following to the list: + // MAC_X64 + // MAC_AARCH64 + // The biggest drawback of being super-strict here is that development on non-supported platforms becomes + // impossible where it was just "not ideal" before. + public static final Set SUPPORTED_PLATFORMS = Sets.newHashSet( + new Platform(OS.WINDOWS, Arch.X64), + new Platform(OS.LINUX, Arch.X64), + new Platform(OS.MAC, Arch.X64) + ); + + public Platform(OS os, Arch arch) { + this.os = os; + this.arch = arch; + } + + /** + * @return the simplified operating system name as platform os + */ + public String getOs() { + //TODO: change return type to OS + return os.name().toLowerCase(Locale.ENGLISH); + } + + /** + * @return the simplified operating system architecture as platform arch + */ + public Arch getArch() { + return arch; + } + + public boolean isLinux() { + return os == OS.LINUX; + } + + public boolean isMac() { + return os == OS.MAC; + } + + public boolean isWindows() { + return os == OS.WINDOWS; + } + + public String toString() { + return "OS '" + os + "', arch '" + arch + "'"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Platform platform = (Platform) o; + return os == platform.os && arch == platform.arch; + } + + @Override + public int hashCode() { + return Objects.hashCode(os, arch); + } + + /** + * Get information on the host platform the launcher is currently running on. + * + * @return the platform + */ + public static Platform getPlatform() throws UnsupportedPlatformException { + final String platformOs = System.getProperty("os.name").toLowerCase(); + final OS os; + if (platformOs.startsWith("linux")) { + os = OS.LINUX; + } else if (platformOs.startsWith("mac os")) { + os = OS.MAC; + } else if (platformOs.startsWith("windows")) { + os = OS.WINDOWS; + } else { + throw new UnsupportedPlatformException("Unsupported OS: " + platformOs); + } + + final String platformArch = System.getProperty("os.arch"); + final Arch arch; + switch (platformArch) { + case "x86_64": + case "amd64": + arch = Arch.X64; + break; + case "x86": + case "i386": + arch = Arch.X86; + break; + case "aarch64": + case "arm64": + arch = Arch.ARM64; + break; + default: + throw new UnsupportedPlatformException("Architecture not supported: " + platformArch); + } + + return new Platform(os, arch); + } +} diff --git a/src/main/java/org/terasology/launcher/platform/UnsupportedPlatformException.java b/src/main/java/org/terasology/launcher/platform/UnsupportedPlatformException.java new file mode 100644 index 000000000..3fd22be49 --- /dev/null +++ b/src/main/java/org/terasology/launcher/platform/UnsupportedPlatformException.java @@ -0,0 +1,15 @@ +// Copyright 2023 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.launcher.platform; + +public class UnsupportedPlatformException extends Exception { + + public UnsupportedPlatformException() { + super(); + } + + public UnsupportedPlatformException(String message) { + super(message); + } +} diff --git a/src/main/java/org/terasology/launcher/util/LauncherDirectoryUtils.java b/src/main/java/org/terasology/launcher/util/LauncherDirectoryUtils.java index 9bd3e5acf..a12ee9921 100644 --- a/src/main/java/org/terasology/launcher/util/LauncherDirectoryUtils.java +++ b/src/main/java/org/terasology/launcher/util/LauncherDirectoryUtils.java @@ -5,6 +5,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.terasology.launcher.platform.Platform; import java.io.IOException; import java.net.URISyntaxException; diff --git a/src/main/java/org/terasology/launcher/util/Platform.java b/src/main/java/org/terasology/launcher/util/Platform.java deleted file mode 100644 index 33abfa100..000000000 --- a/src/main/java/org/terasology/launcher/util/Platform.java +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2020 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -package org.terasology.launcher.util; - -/** - * A simplified representation of a computer platform as `os` and `arch` - */ -public final class Platform { - - private static final Platform PLATFORM = new Platform(); - - private String os; - private String arch; - - /** - * Constructs platform information for the current host system. - * Simplifies operating system name to one of `linux`, `mac`, `windows` if applicable. - * Simplifies operating system architecture to one of `32` and `64` if applicable. - */ - private Platform() { - final String platformOs = System.getProperty("os.name").toLowerCase(); - // TODO: consider using regex - if (platformOs.startsWith("linux")) { - os = "linux"; - } else if (platformOs.startsWith("mac os")) { - os = "mac"; - } else if (platformOs.startsWith("windows")) { - os = "windows"; - } else { - os = platformOs; - } - - final String platformArch = System.getProperty("os.arch"); - if (platformArch.equals("x86_64") || platformArch.equals("amd64")) { - arch = "64"; - } else if (platformArch.equals("x86") || platformArch.equals("i386")) { - arch = "32"; - } else { - arch = platformArch; - } - } - - /** - * @return the simplified operating system name as platform os - */ - public String getOs() { - return os; - } - - /** - * @return the simplified operating system architecture as platform arch - */ - public String getArch() { - return arch; - } - - public boolean isLinux() { - return os.equals("linux"); - } - - public boolean isMac() { - return os.equals("mac"); - } - - public boolean isWindows() { - return os.equals("windows"); - } - - public String toString() { - return "OS '" + os + "', arch '" + arch + "'"; - } - - /** - * Get information on the host platform the launcher is currently running on. - * - * @return the platform - */ - public static Platform getPlatform() { - return PLATFORM; - } -} diff --git a/src/test/java/org/terasology/launcher/game/TestGameStarter.java b/src/test/java/org/terasology/launcher/game/TestGameStarter.java index a39da7aae..e43bad9fa 100644 --- a/src/test/java/org/terasology/launcher/game/TestGameStarter.java +++ b/src/test/java/org/terasology/launcher/game/TestGameStarter.java @@ -9,6 +9,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.event.Level; +import org.terasology.launcher.platform.UnsupportedPlatformException; import org.terasology.launcher.util.JavaHeapSize; import java.io.IOException; @@ -49,24 +50,24 @@ public void setup() { gameParams = List.of(GAME_ARG_1); } - private GameStarter newStarter() throws IOException { + private GameStarter newStarter() throws IOException, UnsupportedPlatformException { return newStarter(Path.of("libs", "Terasology.jar")); } - private GameStarter newStarter(Path relativeGameJarPath) throws IOException { + private GameStarter newStarter(Path relativeGameJarPath) throws IOException, UnsupportedPlatformException { StubGameInstallation stubgameinstall = new StubGameInstallation(gamePath, relativeGameJarPath); return new GameStarter(stubgameinstall, gameDataPath, JavaHeapSize.NOT_USED, JavaHeapSize.GB_4, javaParams, gameParams, LOG_LEVEL); } @Test - public void testConstruction() throws IOException { + public void testConstruction() throws IOException, UnsupportedPlatformException { GameStarter starter = newStarter(); assertNotNull(starter); } @Test - public void testJre() throws IOException { + public void testJre() throws IOException, UnsupportedPlatformException { Semver engineVersion = new Semver("5.0.0"); GameStarter task = newStarter(); // This is the sort of test where the code under test and the expectation are just copies @@ -84,7 +85,7 @@ static Stream provideJarPaths() { @ParameterizedTest @MethodSource("provideJarPaths") - public void testBuildProcess(Path jarRelativePath) throws IOException { + public void testBuildProcess(Path jarRelativePath) throws IOException, UnsupportedPlatformException { GameStarter starter = newStarter(jarRelativePath); ProcessBuilder processBuilder = starter.processBuilder; final Path gameJar = gamePath.resolve(jarRelativePath); @@ -100,14 +101,14 @@ public void testBuildProcess(Path jarRelativePath) throws IOException { } @Test - public void testSupportedJava11() throws IOException { + public void testSupportedJava11() throws IOException, UnsupportedPlatformException { Semver engineVersion = new Semver("5.3.0"); GameStarter task = newStarter(); assertDoesNotThrow(() -> task.getRuntimePath(engineVersion)); } @Test - public void testUnsupportedJava17() throws IOException { + public void testUnsupportedJava17() throws IOException, UnsupportedPlatformException { Semver engineVersion = new Semver("6.0.0"); GameStarter task = newStarter(); assertThrows(GameVersionNotSupportedException.class, () -> task.getRuntimePath(engineVersion)); From 76ee0462097ceed06defd985b31d59a639e029e5 Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Thu, 27 Jun 2024 13:27:58 +0200 Subject: [PATCH 2/2] make Platform an enum itself --- .../launcher/platform/Platform.java | 93 +++++++------------ 1 file changed, 32 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/terasology/launcher/platform/Platform.java b/src/main/java/org/terasology/launcher/platform/Platform.java index aedf4a42a..b5a40d711 100644 --- a/src/main/java/org/terasology/launcher/platform/Platform.java +++ b/src/main/java/org/terasology/launcher/platform/Platform.java @@ -3,58 +3,29 @@ package org.terasology.launcher.platform; -import com.google.common.base.Objects; -import com.google.common.collect.Sets; - -import java.util.Locale; -import java.util.Set; - /** * A simplified representation of a computer platform as `os` and `arch` */ -public final class Platform { - - final private OS os; - final private Arch arch; - - //TODO(skaldarnar): I'm not fully settled on how to model the Platform. I thought splitting this up into two enums - // for OS and Architecture would help with more type-safe construction of these values, simplify comparison to - // select the right JRE for a game, etc. - // - // On the other hand, the set of supported platforms is so limited, and continuing on a non-supported platform - // does not make much sense. So, maybe it is better to have a rather restrictive and explicit enum in the form of - // WINDOWS_X64 - // LINUX_X64 - // I'm adding the architecture to this list, as I hope that we'll be able to support old Intel and new M1 Macs at - // some point in the future, adding the following to the list: - // MAC_X64 - // MAC_AARCH64 - // The biggest drawback of being super-strict here is that development on non-supported platforms becomes - // impossible where it was just "not ideal" before. - public static final Set SUPPORTED_PLATFORMS = Sets.newHashSet( - new Platform(OS.WINDOWS, Arch.X64), - new Platform(OS.LINUX, Arch.X64), - new Platform(OS.MAC, Arch.X64) - ); +public enum Platform { - public Platform(OS os, Arch arch) { - this.os = os; - this.arch = arch; - } + // unsupported platforms commented out, but might be useful for local development + // MACOS_X64(OS.MAC, Arch.X64), + // supported platforms by both the game and the launcher + WINDOWS_X64(OS.WINDOWS, Arch.X64), + LINUX_X64(OS.LINUX, Arch.X64); /** - * @return the simplified operating system name as platform os + * The simplified operating system identifier. */ - public String getOs() { - //TODO: change return type to OS - return os.name().toLowerCase(Locale.ENGLISH); - } - + public final OS os; /** - * @return the simplified operating system architecture as platform arch + * The simplified architecture identifier. */ - public Arch getArch() { - return arch; + public final Arch arch; + + Platform(OS os, Arch arch) { + this.os = os; + this.arch = arch; } public boolean isLinux() { @@ -73,23 +44,6 @@ public String toString() { return "OS '" + os + "', arch '" + arch + "'"; } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Platform platform = (Platform) o; - return os == platform.os && arch == platform.arch; - } - - @Override - public int hashCode() { - return Objects.hashCode(os, arch); - } - /** * Get information on the host platform the launcher is currently running on. * @@ -127,6 +81,23 @@ public static Platform getPlatform() throws UnsupportedPlatformException { throw new UnsupportedPlatformException("Architecture not supported: " + platformArch); } - return new Platform(os, arch); + return fromOsAndArch(os, arch); + } + + /** + * Derive the {@link Platform} from the given {@link OS} and {@link Arch} + * + * @throws UnsupportedPlatformException if the given OS and Arch combination is not supported + */ + public static Platform fromOsAndArch(OS os, Arch arch) throws UnsupportedPlatformException { + if (os.equals(OS.WINDOWS) && arch.equals(Arch.X64)) { + return WINDOWS_X64; + } else if (os.equals(OS.LINUX) && arch.equals(Arch.X64)) { + return LINUX_X64; +// } else if (os.equals(OS.MAC) && arch.equals(Arch.X64)) { +// return MACOS_X64; + } else { + throw new UnsupportedPlatformException("Unsupported platform: " + os + " " + arch); + } } }