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..b5a40d711 --- /dev/null +++ b/src/main/java/org/terasology/launcher/platform/Platform.java @@ -0,0 +1,103 @@ +// Copyright 2023 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.launcher.platform; + +/** + * A simplified representation of a computer platform as `os` and `arch` + */ +public enum Platform { + + // 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); + + /** + * The simplified operating system identifier. + */ + public final OS os; + /** + * The simplified architecture identifier. + */ + public final Arch arch; + + Platform(OS os, Arch arch) { + this.os = os; + this.arch = 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 + "'"; + } + + /** + * 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 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); + } + } +} 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));