Skip to content

Commit 9005b05

Browse files
committed
[GR-66394] Add MacOS launcher
PullRequest: graalpython/3979
2 parents cee059b + 338b68e commit 9005b05

File tree

3 files changed

+295
-1
lines changed

3 files changed

+295
-1
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def create_configuration(self, context):
252252
args = ' '.join(args)
253253
f.write(f'command = {sys.executable} -m venv {args}\n')
254254
# Truffle change: setup our a launcher by adding the path to the creating executable
255-
if os.name == 'nt':
255+
if (os.name == 'nt' or sys.platform == 'darwin'):
256256
f.write('venvlauncher_command = %s\n' % (__graalpython__.venvlauncher_command or sys.executable))
257257
# End of Truffle change
258258

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
#include <assert.h>
42+
#include <ctype.h>
43+
#include <stdio.h>
44+
#include <stdlib.h>
45+
#include <unistd.h>
46+
#include <libgen.h>
47+
#include <string.h>
48+
#include <limits.h>
49+
#include <stdarg.h>
50+
#include <mach-o/dyld.h>
51+
52+
#define GRAAL_PYTHON_EXE_ARG "--python.Executable="
53+
#define GRAAL_PYTHON_BASE_EXE_ARG "--python.VenvlauncherCommand="
54+
55+
static bool debug_enabled = false;
56+
57+
void debug(const char *format, ...) {
58+
if (!debug_enabled) return;
59+
60+
va_list va;
61+
char buffer[PATH_MAX * 2];
62+
va_start(va, format);
63+
int result = vsnprintf(buffer, sizeof(buffer), format, va);
64+
va_end(va);
65+
66+
if (result <= 0) return;
67+
68+
fprintf(stderr, "%s", buffer);
69+
fflush(stderr);
70+
}
71+
72+
73+
/**
74+
* Reads the 'venvlauncher_command' from a pyvenv.cfg file.
75+
* Returns a newly allocated string. Caller must free() it.
76+
*/
77+
char *get_pyenvcfg_command(const char *pyenv_cfg_path) {
78+
FILE *fp = fopen(pyenv_cfg_path, "r");
79+
if (fp == NULL) {
80+
fprintf(stderr, "Failed to open pyenv.cfg file!\n");
81+
exit(1);
82+
}
83+
84+
char *current_line = NULL;
85+
size_t current_line_size = 0;
86+
const char key[] = "venvlauncher_command";
87+
size_t key_len = sizeof(key) - 1;
88+
89+
while (getline(&current_line, &current_line_size, fp) != -1) {
90+
char *p = current_line;
91+
92+
while (isspace((unsigned char) *p)) p++;
93+
94+
if (strncmp(p, key, key_len) == 0) {
95+
p += key_len;
96+
97+
// Skip spaces and '='
98+
while (isspace((unsigned char) *p)) p++;
99+
if (*p == '=') {
100+
p++;
101+
} else {
102+
fprintf(stderr, "venv command is not in correct format. Expected '=' after the venvlauncher_command");
103+
free(current_line);
104+
fclose(fp);
105+
exit(1);
106+
}
107+
while (isspace((unsigned char) *p)) p++;
108+
if (*p == '\"') {
109+
char *end = p + strlen(p);
110+
while (end > p && (isspace((unsigned char) end[-1]) || end[-1] == '\n')) {
111+
*--end = '\0';
112+
}
113+
if (end <= p + 1 || end[-1] != '\"') {
114+
fprintf(stderr, "venv command is not in correct format");
115+
free(current_line);
116+
fclose(fp);
117+
exit(1);
118+
}
119+
p++;
120+
end[-1] = '\0';
121+
}
122+
123+
char *result = strdup(p);
124+
free(current_line);
125+
fclose(fp);
126+
return result;
127+
128+
}
129+
}
130+
131+
free(current_line);
132+
if (ferror(fp)) {
133+
perror("getline failed");
134+
fclose(fp);
135+
exit(1);
136+
}
137+
138+
fclose(fp);
139+
fprintf(stderr, "venvlauncher_command not found in pyenv.cfg file");
140+
exit(1);
141+
}
142+
143+
int count_args(const char *cmd) {
144+
char *copy = strdup(cmd);
145+
int count = 0;
146+
char *token = strtok(copy, " ");
147+
while (token) {
148+
count++;
149+
token = strtok(NULL, " ");
150+
}
151+
152+
free(copy);
153+
return count;
154+
}
155+
156+
char **split_venv_command_into_args(const char *venv_command, int *argc_out) {
157+
158+
char *copy = strdup(venv_command);
159+
const int capacity = count_args(copy);
160+
char **args = malloc(capacity * sizeof(char *));
161+
if (!args) {
162+
fprintf(stderr, "allocation failed\n");
163+
free(copy);
164+
exit(1);
165+
}
166+
167+
int count = 0;
168+
char *current_token = strtok(copy, " ");
169+
while (current_token) {
170+
args[count++] = strdup(current_token);
171+
current_token = strtok(NULL, " ");
172+
}
173+
174+
free(copy);
175+
assert(capacity == count);
176+
*argc_out = count;
177+
return args;
178+
}
179+
180+
void find_pyvenv(char *pyvenv_cfg_path, size_t path_size) {
181+
char executable_path[PATH_MAX];
182+
uint32_t executable_path_size = sizeof(executable_path);
183+
184+
// Get current executable path (macOS)
185+
if (_NSGetExecutablePath(executable_path, &executable_path_size) != 0) {
186+
fprintf(stderr, "Failed to get executable path\n");
187+
exit(1);
188+
}
189+
190+
// First try search for the pyvenv on top level
191+
char *dir_path = dirname(executable_path);
192+
snprintf(pyvenv_cfg_path, path_size, "%s/pyvenv.cfg", dir_path);
193+
debug("Searching for pyenv.cfg file in %s\n", pyvenv_cfg_path);
194+
if (access(pyvenv_cfg_path, F_OK) != 0) {
195+
// Try searching one level up
196+
dir_path = dirname(dir_path);
197+
snprintf(pyvenv_cfg_path, path_size, "%s/pyvenv.cfg", dir_path);
198+
debug("Searching for pyenv.cfg file in %s\n", pyvenv_cfg_path);
199+
if (access(pyvenv_cfg_path, F_OK) != 0) {
200+
fprintf(stderr, "Error: pyvenv.cfg file not found at %s\n", pyvenv_cfg_path);
201+
exit(1);
202+
}
203+
}
204+
}
205+
206+
int main(int argc, char *argv[]) {
207+
if (getenv("PYLAUNCHER_DEBUG") != NULL) {
208+
debug_enabled = true;
209+
}
210+
debug("Original argv are:\n");
211+
for (int i = 1; i < argc; i++) {
212+
debug("argv[%d] = %s\n", i, argv[i]);
213+
}
214+
215+
char pyvenv_cfg_path[PATH_MAX];
216+
find_pyvenv(pyvenv_cfg_path, sizeof(pyvenv_cfg_path));
217+
char *venv_command = get_pyenvcfg_command(pyvenv_cfg_path);
218+
219+
int venv_argc = 0;
220+
char **venv_args = split_venv_command_into_args(venv_command, &venv_argc);
221+
222+
// Adds "--python.VenvlauncherCommand="
223+
// + 2 for quotes
224+
// + 1 for '\0'
225+
size_t python_base_exec_size = strlen(venv_command) + strlen(GRAAL_PYTHON_BASE_EXE_ARG) + 2 + 1;
226+
char python_base_exec_command[python_base_exec_size];
227+
snprintf(python_base_exec_command, python_base_exec_size, "%s\"%s\"", GRAAL_PYTHON_BASE_EXE_ARG, venv_command);
228+
229+
// Adds "--python.Executable="
230+
size_t python_exec_arg_size = strlen(GRAAL_PYTHON_EXE_ARG) + strlen(argv[0]) + 1;
231+
char python_exec_command[python_exec_arg_size];
232+
snprintf(python_exec_command,
233+
python_exec_arg_size,
234+
"%s%s",
235+
GRAAL_PYTHON_EXE_ARG,
236+
argv[0]);
237+
238+
// venv_args + "--python.VenvlauncherCommand=" + Adds "--python.Executable=" + rest of argc (-1 because we are not interested in argv[0]) + NULL
239+
size_t args_size = venv_argc + 2 + (argc - 1) + 1;
240+
char *args[args_size];
241+
242+
// copy venv_args
243+
int k = 0;
244+
for (k = 0; k < venv_argc; k++) {
245+
args[k] = venv_args[k];
246+
}
247+
248+
args[k++] = python_base_exec_command;
249+
args[k++] = python_exec_command;
250+
251+
// copy rest of the args
252+
for (int i = 1; i < argc; i++) {
253+
args[k++] = argv[i];
254+
}
255+
256+
args[k] = NULL;
257+
258+
debug("Final arguments to execv: \n");
259+
for (int i = 0; i < k; i++) {
260+
debug("arg[%d] = %s\n", i, args[i]);
261+
}
262+
263+
execv(args[0], args);
264+
perror("execv failed"); // only runs if execv fails
265+
return 1;
266+
}

