3030"""
3131import os
3232import re
33+ import tempfile
3334
34- from easybuild .easyblocks .generic .configuremake import ConfigureMake
3535from easybuild .easyblocks .generic .cmakemake import CMakeMake
3636from easybuild .easyblocks .generic .pythonpackage import det_pylibdir
3737from easybuild .framework .easyconfig import CUSTOM
38+ from easybuild .tools import LooseVersion
3839from easybuild .tools .build_log import EasyBuildError
3940from easybuild .tools .config import build_option
41+ from easybuild .tools .filetools import write_file
4042from easybuild .tools .modules import get_software_root
4143from easybuild .tools .run import run_shell_cmd
4244from easybuild .tools .systemtools import get_shared_lib_ext
4345
44- from easybuild .tools import LooseVersion
45-
4646
4747class EB_NEURON (CMakeMake ):
4848 """Support for building/installing NEURON."""
4949
50- def __init__ (self , * args , ** kwargs ):
51- """Initialisation of custom class variables for NEURON."""
52- super (EB_NEURON , self ).__init__ (* args , ** kwargs )
53-
54- self .hostcpu = 'UNKNOWN'
55- self .with_python = False
56- self .pylibdir = 'UNKNOWN'
57-
5850 @staticmethod
5951 def extra_options ():
6052 """Custom easyconfig parameters for NEURON."""
61-
6253 extra_vars = {
6354 'paranrn' : [True , "Enable support for distributed simulations." , CUSTOM ],
6455 }
6556 return CMakeMake .extra_options (extra_vars )
6657
67- def configure_step (self ):
68- """Custom configuration procedure for NEURON."""
69- if LooseVersion (self .version ) < LooseVersion ('7.8.1' ):
70-
71- # make sure we're using the correct configure command
72- # (required because custom easyconfig parameters from CMakeMake are picked up)
73- self .cfg ['configure_cmd' ] = "./configure"
74-
75- # enable support for distributed simulations if desired
76- if self .cfg ['paranrn' ]:
77- self .cfg .update ('configopts' , '--with-paranrn' )
78-
79- # specify path to InterViews if it is available as a dependency
80- interviews_root = get_software_root ('InterViews' )
81- if interviews_root :
82- self .cfg .update ('configopts' , "--with-iv=%s" % interviews_root )
83- else :
84- self .cfg .update ('configopts' , "--without-iv" )
85-
86- # optionally enable support for Python as alternative interpreter
87- python_root = get_software_root ('Python' )
88- if python_root :
89- self .with_python = True
90- self .cfg .update ('configopts' , "--with-nrnpython=%s/bin/python" % python_root )
58+ def __init__ (self , * args , ** kwargs ):
59+ """Initialisation of custom class variables for NEURON."""
60+ super (EB_NEURON , self ).__init__ (* args , ** kwargs )
9161
92- # determine host CPU type
93- cmd = "./config.guess"
94- res = run_shell_cmd (cmd )
62+ self .python_root = None
63+ self .pylibdir = 'UNKNOWN'
9564
96- self .hostcpu = res .output .split ('\n ' )[0 ].split ('-' )[0 ]
97- self .log .debug ("Determined host CPU type as %s" % self .hostcpu )
65+ def prepare_step (self , * args , ** kwargs ):
66+ """Custom prepare step with python detection"""
67+ super (EB_NEURON , self ).prepare_step (* args , ** kwargs )
9868
99- # determine Python lib dir
100- self .pylibdir = det_pylibdir ()
69+ self .python_root = get_software_root ('Python' )
10170
102- # complete configuration with configure_method of parent
103- ConfigureMake .configure_step (self )
71+ def configure_step (self ):
72+ """Custom configuration procedure for NEURON."""
73+ # enable support for distributed simulations if desired
74+ if self .cfg ['paranrn' ]:
75+ self .cfg .update ('configopts' , '-DNRN_ENABLE_MPI=ON' )
10476 else :
105- # enable support for distributed simulations if desired
106- if self .cfg ['paranrn' ]:
107- self .cfg .update ('configopts' , '-DNRN_ENABLE_MPI=ON' )
108- else :
109- self .cfg .update ('configopts' , '-DNRN_ENABLE_MPI=OFF' )
110-
111- # specify path to InterViews if it is available as a dependency
112- interviews_root = get_software_root ('InterViews' )
113- if interviews_root :
114- self .cfg .update ('configopts' , "-DIV_DIR=%s -DNRN_ENABLE_INTERVIEWS=ON" % interviews_root )
115- else :
116- self .cfg .update ('configopts' , "-DNRN_ENABLE_INTERVIEWS=OFF" )
117-
118- # no longer used it seems
119- self .hostcpu = ''
120-
121- # optionally enable support for Python as alternative interpreter
122- python_root = get_software_root ('Python' )
123- if python_root :
124- self .with_python = True
125- self .cfg .update ('configopts' , "-DNRN_ENABLE_PYTHON=ON -DPYTHON_EXECUTABLE=%s/bin/python" % python_root )
126- self .cfg .update ('configopts' , "-DNRN_ENABLE_MODULE_INSTALL=ON "
127- "-DNRN_MODULE_INSTALL_OPTIONS='--prefix=%s'" % self .installdir )
128- else :
129- self .cfg .update ('configopts' , "-DNRN_ENABLE_PYTHON=OFF" )
130-
131- # determine Python lib dir
132- self .pylibdir = det_pylibdir ()
133-
134- # complete configuration with configure_method of parent
135- CMakeMake .configure_step (self )
77+ self .cfg .update ('configopts' , '-DNRN_ENABLE_MPI=OFF' )
13678
137- def install_step (self ):
138- """Custom install procedure for NEURON."""
139-
140- super (EB_NEURON , self ).install_step ()
141-
142- # with the CMakeMake, the python module is installed automatically
143- if LooseVersion (self .version ) < LooseVersion ('7.8.1' ):
144- if self .with_python :
145- pypath = os .path .join ('src' , 'nrnpython' )
146- try :
147- pwd = os .getcwd ()
148- os .chdir (pypath )
149- except OSError as err :
150- raise EasyBuildError ("Failed to change to %s: %s" , pypath , err )
151-
152- cmd = "python setup.py install --prefix=%s" % self .installdir
153- run_shell_cmd (cmd )
154-
155- try :
156- os .chdir (pwd )
157- except OSError as err :
158- raise EasyBuildError ("Failed to change back to %s: %s" , pwd , err )
159-
160- def sanity_check_step (self ):
161- """Custom sanity check for NEURON."""
162- shlib_ext = get_shared_lib_ext ()
163- binpath = os .path .join (self .hostcpu , 'bin' )
164- libpath = os .path .join (self .hostcpu , 'lib' , 'lib%s.' + shlib_ext )
165- # hoc_ed is not included in the sources of 7.4. However, it is included in the binary distribution.
166- # Nevertheless, the binary has a date old enough (June 2014, instead of November 2015 like all the
167- # others) to be considered a mistake in the distribution
168- binaries = ["neurondemo" , "nrngui" , "nrniv" , "nrnivmodl" , "nocmodl" , "modlunit" , "nrnmech_makefile" ,
169- "mkthreadsafe" ]
170- libs = ["nrniv" ]
171- sanity_check_dirs = ['share/nrn' ]
172-
173- if LooseVersion (self .version ) < LooseVersion ('7.4' ):
174- binaries += ["hoc_ed" ]
175-
176- if LooseVersion (self .version ) < LooseVersion ('7.8.1' ):
177- binaries += ["bbswork.sh" , "hel2mos1.sh" , "ivoc" , "memacs" , "mos2nrn" , "mos2nrn2.sh" , "oc" ]
178- binaries += ["nrn%s" % x for x in ["iv_makefile" , "oc" , "oc_makefile" , "ocmodl" ]]
179- libs += ["ivoc" , "ivos" , "memacs" , "meschach" , "neuron_gnu" , "nrnmpi" , "nrnoc" , "nrnpython" ,
180- "oc" , "ocxt" , "scopmath" , "sparse13" , "sundials" ]
181- sanity_check_dirs += ['include/nrn' ]
182- # list of included binaries changed with cmake. See
183- # https://github.com/neuronsimulator/nrn/issues/899
79+ # specify path to InterViews if it is available as a dependency
80+ interviews_root = get_software_root ('InterViews' )
81+ if interviews_root :
82+ self .cfg .update ('configopts' , f"-DIV_DIR={ interviews_root } -DNRN_ENABLE_INTERVIEWS=ON" )
18483 else :
185- binaries += ["nrnpyenv.sh" , "set_nrnpyenv.sh" , "sortspike" ]
186- libs += ["rxdmath" ]
187- sanity_check_dirs += ['include' ]
188- if self .with_python :
189- sanity_check_dirs += [os .path .join ("lib" , "python" ),
190- os .path .join ("lib" , "python%(pyshortver)s" , "site-packages" )]
191-
192- # this is relevant for installations of Python packages for multiple Python versions (via multi_deps)
193- # (we can not pass this via custom_paths, since then the %(pyshortver)s template value will not be resolved)
194- # ensure that we only add to paths specified in the EasyConfig
195- sanity_check_files = [os .path .join (binpath , x ) for x in binaries ] + [libpath % x for x in libs ]
196- self .cfg ['sanity_check_paths' ] = {
197- 'files' : sanity_check_files ,
198- 'dirs' : sanity_check_dirs ,
199- }
84+ self .cfg .update ('configopts' , "-DNRN_ENABLE_INTERVIEWS=OFF" )
85+
86+ # optionally enable support for Python as alternative interpreter
87+ if self .python_root :
88+ python_cfgopts = " " .join ([
89+ "-DNRN_ENABLE_PYTHON=ON" ,
90+ f"-DPYTHON_EXECUTABLE={ self .python_root } /bin/python" ,
91+ "-DNRN_ENABLE_MODULE_INSTALL=ON" ,
92+ f"-DNRN_MODULE_INSTALL_OPTIONS='--prefix={ self .installdir } '" ,
93+ ])
94+ self .cfg .update ('configopts' , python_cfgopts )
95+ else :
96+ self .cfg .update ('configopts' , "-DNRN_ENABLE_PYTHON=OFF" )
20097
201- super (EB_NEURON , self ).sanity_check_step ()
98+ # determine Python lib dir
99+ self .pylibdir = det_pylibdir ()
202100
203- try :
204- fake_mod_data = self .load_fake_module ()
205- except EasyBuildError as err :
206- self .log .debug ("Loading fake module failed: %s" % err )
207-
208- # test NEURON demo
209- inp = '\n ' .join ([
210- "demo(3) // load the pyramidal cell model." ,
211- "init() // initialise the model" ,
212- "t // should be zero" ,
213- "soma.v // will print -65" ,
214- "run() // run the simulation" ,
215- "t // should be 5, indicating that 5ms were simulated" ,
216- "soma.v // will print a value other than -65, indicating that the simulation was executed" ,
217- "quit()" ,
218- ])
219- res = run_shell_cmd ("neurondemo" , stdin = inp , fail_on_error = False )
220-
221- validate_regexp = re .compile (r"^\s+-65\s*\n\s+5\s*\n\s+-68.134337" , re .M )
222- if res .exit_code or not validate_regexp .search (res .output ):
223- raise EasyBuildError ("Validation of NEURON demo run failed." )
224- else :
225- self .log .info ("Validation of NEURON demo OK!" )
101+ # complete configuration with configure_method of parent
102+ CMakeMake .configure_step (self )
226103
104+ def test_step (self ):
105+ """Custom tests for NEURON."""
227106 if build_option ('mpi_tests' ):
228107 nproc = self .cfg ['parallel' ]
229108 try :
230- cwd = os .getcwd ()
231- os .chdir (os .path .join (self .cfg ['start_dir' ], 'src' , 'parallel' ))
232-
233- cmd = self .toolchain .mpi_cmd_for ("nrniv -mpi test0.hoc" , nproc )
234- res = run_shell_cmd (cmd , fail_on_error = False )
235-
236- os .chdir (cwd )
109+ hoc_file = os .path .join (self .cfg ['start_dir' ], 'src' , 'parallel' , 'test0.hoc' )
110+ cmd = self .toolchain .mpi_cmd_for (f"bin/nrniv -mpi { hoc_file } " , nproc )
111+ res = run_shell_cmd (cmd )
237112 except OSError as err :
238113 raise EasyBuildError ("Failed to run parallel hello world: %s" , err )
239114
240115 valid = True
241116 for i in range (0 , nproc ):
242- validate_regexp = re .compile ("I am %d of %d" % ( i , nproc ) )
117+ validate_regexp = re .compile (f "I am { i :d } of { nproc :d } " )
243118 if not validate_regexp .search (res .output ):
244119 valid = False
245120 break
246121 if res .exit_code or not valid :
247122 raise EasyBuildError ("Validation of parallel hello world run failed." )
248- else :
249- self .log .info ("Parallel hello world OK!" )
123+ self .log .info ("Parallel hello world OK!" )
250124 else :
251125 self .log .info ("Skipping MPI testing of NEURON since MPI testing is disabled" )
252126
253- if self .with_python :
254- cmd = "python -c 'import neuron; neuron.test()'"
255- run_shell_cmd (cmd )
127+ def sanity_check_step (self ):
128+ """Custom sanity check for NEURON."""
129+ shlib_ext = get_shared_lib_ext ()
130+
131+ binaries = ["mkthreadsafe" , "modlunit" , "neurondemo" , "nocmodl" , "nrngui" , "nrniv" , "nrnivmodl" ,
132+ "nrnmech_makefile" , "nrnpyenv.sh" , "set_nrnpyenv.sh" , "sortspike" ]
133+ libs = ["nrniv" , "rxdmath" ]
134+ sanity_check_dirs = ['include' , 'share/nrn' ]
256135
257- # cleanup
258- self .clean_up_fake_module (fake_mod_data )
136+ if self .python_root :
137+ sanity_check_dirs += [os .path .join ("lib" , "python" )]
138+ if LooseVersion (self .version ) < LooseVersion ('8' ):
139+ sanity_check_dirs += [os .path .join ("lib" , "python%(pyshortver)s" , "site-packages" )]
259140
260- def make_module_req_guess (self ):
261- """Custom guesses for environment variables (PATH, ...) for NEURON."""
141+ # this is relevant for installations of Python packages for multiple Python versions (via multi_deps)
142+ # (we can not pass this via custom_paths, since then the %(pyshortver)s template value will not be resolved)
143+ # ensure that we only add to paths specified in the EasyConfig
144+ sanity_check_files = [os .path .join ('bin' , x ) for x in binaries ]
145+ sanity_check_files += [f'lib/lib{ soname } .{ shlib_ext } ' for soname in libs ]
262146
263- guesses = super (EB_NEURON , self ).make_module_req_guess ()
147+ custom_paths = {
148+ 'files' : sanity_check_files ,
149+ 'dirs' : sanity_check_dirs ,
150+ }
264151
265- guesses .update ({
266- 'PATH' : [os .path .join (self .hostcpu , 'bin' )],
267- })
152+ # run NEURON demo
153+ demo_dir = tempfile .mkdtemp ()
154+ demo_inp_file = os .path .join (demo_dir , 'neurondemo.inp' )
155+ demo_inp_cmds = '\n ' .join ([
156+ "demo(3) // load the pyramidal cell model." ,
157+ "init() // initialise the model" ,
158+ "t // should be zero" ,
159+ "soma.v // will print -65" ,
160+ "run() // run the simulation" ,
161+ "t // should be 5, indicating that 5ms were simulated" ,
162+ "soma.v // will print a value other than -65, indicating that the simulation was executed" ,
163+ "quit()" ,
164+ ])
165+ write_file (demo_inp_file , demo_inp_cmds )
166+
167+ demo_sanity_cmd = f"neurondemo < { demo_inp_file } 2>&1"
168+ demo_regexp_version = f'NEURON -- VERSION { self .version } '
169+ demo_regexp_soma = '68.134337'
170+
171+ custom_commands = [
172+ f"{ demo_sanity_cmd } | grep -c '{ demo_regexp_version } '" ,
173+ f"{ demo_sanity_cmd } | grep -c '{ demo_regexp_soma } '" ,
174+ ]
175+
176+ if self .python_root :
177+ custom_commands .append ("python -c 'import neuron; neuron.test()'" )
178+
179+ super (EB_NEURON , self ).sanity_check_step (custom_paths = custom_paths , custom_commands = custom_commands )
180+
181+ def make_module_step (self , * args , ** kwargs ):
182+ """
183+ Custom paths of NEURON module load environment
184+ """
185+ if self .python_root :
186+ # location of neuron python package
187+ if LooseVersion (self .version ) < LooseVersion ('8' ):
188+ self .module_load_environment .PYTHONPATH = [os .path .join ("lib" , "python*" , "site-packages" )]
189+ else :
190+ self .module_load_environment .PYTHONPATH = [os .path .join ('lib' , 'python' )]
268191
269- return guesses
192+ return super ( EB_NEURON , self ). make_module_step ( * args , ** kwargs )
270193
271194 def make_module_extra (self ):
272195 """Define extra module entries required."""
@@ -279,8 +202,8 @@ def make_module_extra(self):
279202 val = os .getenv (var )
280203 if val :
281204 txt += self .module_generator .set_environment (var , val )
282- self .log .debug ("%s set to %s , adding it to module" % ( var , val ) )
205+ self .log .debug (f" { var } set to { val } , adding it to module" )
283206 else :
284- self .log .debug ("%s not set: %s" % ( var , os .environ .get (var , None )) )
207+ self .log .debug (f" { var } not set: { os .environ .get (var , None )} " )
285208
286209 return txt
0 commit comments