NEURON
nrnpy.cpp
Go to the documentation of this file.
1 #include <../../nrnconf.h>
2 // For Linux and Max OS X,
3 // Solve the problem of not knowing what version of Python the user has by
4 // possibly deferring linking to libnrnpython.so to run time using the proper
5 // Python interface
6 
7 #include <../nrnpython/nrnpython_config.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <InterViews/resource.h>
11 #include "nrnoc2iv.h"
12 #include "classreg.h"
13 #include "nonvintblock.h"
14 #include "nrnmpi.h"
15 
16 #include <algorithm>
17 #include <cctype>
18 
19 extern int nrn_nopython;
20 extern int nrnpy_nositeflag;
21 extern char* nrnpy_pyexe;
22 extern int nrn_is_python_extension;
24 extern int (*p_nrnpython_start)(int);
25 void nrnpython();
26 static void (*p_nrnpython_real)();
28 extern "C" char* hoc_back2forward(char* s);
29 char* hoc_forward2back(char* s);
30 #if DARWIN
31 extern void nrn_possible_mismatched_arch(const char*);
32 #endif
33 
34 // following is undefined or else has the value of sys.api_version
35 // at time of configure (using the python first in the PATH).
36 #if defined(NRNPYTHON_DYNAMICLOAD)
37 
38 #ifdef MINGW
39 #define RTLD_NOW 0
40 #define RTLD_GLOBAL 0
41 #define RTLD_NOLOAD 0
42 extern "C" {
43 extern void* dlopen_noerr(const char* name, int mode);
44 #define dlopen dlopen_noerr
45 extern void* dlsym(void* handle, const char* name);
46 extern int dlclose(void* handle);
47 extern char* dlerror();
48 }
49 #else
50 //#define _GNU_SOURCE
51 #include <dlfcn.h>
52 #endif
53 
54 extern char* neuron_home;
55 
56 #if NRNPYTHON_DYNAMICLOAD >= 30
57 
58 #ifdef MINGW
59 static const char* ver[] = {"3.6", 0};
60 #else
61 static const char* ver[] = {"3.9", "3.8", "3.7", "3.6", 0};
62 #endif // !MINGW
63 
64 #else
65 
66 static const char* ver[] = {0};
67 
68 #endif
69 
70 static int iver; // which python is loaded?
71 static void* python_already_loaded();
72 static void* load_python();
73 static void load_nrnpython(int, const char*);
74 #else //!defined(NRNPYTHON_DYNAMICLOAD)
75 extern "C" int nrnpython_start(int);
76 extern "C" void nrnpython_reg_real();
77 extern "C" void nrnpython_real();
78 #endif //defined(NRNPYTHON_DYNAMICLOAD)
79 
81 
82 void nrnpython() {
83 #if USE_PYTHON
84  if (p_nrnpython_real) {
85  (*p_nrnpython_real)();
86  return;
87  }
88 #endif
89  hoc_retpushx(0.);
90 }
91 
92 // Stub class for when Python does not exist
93 static void* p_cons(Object*) {
94  return 0;
95 }
96 static void p_destruct(void* v) {
97 }
98 static Member_func p_members[] = {0,0};
99 
100 #if NRNPYTHON_DYNAMICLOAD
101 static char* nrnpy_pylib;
102 
103 static void siteprob(void) {
105 printf("Py_Initialize exited. PYTHONHOME probably needs to be set correctly.\n");
106  if(nrnpy_pyhome) {
107 printf("The value of PYTHONHOME or our automatic guess based on the output of nrnpyenv.sh:\n export PYTHONHOME=%s\ndid not work.\n", nrnpy_pyhome);
108  }
109 printf("It will help to examine the output of:\nnrnpyenv.sh\n\
110 and set the indicated environment variables, or avoid python by adding\n\
111 nopython: on\n\
112 to %s/lib/nrn.defaults (or .nrn.defaults in your $HOME directory)\n",
113 neuron_home);
114  }
115 }
116 
117 static void set_nrnpylib() {
118  nrnpy_pylib = getenv("NRN_PYLIB");
119  nrnpy_pyhome = getenv("NRN_PYTHONHOME");
120  if (nrnpy_pylib && nrnpy_pyhome) { return; }
121  // copy allows free of the copy if needed
122  if (nrnpy_pylib) { nrnpy_pylib = strdup(nrnpy_pylib); }
123  if (nrnpy_pyhome) { nrnpy_pyhome = strdup(nrnpy_pyhome); }
124 
125  if (nrnmpi_myid_world == 0) {
126  int linesz = 1024 + (nrnpy_pyexe ? strlen(nrnpy_pyexe) : 0);
127  #ifdef MINGW
128  linesz += 3*strlen(neuron_home);
129  char* line = new char[linesz+1];
130  char* bnrnhome = strdup(neuron_home);
131  char* fnrnhome = strdup(neuron_home);
132  hoc_forward2back(bnrnhome);
133  hoc_back2forward(fnrnhome);
134  sprintf(line, "%s\\mingw\\usr\\bin\\bash %s/bin/nrnpyenv.sh %s --NEURON_HOME=%s",
135  bnrnhome,
136  fnrnhome,
137  (nrnpy_pyexe && strlen(nrnpy_pyexe) > 0) ? nrnpy_pyexe : "",
138  fnrnhome);
139  free(fnrnhome);
140  free(bnrnhome);
141  #else
142  char* line = new char[linesz+1];
143  sprintf(line, "bash %s/../../bin/nrnpyenv.sh %s",
144  neuron_home,
145  (nrnpy_pyexe && strlen(nrnpy_pyexe) > 0) ? nrnpy_pyexe : "");
146  #endif
147  FILE* p = popen(line, "r");
148  if (!p) {
149  printf("could not popen '%s'\n", line);
150  }else{
151  if (!fgets(line, linesz, p)) {
152  printf("failed: %s\n", line);
153  }
154  while(fgets(line, linesz, p)) {
155  char* cp;
156  // must get rid of beginning '"' and trailing '"\n'
157  if (!nrnpy_pyhome && (cp = strstr(line, "export NRN_PYTHONHOME="))) {
158  cp += strlen("export NRN_PYTHONHOME=") + 1;
159  cp[strlen(cp) - 2] = '\0';
160  if (nrnpy_pyhome) { free(nrnpy_pyhome); }
161  nrnpy_pyhome = strdup(cp);
162  }else if (!nrnpy_pylib && (cp = strstr(line, "export NRN_PYLIB="))) {
163  cp += strlen("export NRN_PYLIB=") + 1;
164  cp[strlen(cp) - 2] = '\0';
165  if (nrnpy_pylib) { free(nrnpy_pylib); }
166  nrnpy_pylib = strdup(cp);
167  }
168  }
169  pclose(p);
170  }
171  delete [] line;
172  }
173 #if NRNMPI
174  if (nrnmpi_numprocs_world > 1) { // 0 broadcasts to everyone else.
175  nrnmpi_char_broadcast_world(&nrnpy_pylib, 0);
176  nrnmpi_char_broadcast_world(&nrnpy_pyhome, 0);
177  }
178 #endif
179 }
180 
181 #if 0
182 static void set_pythonhome(void* handle){
183  if (nrnmpi_myid == 0) {atexit(siteprob);}
184 #ifdef MINGW
185 #else
186  if (getenv("PYTHONHOME") || nrnpy_nositeflag) { return; }
187  if (nrnpy_pyhome) {
188  int res = setenv("PYTHONHOME", nrnpy_pyhome, 1);
189  assert(res == 0);
190  return;
191  }
192 
194  void* s = dlsym(handle, "Py_Initialize");
195  assert(s != NULL);
196  int success = dladdr(s, &dl_info);
197  if (success) {
198  //printf("%s\n", dl_info.dli_fname);
199  nrnpy_pyhome = strdup(dl_info.dli_fname);
200  char* p = nrnpy_pyhome;
201  int n = strlen(p);
202  int seen = 0;
203  for (int i = n-1; i > 0; --i) {
204  if (p[i] == '/') {
205  if (++seen >= 2) {
206  p[i] = '\0' ;
207  break;
208  }
209  }
210  }
211  int res = setenv("PYTHONHOME", p, 1);
212  assert(res == 0);
213  }
214 #endif
215 }
216 #endif // if 0
217 #endif
218 
220  //printf("nrnpython_reg in nrnpy.cpp\n");
221 #if USE_PYTHON
222  if (nrn_nopython) {
223  p_nrnpython_start = 0;
224  p_nrnpython_real = 0;
226  }else{
227 #if NRNPYTHON_DYNAMICLOAD
228  void* handle = NULL;
229 
231  // As last resort (or for python3) load $NRN_PYLIB
232  set_nrnpylib();
233  //printf("nrnpy_pylib %s\n", nrnpy_pylib);
234  //printf("nrnpy_pyhome %s\n", nrnpy_pyhome);
235  if (nrnpy_pylib) {
236  handle = dlopen(nrnpy_pylib, RTLD_NOW|RTLD_GLOBAL);
237  if (!handle) {
238  fprintf(stderr, "Could not dlopen NRN_PYLIB: %s\n", nrnpy_pylib);
239 #if DARWIN
240  nrn_possible_mismatched_arch(nrnpy_pylib);
241 #endif
242  exit(1);
243  }
244  }
245  if (!handle) { python_already_loaded();}
246  if (!handle) { // embed python
247  handle = load_python();
248  }
249 #if 0
250  // No longer do this as Py_SetPythonHome is used
251  if (handle) {
252  // need to worry about the site.py problem
253  // can fix with a proper PYTHONHOME but need to know
254  // what path was used to load the python library.
255  set_pythonhome(handle);
256  }
257 #endif
258  }else{
259  //printf("nrn_is_python_extension = %d\n", nrn_is_python_extension);
260  }
261  // for some mysterious reason on max osx 10.12
262  // (perhaps due to System Integrity Protection?) when python is
263  // launched, python_already_loaded() returns a NULL handle unless
264  // the full path to the dylib is used. Since we know it is loaded
265  // in these circumstances, it is sufficient to go ahead and dlopen
266  // the nrnpython interface library
267  if (handle || nrn_is_python_extension) {
268  load_nrnpython(nrn_is_python_extension, nrnpy_pylib);
269  }
270 #else
274 #endif
275  }
276  if (p_nrnpython_reg_real) {
277  (*p_nrnpython_reg_real)();
278  if (nrnpy_site_problem_p) {
280  }
281  return;
282  }
283 #endif
284  class2oc("PythonObject", p_cons, p_destruct, p_members, NULL, NULL, NULL);
285 }
286 
287 #if NRNPYTHON_DYNAMICLOAD // to end of file
288 
289 // important dlopen flags :
290 // RTLD_NOLOAD returns NULL if not open, or handle if it is resident.
291 
292 static void* ver_dlo(int flag) {
293  for (int i = 0; ver[i]; ++i) {
294  char name[100];
295 #ifdef MINGW
296  sprintf(name, "python%c%c.dll", ver[i][0], ver[i][2]);
297 #else
298 #if DARWIN
299  sprintf(name, "libpython%s.dylib", ver[i]);
300 #else
301  sprintf(name, "libpython%s.so", ver[i]);
302 #endif
303 #endif
304  void* handle = dlopen(name, flag);
305  iver = i;
306  if (handle) {
307  return handle;
308  }
309  }
310  iver = -1;
311  return NULL;
312 }
313 
314 static void* python_already_loaded() {
315  void* handle = ver_dlo(RTLD_NOW|RTLD_GLOBAL|RTLD_NOLOAD);
316  //printf("python_already_loaded %d\n", iver);
317  return handle;
318 }
319 
320 static void* load_python() {
321  void* handle = ver_dlo(RTLD_NOW|RTLD_GLOBAL);
322  //printf("load_python %d\n", iver);
323  return handle;
324 }
325 
326 static void* load_sym(void* handle, const char* name) {
327  void* p = dlsym(handle, name);
328  if (!p) {
329  printf("Could not load %s\n", name);
330  exit(1);
331  }
332  return p;
333 }
334 
335 static void* load_nrnpython_helper(const char* npylib) {
336  char name[2048];
337 #ifdef MINGW
338  sprintf(name, "%s.dll", npylib);
339 #else // !MINGW
340 #if DARWIN
341  sprintf(name, "%s/../../lib/%s.dylib", neuron_home, npylib);
342 #else // !DARWIN
343  sprintf(name, "%s/../../lib/%s.so", neuron_home, npylib);
344 #endif // DARWIN
345 #endif // MINGW
346  void* handle = dlopen(name, RTLD_NOW);
347  return handle;
348 }
349 
350 // Get python version as integer from pythonlib path
351 static int pylib2pyver10(std::string pylib) {
352  //skip past last \ or /
353  const auto pos = pylib.find_last_of("/\\");
354  if (pos != std::string::npos) {
355  pylib = pylib.substr(pos + 1);
356  }
357 
358  // erase nondigits
359  pylib.erase(
360  std::remove_if(pylib.begin(),
361  pylib.end(),
362  [](char c) {
363  return !std::isdigit(c);
364  }), pylib.end());
365 
366  // parse number. 0 is fine to return as error (no need for stoi)
367  return std::atoi(pylib.c_str());
368 }
369 
370 static void load_nrnpython(int pyver10, const char* pylib) {
371  void* handle = NULL;
372 #if (defined(__MINGW32__) || (defined(USE_LIBNRNPYTHON_MAJORMINOR) && USE_LIBNRNPYTHON_MAJORMINOR == 1))
373  char name[256];
374  int pv10 = pyver10;
375  if (pyver10 < 1 && pylib) {
376  pv10 = pylib2pyver10(pylib);
377  }
378  sprintf(name, "libnrnpython%d", pv10);
379  handle = load_nrnpython_helper(name);
380  if (!handle) {
381  printf("Could not load %s\n", name);
382  printf("pyver10=%d pylib=%s\n", pyver10, pylib ? pylib : "NULL");
383  return;
384  }
385 #else
386  handle = load_nrnpython_helper("libnrnpython3");
387  if (!handle) {
388  printf("Could not load libnrnpython3\n");
389  printf("pyver10=%d pylib=%s\n", pyver10, pylib ? pylib : "NULL");
390  return;
391  }
392 #endif
393  p_nrnpython_start = (int(*)(int))load_sym(handle, "nrnpython_start");
394  p_nrnpython_real = (void(*)())load_sym(handle, "nrnpython_real");
395  p_nrnpython_reg_real = (void(*)())load_sym(handle, "nrnpython_reg_real");
396 }
397 
398 #endif
char * nrnpy_pyexe
char * strstr(cs, ct) char *cs
#define assert(ex)
Definition: hocassrt.h:26
pclose(FILE *p)
Definition: macprt.cpp:107
char * hoc_back2forward(char *s)
Definition: mswinprt.cpp:193
const char * dlerror(void)
Definition: osxdlfcn.cpp:174
int nrnmpi_numprocs_world
int dladdr(const void *dl_restrict, Dl_info *dl_restrict)
void
char * hoc_forward2back(char *s)
Definition: mswinprt.cpp:184
size_t p
#define v
Definition: md1redef.h:4
void nrnpython_reg()
Definition: nrnpy.cpp:219
char * nrnpy_pyhome
Definition: nrnpy.cpp:80
sprintf(buf," if (secondorder) {\ " int _i;\" " for(_i=0;_i< %d;++_i) {\" " _p[_slist%d[_i]]+=dt *_p[_dlist%d[_i]];\" " }}\", numeqn, listnum, listnum)
int dlclose(void *handle)
Definition: osxdlfcn.cpp:178
#define RTLD_NOW
Definition: osxdlfcn.h:61
int const size_t const size_t n
Definition: nrngsl.h:12
int nrnmpi_myid_world
_CONST char * s
Definition: system.cpp:74
void class2oc(const char *, void *(*cons)(Object *), void(*destruct)(void *), Member_func *, int(*checkpoint)(void **), Member_ret_obj_func *, Member_ret_str_func *)
Definition: hoc_oop.cpp:1581
FILE * popen(char *s1, char *s2)
Definition: macprt.cpp:102
#define printf
Definition: mwprefix.h:26
int(* p_nrnpython_start)(int)
int
Definition: nrnmusic.cpp:71
const char * dli_fname
Definition: osxdlfcn.h:48
int * nrnpy_site_problem_p
Definition: nrnpy.cpp:23
hoc_retpushx(1.0)
char * getenv(const char *s)
Definition: macprt.cpp:67
const char * neuron_home
Definition: hoc_init.cpp:268
static void(* p_nrnpython_real)()
Definition: nrnpy.cpp:26
static Member_func p_members[]
Definition: nrnpy.cpp:98
fprintf(stderr, "Don't know the location of params at %p\, pp)
void nrnpython()
Definition: nrnpy.cpp:82
int nrn_nopython
void * dlopen(const char *path, int mode)
Definition: osxdlfcn.cpp:85
char * name
Definition: init.cpp:16
static void(* p_nrnpython_reg_real)()
Definition: nrnpy.cpp:27
int nrnpy_nositeflag
Definition: ivocmain.cpp:193
int nrnpython_start(int)
defined(NRNPYTHON_DYNAMICLOAD)
Definition: nrnpython.cpp:140
static void * p_cons(Object *)
Definition: nrnpy.cpp:93
int nrnmpi_myid
#define RTLD_NOLOAD
Definition: osxdlfcn.h:64
Definition: hocdec.h:226
#define i
Definition: md1redef.h:12
#define c
void nrnpython_reg_real()
Definition: nrnpy_p2h.cpp:133
static char line[MAXLINE]
Definition: ivecop.c:35
int nrn_is_python_extension
Definition: fileio.cpp:871
void nrnpython_real()
Definition: nrnpython.cpp:217
void * dlsym(void *handle, const char *symbol)
Definition: osxdlfcn.cpp:193
static void p_destruct(void *v)
Definition: nrnpy.cpp:96
return NULL
Definition: cabcode.cpp:461
static char * ver[10]
Definition: nrnversion.cpp:16
#define RTLD_GLOBAL
Definition: osxdlfcn.h:63