mx.graalpython/suite.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,24 @@
519519
},
520520
},
521521

522+
"python-macos-launcher": {
523+
"subDir": "graalpython",
524+
"native": "executable",
525+
"deliverable": "macos-venvlauncher",
526+
"os_arch": {
527+
"darwin": {
528+
"<others>": {
529+
"defaultBuild": True,
530+
},
531+
},
532+
"<others>": {
533+
"<others>": {
534+
"defaultBuild": False,
535+
},
536+
},
537+
},
538+
},
539+
522540
"python-libbz2": {
523541
"subDir": "graalpython",
524542
"class": "CMakeNinjaProject",
@@ -1250,6 +1268,16 @@
12501268
},
12511269
},
12521270
},
1271+
"darwin": {
1272+
"<others>": {
1273+
"layout": {
1274+
"./META-INF/resources/<os>/<arch>/lib/graalpy<graal_ver:major_minor>/": [
1275+
"dependency:GRAALPYTHON_NATIVE_LIBS/<os>/<arch>/*",
1276+
],
1277+
"./META-INF/resources/<os>/<arch>/lib/python<py_ver:major_minor>/venv/scripts/macos/graalpy": "dependency:python-macos-launcher",
1278+
}
1279+
}
1280+
},
12531281
"<others>": {
12541282
"<others>": {
12551283
"layout": {

0 commit comments

Comments
 (0)