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.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Supplier;
@ -68,7 +69,7 @@ public class DefaultLauncher extends Launcher {
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();
switch (options.getProcessPriority()) {
@ -208,10 +209,19 @@ public class DefaultLauncher extends Launcher {
Path gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId());
Map<String, String> configuration = getConfigurations();
configuration.put("${classpath}", String.join(OperatingSystem.PATH_SEPARATOR, classpath));
configuration.put("${natives_directory}", nativeFolder.getAbsolutePath());
configuration.put("${game_assets}", 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));
if (authInfo.getArguments() != null && authInfo.getArguments().getJvm() != null && !authInfo.getArguments().getJvm().isEmpty())
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.removeIf(it -> getForbiddens().containsKey(it) && getForbiddens().get(it).get());
return res;
return new Command(res, tempNativeFolder);
}
public Map<String, Boolean> getFeatures() {
@ -363,8 +373,10 @@ public class DefaultLauncher extends Launcher {
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
List<String> rawCommandLine = generateCommandLine(nativeFolder).asMutableList();
List<String> rawCommandLine = command.commandLine.asMutableList();
// Pass classpath using the environment variable, to reduce the command length
String classpath = null;
@ -374,6 +386,12 @@ public class DefaultLauncher extends Launcher {
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)) {
throw new IllegalStateException("Illegal command line " + rawCommandLine);
}
@ -464,8 +482,8 @@ public class DefaultLauncher extends Launcher {
if (!FileUtils.makeFile(scriptFile))
throw new IOException("Script file: " + scriptFile + " cannot be created.");
final CommandBuilder commandLine = generateCommandLine(nativeFolder);
final String command = usePowerShell ? null : commandLine.toString();
final Command commandLine = generateCommandLine(nativeFolder);
final String command = usePowerShell ? null : commandLine.commandLine.toString();
if (!usePowerShell && isWindows) {
if (command.length() > 8192) { // maximum length of the command in cmd
@ -509,7 +527,7 @@ public class DefaultLauncher extends Launcher {
writer.newLine();
writer.write('&');
for (String rawCommand : commandLine.asList()) {
for (String rawCommand : commandLine.commandLine.asList()) {
writer.write(' ');
writer.write(CommandBuilder.pwshString(rawCommand));
}
@ -533,6 +551,10 @@ public class DefaultLauncher extends Launcher {
writer.write("export " + entry.getKey() + "=" + entry.getValue());
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.newLine();
@ -551,6 +573,10 @@ public class DefaultLauncher extends Launcher {
writer.write("pause");
writer.newLine();
}
if (commandLine.tempNativeFolder != null) {
writer.write(new CommandBuilder().add("rm", commandLine.tempNativeFolder.toString()).toString());
writer.newLine();
}
}
}
if (!scriptFile.setExecutable(true))
@ -574,4 +600,14 @@ public class DefaultLauncher extends Launcher {
managedProcess.addRelatedThread(stderr);
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.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
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));
}
public static boolean isASCII(CharSequence cs) {
return US_ASCII_ENCODER.canEncode(cs);
}
/**
* 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 CharsetEncoder US_ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder();
}