Support path containing unicode characters on Linux

This commit is contained in:
Glavo
2021-10-30 22:41:07 +08:00
committed by Yuhui Huang
parent 6aef034880
commit 9f617c33f5
2 changed files with 50 additions and 7 deletions

View File

@@ -45,6 +45,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*; import java.util.*;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -68,7 +69,7 @@ public class DefaultLauncher extends Launcher {
super(repository, version, authInfo, options, listener, daemon); super(repository, version, authInfo, options, listener, daemon);
} }
private CommandBuilder generateCommandLine(File nativeFolder) throws IOException { private Command generateCommandLine(File nativeFolder) throws IOException {
CommandBuilder res = new CommandBuilder(); CommandBuilder res = new CommandBuilder();
switch (options.getProcessPriority()) { switch (options.getProcessPriority()) {
@@ -208,10 +209,19 @@ public class DefaultLauncher extends Launcher {
Path gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId()); Path gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId());
Map<String, String> configuration = getConfigurations(); Map<String, String> configuration = getConfigurations();
configuration.put("${classpath}", String.join(OperatingSystem.PATH_SEPARATOR, classpath)); configuration.put("${classpath}", String.join(OperatingSystem.PATH_SEPARATOR, classpath));
configuration.put("${natives_directory}", nativeFolder.getAbsolutePath());
configuration.put("${game_assets}", gameAssets.toAbsolutePath().toString()); configuration.put("${game_assets}", gameAssets.toAbsolutePath().toString());
configuration.put("${assets_root}", gameAssets.toAbsolutePath().toString()); configuration.put("${assets_root}", gameAssets.toAbsolutePath().toString());
String nativeFolderPath = nativeFolder.getAbsolutePath();
Path tempNativeFolder = null;
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX
&& !StringUtils.isASCII(nativeFolderPath)) {
tempNativeFolder = Paths.get("/", "tmp", "hmcl-natives-" + UUID.randomUUID());
nativeFolderPath = tempNativeFolder + File.pathSeparator + nativeFolderPath;
}
configuration.put("${natives_directory}", nativeFolderPath);
res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration)); res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration));
if (authInfo.getArguments() != null && authInfo.getArguments().getJvm() != null && !authInfo.getArguments().getJvm().isEmpty()) if (authInfo.getArguments() != null && authInfo.getArguments().getJvm() != null && !authInfo.getArguments().getJvm().isEmpty())
res.addAll(Arguments.parseArguments(authInfo.getArguments().getJvm(), configuration)); res.addAll(Arguments.parseArguments(authInfo.getArguments().getJvm(), configuration));
@@ -258,7 +268,7 @@ public class DefaultLauncher extends Launcher {
res.addAllWithoutParsing(Arguments.parseStringArguments(options.getGameArguments(), configuration)); res.addAllWithoutParsing(Arguments.parseStringArguments(options.getGameArguments(), configuration));
res.removeIf(it -> getForbiddens().containsKey(it) && getForbiddens().get(it).get()); res.removeIf(it -> getForbiddens().containsKey(it) && getForbiddens().get(it).get());
return res; return new Command(res, tempNativeFolder);
} }
public Map<String, Boolean> getFeatures() { public Map<String, Boolean> getFeatures() {
@@ -363,8 +373,10 @@ public class DefaultLauncher extends Launcher {
nativeFolder = new File(options.getNativesDir()); nativeFolder = new File(options.getNativesDir());
} }
final Command command = generateCommandLine(nativeFolder);
// To guarantee that when failed to generate launch command line, we will not call pre-launch command // To guarantee that when failed to generate launch command line, we will not call pre-launch command
List<String> rawCommandLine = generateCommandLine(nativeFolder).asMutableList(); List<String> rawCommandLine = command.commandLine.asMutableList();
// Pass classpath using the environment variable, to reduce the command length // Pass classpath using the environment variable, to reduce the command length
String classpath = null; String classpath = null;
@@ -374,6 +386,12 @@ public class DefaultLauncher extends Launcher {
classpath = rawCommandLine.remove(cpIndex); classpath = rawCommandLine.remove(cpIndex);
} }
if (command.tempNativeFolder != null) {
Files.deleteIfExists(command.tempNativeFolder);
Files.createSymbolicLink(command.tempNativeFolder, nativeFolder.toPath().toAbsolutePath());
command.tempNativeFolder.toFile().deleteOnExit();
}
if (rawCommandLine.stream().anyMatch(StringUtils::isBlank)) { if (rawCommandLine.stream().anyMatch(StringUtils::isBlank)) {
throw new IllegalStateException("Illegal command line " + rawCommandLine); throw new IllegalStateException("Illegal command line " + rawCommandLine);
} }
@@ -464,8 +482,8 @@ public class DefaultLauncher extends Launcher {
if (!FileUtils.makeFile(scriptFile)) if (!FileUtils.makeFile(scriptFile))
throw new IOException("Script file: " + scriptFile + " cannot be created."); throw new IOException("Script file: " + scriptFile + " cannot be created.");
final CommandBuilder commandLine = generateCommandLine(nativeFolder); final Command commandLine = generateCommandLine(nativeFolder);
final String command = usePowerShell ? null : commandLine.toString(); final String command = usePowerShell ? null : commandLine.commandLine.toString();
if (!usePowerShell && isWindows) { if (!usePowerShell && isWindows) {
if (command.length() > 8192) { // maximum length of the command in cmd if (command.length() > 8192) { // maximum length of the command in cmd
@@ -509,7 +527,7 @@ public class DefaultLauncher extends Launcher {
writer.newLine(); writer.newLine();
writer.write('&'); writer.write('&');
for (String rawCommand : commandLine.asList()) { for (String rawCommand : commandLine.commandLine.asList()) {
writer.write(' '); writer.write(' ');
writer.write(CommandBuilder.pwshString(rawCommand)); writer.write(CommandBuilder.pwshString(rawCommand));
} }
@@ -533,6 +551,10 @@ public class DefaultLauncher extends Launcher {
writer.write("export " + entry.getKey() + "=" + entry.getValue()); writer.write("export " + entry.getKey() + "=" + entry.getValue());
writer.newLine(); writer.newLine();
} }
if (commandLine.tempNativeFolder != null) {
writer.write(new CommandBuilder().add("ln", "-s", nativeFolder.getAbsolutePath(), commandLine.tempNativeFolder.toString()).toString());
writer.newLine();
}
writer.write(new CommandBuilder().add("cd", repository.getRunDirectory(version.getId()).getAbsolutePath()).toString()); writer.write(new CommandBuilder().add("cd", repository.getRunDirectory(version.getId()).getAbsolutePath()).toString());
} }
writer.newLine(); writer.newLine();
@@ -551,6 +573,10 @@ public class DefaultLauncher extends Launcher {
writer.write("pause"); writer.write("pause");
writer.newLine(); writer.newLine();
} }
if (commandLine.tempNativeFolder != null) {
writer.write(new CommandBuilder().add("rm", commandLine.tempNativeFolder.toString()).toString());
writer.newLine();
}
} }
} }
if (!scriptFile.setExecutable(true)) if (!scriptFile.setExecutable(true))
@@ -574,4 +600,14 @@ public class DefaultLauncher extends Launcher {
managedProcess.addRelatedThread(stderr); managedProcess.addRelatedThread(stderr);
managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), processListener::onExit), "exit-waiter", isDaemon)); managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), processListener::onExit), "exit-waiter", isDaemon));
} }
private static final class Command {
final CommandBuilder commandLine;
final Path tempNativeFolder;
Command(CommandBuilder commandBuilder, Path tempNativeFolder) {
this.commandLine = commandBuilder;
this.tempNativeFolder = tempNativeFolder;
}
}
} }

View File

@@ -22,6 +22,8 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -258,6 +260,10 @@ public final class StringUtils {
return Optional.of(str.substring(0, halfLength) + " ... " + str.substring(str.length() - halfLength)); return Optional.of(str.substring(0, halfLength) + " ... " + str.substring(str.length() - halfLength));
} }
public static boolean isASCII(CharSequence cs) {
return US_ASCII_ENCODER.canEncode(cs);
}
/** /**
* Class for computing the longest common subsequence between strings. * Class for computing the longest common subsequence between strings.
*/ */
@@ -295,4 +301,5 @@ public final class StringUtils {
public static final Pattern CHINESE_PATTERN = Pattern.compile("[\\u4e00-\\u9fa5]"); public static final Pattern CHINESE_PATTERN = Pattern.compile("[\\u4e00-\\u9fa5]");
public static final CharsetEncoder US_ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder();
} }