Skip to content

Commit 0f72f81

Browse files
committed
[GR-40848] [GR-40714] Fix detection of the executable name and pyvenv.cfg support.
PullRequest: graalpython/2443
2 parents 3c022c5 + 681a39d commit 0f72f81

File tree

8 files changed

+191
-58
lines changed

8 files changed

+191
-58
lines changed

ci.jsonnet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "overlay": "bbd123566faf600786d8ea6dc6a16639f9f97bce" }
1+
{ "overlay": "f31469651b749c69dc177d1392d1c2dffa4e0b04" }

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

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
*/
2626
package com.oracle.graal.python.shell;
2727

28+
import java.io.BufferedReader;
2829
import java.io.EOFException;
2930
import java.io.File;
3031
import java.io.FileDescriptor;
3132
import java.io.FileOutputStream;
33+
import java.io.IOError;
3234
import java.io.IOException;
3335
import java.io.InputStream;
3436
import java.io.OutputStream;
@@ -73,8 +75,7 @@ public static void main(String[] args) {
7375

7476
private static final String LANGUAGE_ID = "python";
7577

76-
// provided by GraalVM bash launchers, ignored in native image mode
77-
protected static final String J_BASH_LAUNCHER_EXEC_NAME = System.getProperty("org.graalvm.launcher.executablename");
78+
private static final String J_PYENVCFG = "pyvenv.cfg";
7879

7980
private static long startupWallClockTime = -1;
8081
private static long startupNanoTime = -1;
@@ -101,6 +102,7 @@ public static void main(String[] args) {
101102
private boolean dontWriteBytecode = false;
102103
private String warnOptions = null;
103104
private String checkHashPycsMode = "default";
105+
private String execName;
104106

105107
boolean useASTInterpreter = false;
106108

@@ -398,18 +400,16 @@ private static void print(String string) {
398400
System.err.println(string);
399401
}
400402

401-
private static String getLauncherExecName() {
402-
if (ImageInfo.inImageCode()) {
403-
String binPathName = null;
404-
if (ProcessProperties.getArgumentVectorBlockSize() > 0) {
405-
binPathName = calculateProgramFullPath(ProcessProperties.getArgumentVectorProgramName());
406-
}
407-
if (binPathName != null) {
408-
return binPathName;
409-
}
410-
return ProcessProperties.getExecutableName();
403+
protected String getLauncherExecName() {
404+
if (execName != null) {
405+
return execName;
406+
}
407+
execName = getProgramName();
408+
if (execName == null) {
409+
return null;
411410
}
412-
return GraalPythonMain.J_BASH_LAUNCHER_EXEC_NAME;
411+
execName = calculateProgramFullPath(execName);
412+
return execName;
413413
}
414414

415415
/**
@@ -578,10 +578,21 @@ protected void launch(Builder contextBuilder) {
578578
if (executable != null) {
579579
contextBuilder.option("python.ExecutableList", executable);
580580
} else {
581-
contextBuilder.option("python.Executable", getExecutable());
581+
executable = getExecutable();
582+
contextBuilder.option("python.Executable", executable);
582583
// The unlikely separator is used because options need to be
583584
// strings. See PythonOptions.getExecutableList()
584585
contextBuilder.option("python.ExecutableList", String.join("🏆", getExecutableList()));
586+
// We try locating and loading options from pyvenv.cfg according to PEP405 as long as
587+
// the user did not explicitly pass some options that would be otherwise loaded from
588+
// pyvenv.cfg. Notable usage of this feature is GraalPython venvs which generate a
589+
// launcher script that passes those options explicitly without relying on pyvenv.cfg
590+
boolean tryVenvCfg = !hasContextOptionSetViaCommandLine("SysPrefix") &&
591+
!hasContextOptionSetViaCommandLine("PythonHome") &&
592+
System.getenv("GRAAL_PYTHONHOME") == null;
593+
if (tryVenvCfg) {
594+
findAndApplyVenvCfg(contextBuilder, executable);
595+
}
585596
}
586597

587598
// setting this to make sure our TopLevelExceptionHandler calls the excepthook
@@ -676,6 +687,50 @@ protected void launch(Builder contextBuilder) {
676687
System.exit(rc);
677688
}
678689

690+
private void findAndApplyVenvCfg(Builder contextBuilder, String executable) {
691+
Path binDir = Paths.get(executable).getParent();
692+
if (binDir == null) {
693+
return;
694+
}
695+
Path venvCfg = binDir.resolve(J_PYENVCFG);
696+
if (!Files.exists(venvCfg)) {
697+
Path binParent = binDir.getParent();
698+
if (binParent == null) {
699+
return;
700+
}
701+
venvCfg = binParent.resolve(J_PYENVCFG);
702+
if (!Files.exists(venvCfg)) {
703+
return;
704+
}
705+
}
706+
try (BufferedReader reader = Files.newBufferedReader(venvCfg)) {
707+
String line;
708+
while ((line = reader.readLine()) != null) {
709+
String[] parts = line.split("=", 2);
710+
if (parts.length != 2) {
711+
continue;
712+
}
713+
String name = parts[0].trim();
714+
if (name.equals("home")) {
715+
contextBuilder.option("python.PythonHome", parts[1].trim());
716+
String sysPrefix = null;
717+
try {
718+
sysPrefix = venvCfg.getParent().toAbsolutePath().toString();
719+
} catch (IOError | NullPointerException ex) {
720+
// NullPointerException covers the possible null result of getParent()
721+
warn("Could not set the sys.prefix according to the pyvenv.cfg file.");
722+
}
723+
if (sysPrefix != null) {
724+
contextBuilder.option("python.SysPrefix", sysPrefix);
725+
}
726+
break;
727+
}
728+
}
729+
} catch (IOException ex) {
730+
throw abort("Could not read the pyvenv.cfg file.", 66);
731+
}
732+
}
733+
679734
private static boolean matchesPythonOption(String arg, String key) {
680735
assert !key.startsWith("python.");
681736
return arg.startsWith("--python." + key) || arg.startsWith("--" + key);
@@ -698,6 +753,18 @@ private String getContextOptionIfSetViaCommandLine(String key) {
698753
return null;
699754
}
700755

756+
private boolean hasContextOptionSetViaCommandLine(String key) {
757+
if (System.getProperty("polyglot.python." + key) != null) {
758+
return System.getProperty("polyglot.python." + key) != null;
759+
}
760+
for (String f : givenArguments) {
761+
if (matchesPythonOption(f, key)) {
762+
return true;
763+
}
764+
}
765+
return false;
766+
}
767+
701768
private static void printFileNotFoundException(NoSuchFileException e) {
702769
String reason = e.getReason();
703770
if (reason == null) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,92 @@ public Object doIt(PFunction func,
499499
}
500500
}
501501

502+
@Builtin(name = "get_toolchain_tools_for_venv")
503+
@TypeSystemReference(PythonArithmeticTypes.class)
504+
@GenerateNodeFactory
505+
public abstract static class GetToolchainToolsForVenv extends PythonBuiltinNode {
506+
private static final class Tool {
507+
final String name;
508+
final boolean isVariableName;
509+
final Object[] targets;
510+
511+
public Tool(String name, boolean isVariableName, Object[] targets) {
512+
this.name = name;
513+
this.isVariableName = isVariableName;
514+
this.targets = targets;
515+
}
516+
517+
static Tool forVariable(String name, Object... targets) {
518+
return new Tool(name, true, targets);
519+
}
520+
521+
static Tool forBinary(String name, Object... targets) {
522+
return new Tool(name, true, targets);
523+
}
524+
}
525+
526+
static final Tool[] tools = new Tool[]{
527+
Tool.forVariable("AR", tsLiteral("ar")),
528+
Tool.forVariable("RANLIB", tsLiteral("ranlib")),
529+
Tool.forVariable("NM", tsLiteral("nm")),
530+
Tool.forVariable("LD", tsLiteral("ld.lld"), tsLiteral("ld"), tsLiteral("lld")),
531+
Tool.forVariable("CC", tsLiteral("clang"), tsLiteral("cc")),
532+
Tool.forVariable("CXX", tsLiteral("clang++"), tsLiteral("c++")),
533+
Tool.forBinary("llvm-as", tsLiteral("as")),
534+
Tool.forBinary("clang-cl", tsLiteral("cl")),
535+
Tool.forBinary("clang-cpp", tsLiteral("cpp")),
536+
};
537+
538+
@Specialization
539+
@TruffleBoundary
540+
protected Object getToolPath() {
541+
Env env = getContext().getEnv();
542+
LanguageInfo llvmInfo = env.getInternalLanguages().get(J_LLVM_LANGUAGE);
543+
Toolchain toolchain = env.lookup(llvmInfo, Toolchain.class);
544+
List<TruffleFile> toolchainPaths = toolchain.getPaths("PATH");
545+
EconomicMapStorage storage = EconomicMapStorage.create(tools.length);
546+
for (Tool tool : tools) {
547+
String path = null;
548+
if (tool.isVariableName) {
549+
TruffleFile toolPath = toolchain.getToolPath(tool.name);
550+
if (toolPath != null) {
551+
path = toolPath.getAbsoluteFile().getPath();
552+
}
553+
} else {
554+
for (TruffleFile toolchainPath : toolchainPaths) {
555+
LOGGER.finest(() -> " Testing path " + toolchainPath.getPath() + " for tool " + tool.name);
556+
TruffleFile pathToTest = toolchainPath.resolve(tool.name);
557+
if (pathToTest.exists()) {
558+
path = pathToTest.getAbsoluteFile().getPath();
559+
break;
560+
}
561+
}
562+
}
563+
if (path != null) {
564+
storage.putUncached(toTruffleStringUncached(path), factory().createTuple(tool.targets));
565+
} else {
566+
LOGGER.info("Could not locate tool " + tool.name);
567+
}
568+
}
569+
return factory().createDict(storage);
570+
}
571+
}
572+
573+
/*
574+
* Internal check used in tests only to check that we are running through managed launcher.
575+
*/
576+
@Builtin(name = "is_managed_launcher")
577+
@TypeSystemReference(PythonArithmeticTypes.class)
578+
@GenerateNodeFactory
579+
public abstract static class IsManagedLauncher extends PythonBuiltinNode {
580+
@Specialization
581+
@TruffleBoundary
582+
protected boolean isManaged() {
583+
// The best approximation for now
584+
return !getContext().getEnv().isNativeAccessAllowed() && getContext().getOption(PythonOptions.RunViaLauncher);
585+
}
586+
}
587+
502588
@Builtin(name = "get_toolchain_tool_path", minNumOfPositionalArgs = 1)
503589
@TypeSystemReference(PythonArithmeticTypes.class)
504590
@GenerateNodeFactory

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/ObjectHashMap.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ private boolean keysEqual(int[] originalIndices, ThreadState state, int index, O
833833
* smaller than the old size if there were many dummy entries. The rehashing also removes the
834834
* dummy entries.
835835
*/
836-
@TruffleBoundary(allowInlining = true)
836+
@TruffleBoundary
837837
private void rehashAndPut(Object newKey, long newKeyHash, Object newValue) {
838838
int requiredIndicesSize = usedHashes * GROWTH_RATE;
839839
// We need the hash table of this size, in order to accommodate "requiredIndicesSize" items
@@ -867,7 +867,7 @@ private void rehashAndPut(Object newKey, long newKeyHash, Object newValue) {
867867
insertNewKey(localIndices, newKey, newKeyHash, newValue);
868868
}
869869

870-
@TruffleBoundary(allowInlining = true)
870+
@TruffleBoundary
871871
private void compact() {
872872
// shuffle[X] will tell us by how much value X found in 'indices' should be shuffled to left
873873
int[] shuffle = new int[hashes.length];

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,20 +1551,22 @@ public void initializeHomeAndPrefixPaths(Env newEnv, String languageHome) {
15511551
"\n\tCAPI: {5}" +
15521552
"\n\tJNI library: {6}", languageHome, sysPrefix, basePrefix, coreHome, stdLibHome, capiHome, jniHome));
15531553

1554-
String envHome = null;
1555-
try {
1556-
envHome = System.getenv("GRAAL_PYTHONHOME");
1557-
} catch (SecurityException e) {
1554+
String pythonHome = newEnv.getOptions().get(PythonOptions.PythonHome);
1555+
if (pythonHome.isEmpty()) {
1556+
try {
1557+
pythonHome = System.getenv("GRAAL_PYTHONHOME");
1558+
} catch (SecurityException e) {
1559+
}
15581560
}
15591561

15601562
final TruffleFile home;
1561-
if (languageHome != null && envHome == null) {
1563+
if (languageHome != null && pythonHome == null) {
15621564
home = newEnv.getInternalTruffleFile(languageHome);
1563-
} else if (envHome != null) {
1565+
} else if (pythonHome != null) {
15641566
boolean envHomeIsDirectory = false;
15651567
TruffleFile envHomeFile = null;
15661568
try {
1567-
envHomeFile = newEnv.getInternalTruffleFile(envHome);
1569+
envHomeFile = newEnv.getInternalTruffleFile(pythonHome);
15681570
envHomeIsDirectory = envHomeFile.isDirectory();
15691571
} catch (SecurityException e) {
15701572
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ private PythonOptions() {
9292
// no instances
9393
}
9494

95+
@Option(category = OptionCategory.EXPERT, help = "Set the home of Python. Equivalent of GRAAL_PYTHONHOME env variable. " +
96+
"Determines default values for the CoreHome, StdLibHome, SysBasePrefix, SysPrefix.", usageSyntax = "<path>", stability = OptionStability.EXPERIMENTAL) //
97+
public static final OptionKey<String> PythonHome = new OptionKey<>("");
98+
9599
@Option(category = OptionCategory.USER, help = "Set the location of sys.prefix. Overrides any environment variables or Java options.", usageSyntax = "<path>", stability = OptionStability.STABLE) //
96100
public static final OptionKey<TruffleString> SysPrefix = new OptionKey<>(T_EMPTY_STRING, TS_OPTION_TYPE);
97101

graalpython/lib-python/3/venv/__init__.py

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -187,38 +187,12 @@ def create_if_needed(d):
187187

188188
def _install_compilers(self, context):
189189
"""Puts the Graal LLVM compiler tools on the path"""
190-
191-
# Table of well-known LLVM tools that must be queried by a variable name.
192-
llvm_tools = {
193-
"AR": ("ar",),
194-
"RANLIB": ("ranlib",),
195-
"NM": ("nm",),
196-
"LD": ("ld.lld", "ld", "lld"),
197-
"CC": ("clang", "cc"),
198-
"CXX": ("clang++", "c++"),
199-
}
200-
# Table of additional LLVM tools to use if they are available.
201-
_llvm_bins = {
202-
"llvm-as": ("as",),
203-
"clang-cl": ("cl",),
204-
"clang-cpp": ("cpp",),
205-
}
206190
bin_dir = os.path.join(context.env_dir, context.bin_name)
207-
def create_symlinks(table, resolver):
208-
for tool_var in table:
209-
tool_path = resolver(tool_var)
210-
if os.path.exists(tool_path):
211-
for name in table[tool_var]:
212-
dest = os.path.join(bin_dir, name)
213-
if not os.path.exists(dest):
214-
os.symlink(tool_path, dest)
215-
216-
create_symlinks(llvm_tools, __graalpython__.get_toolchain_tool_path)
217-
# NOTE: function 'get_toolcahin_paths' returns a tuple
218-
llvm_path = __graalpython__.get_toolchain_paths("PATH")
219-
if llvm_path and llvm_path[0]:
220-
create_symlinks(_llvm_bins, lambda binary_name: os.path.join(llvm_path[0], binary_name))
221-
191+
for (tool_path, names) in __graalpython__.get_toolchain_tools_for_venv().items():
192+
for name in names:
193+
dest = os.path.join(bin_dir, name)
194+
if not os.path.exists(dest):
195+
os.symlink(tool_path, dest)
222196

223197
def _patch_shebang(self, context):
224198
# Truffle change: we need to patch the pip/pip3 (and maybe other)

mx.graalpython/suite.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,23 @@
4444
},
4545
{
4646
"name": "tools",
47-
"version": "9a0570863be7d236519bceda4b05ee3e00a6c384",
47+
"version": "98fb946635802e01b8c892e4ecee44c48fb7ffa3",
4848
"subdir": True,
4949
"urls": [
5050
{"url": "https://github.com/oracle/graal", "kind": "git"},
5151
],
5252
},
5353
{
5454
"name": "sulong",
55-
"version": "9a0570863be7d236519bceda4b05ee3e00a6c384",
55+
"version": "98fb946635802e01b8c892e4ecee44c48fb7ffa3",
5656
"subdir": True,
5757
"urls": [
5858
{"url": "https://github.com/oracle/graal", "kind": "git"},
5959
]
6060
},
6161
{
6262
"name": "regex",
63-
"version": "9a0570863be7d236519bceda4b05ee3e00a6c384",
63+
"version": "98fb946635802e01b8c892e4ecee44c48fb7ffa3",
6464
"subdir": True,
6565
"urls": [
6666
{"url": "https://github.com/oracle/graal", "kind": "git"},

0 commit comments

Comments
 (0)