Skip to content

Commit 437be67

Browse files
committed
Fix GH #471 - Windows executable name missing extension when launched with full path
1 parent cbfde86 commit 437be67

File tree

2 files changed

+39
-21
lines changed

2 files changed

+39
-21
lines changed

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonMain.java

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2024, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2025, Oracle and/or its affiliates.
33
* Copyright (c) 2013, Regents of the University of California
44
*
55
* All rights reserved.
@@ -47,6 +47,7 @@
4747
import java.util.Set;
4848
import java.util.UUID;
4949
import java.util.function.Function;
50+
import java.util.function.Predicate;
5051

5152
import org.graalvm.launcher.AbstractLanguageLauncher;
5253
import org.graalvm.nativeimage.ImageInfo;
@@ -530,11 +531,17 @@ private String getLauncherExecName() {
530531
* {@link #getEnv(String)} is used to retrieve $PATH.
531532
* @return The absolute path to the program or {@code null}.
532533
*/
533-
private String calculateProgramFullPath(String program, Function<Path, Boolean> isExecutable, String envPath) {
534+
private String calculateProgramFullPath(String program, Predicate<Path> isExecutable, String envPath) {
534535
Path programPath = Paths.get(program);
535536

536537
// If this is an absolute path, we are already fine.
537538
if (programPath.isAbsolute()) {
539+
if (IS_WINDOWS) {
540+
String resolvedProgramNameWithExtension = getProgramNameWithExtension(isExecutable, program);
541+
if (resolvedProgramNameWithExtension != null) {
542+
return resolvedProgramNameWithExtension;
543+
}
544+
}
538545
return program;
539546
}
540547

@@ -553,26 +560,15 @@ private String calculateProgramFullPath(String program, Function<Path, Boolean>
553560
i = path.indexOf(File.pathSeparatorChar, previous);
554561
int end = i == -1 ? path.length() : i;
555562
Path resolvedProgramName = Paths.get(path.substring(previous, end)).resolve(programPath);
556-
if (isExecutable.apply(resolvedProgramName)) {
563+
if (isExecutable.test(resolvedProgramName)) {
557564
return resolvedProgramName.toString();
558565
}
559566

560567
// On windows, the program name may be without the extension
561568
if (IS_WINDOWS) {
562-
String pathExtEnvvar = getEnv("PATHEXT");
563-
if (pathExtEnvvar != null) {
564-
// default extensions are defined
565-
String resolvedStr = resolvedProgramName.toString();
566-
if (resolvedStr.length() <= 3 || resolvedStr.charAt(resolvedStr.length() - 4) != '.') {
567-
// program has no file extension
568-
String[] pathExts = pathExtEnvvar.toLowerCase().split(";");
569-
for (String pathExt : pathExts) {
570-
resolvedProgramName = Path.of(resolvedStr + pathExt);
571-
if (isExecutable.apply(resolvedProgramName)) {
572-
return resolvedProgramName.toString();
573-
}
574-
}
575-
}
569+
String resolvedProgramNameWithExtension = getProgramNameWithExtension(isExecutable, resolvedProgramName.toString());
570+
if (resolvedProgramNameWithExtension != null) {
571+
return resolvedProgramNameWithExtension;
576572
}
577573
}
578574

@@ -598,6 +594,28 @@ private String calculateProgramFullPath(String program, Function<Path, Boolean>
598594
return programPath.toAbsolutePath().toString();
599595
}
600596

597+
private static String getProgramNameWithExtension(Predicate<Path> isExecutable, String programStr) {
598+
if (isExecutable.test(Path.of(programStr))) {
599+
return programStr;
600+
}
601+
if (programStr.length() <= 3 || programStr.charAt(programStr.length() - 4) != '.') {
602+
// program has no file extension
603+
Path programNameWithExtension = null;
604+
String pathExtEnvvar = getEnv("PATHEXT");
605+
if (pathExtEnvvar != null) {
606+
// default extensions are defined
607+
String[] pathExts = pathExtEnvvar.toLowerCase().split(";");
608+
for (String pathExt : pathExts) {
609+
programNameWithExtension = Path.of(programStr + pathExt);
610+
if (isExecutable.test(programNameWithExtension)) {
611+
return programNameWithExtension.toString();
612+
}
613+
}
614+
}
615+
}
616+
return null;
617+
}
618+
601619
private String[] getExecutableList() {
602620
String launcherExecName = getLauncherExecName();
603621
if (launcherExecName != null) {

graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/shell/TestPathResolution.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -43,7 +43,7 @@
4343
import java.lang.reflect.Method;
4444
import java.nio.file.Path;
4545
import java.nio.file.Paths;
46-
import java.util.function.Function;
46+
import java.util.function.Predicate;
4747

4848
import org.junit.Assert;
4949
import org.junit.Before;
@@ -57,11 +57,11 @@ public class TestPathResolution {
5757
@Before
5858
public void setup() throws NoSuchMethodException {
5959
// Use reflection to avoid exposing the method in public API
60-
calculateProgramFullPathMethod = GraalPythonMain.class.getDeclaredMethod("calculateProgramFullPath", String.class, Function.class, String.class);
60+
calculateProgramFullPathMethod = GraalPythonMain.class.getDeclaredMethod("calculateProgramFullPath", String.class, Predicate.class, String.class);
6161
calculateProgramFullPathMethod.setAccessible(true);
6262
}
6363

64-
public String calculateProgramFullPath(String executable, Function<Path, Boolean> isExecutable, String path) {
64+
public String calculateProgramFullPath(String executable, Predicate<Path> isExecutable, String path) {
6565
try {
6666
return (String) calculateProgramFullPathMethod.invoke(new GraalPythonMain(), executable, isExecutable, path);
6767
} catch (Throwable e) {

0 commit comments

Comments
 (0)