def test_linked_synapses(): ''' Test linking to a synaptic variable (should raise an error). ''' G = NeuronGroup(10, '') S = Synapses(G, G, 'w:1') S.connect() G2 = NeuronGroup(100, 'x : 1 (linked)') assert_raises(NotImplementedError, lambda: setattr(G2, 'x', linked_var(S, 'w')))
def test_linked_synapses(): ''' Test linking to a synaptic variable (should raise an error). ''' G = NeuronGroup(10, '') S = Synapses(G, G, 'w:1', connect=True) G2 = NeuronGroup(100, 'x : 1 (linked)') assert_raises(NotImplementedError, lambda: setattr(G2, 'x', linked_var(S, 'w')))
def test_linked_subexpression_synapse(): ''' Test a complicated setup (not unlikely when using brian hears) ''' G = NeuronGroup(2, 'dv/dt = 100*Hz : 1', threshold='v>1', reset='v=0') G.v = [0, .5] G2 = NeuronGroup(10, '''I = clip(x, 0, inf) : 1 x : 1 (linked) ''') # This will not be able to include references to `I` as `I_pre` etc., since # the indirect indexing would have to change depending on the synapses G2.x = linked_var(G.v, index=np.array([0, 1]).repeat(5)) S = Synapses(G2, G2, '') S.connect('i==j') assert 'I' not in S.variables assert 'I_pre' not in S.variables assert 'I_post' not in S.variables assert 'x' not in S.variables assert 'x_pre' not in S.variables assert 'x_post' not in S.variables
def build( self, project_dir='output', compile_project=True, run_project=False, debug=True, with_output=True, native=True, additional_source_files=None, additional_header_files=None, main_includes=None, run_includes=None, run_args=None, ): ''' Build the project TODO: more details Parameters ---------- project_dir : str The output directory to write the project to, any existing files will be overwritten. compile_project : bool Whether or not to attempt to compile the project using GNU make. run_project : bool Whether or not to attempt to run the built project if it successfully builds. debug : bool Whether to compile in debug mode. with_output : bool Whether or not to show the ``stdout`` of the built program when run. native : bool Whether or not to compile natively using the ``--march=native`` gcc option. additional_source_files : list of str A list of additional ``.cpp`` files to include in the build. additional_header_files : list of str A list of additional ``.h`` files to include in the build. main_includes : list of str A list of additional header files to include in ``main.cpp``. run_includes : list of str A list of additional header files to include in ``run.cpp``. ''' if additional_source_files is None: additional_source_files = [] if additional_header_files is None: additional_header_files = [] if main_includes is None: main_includes = [] if run_includes is None: run_includes = [] if run_args is None: run_args = [] self.project_dir = project_dir ensure_directory(project_dir) for d in ['code_objects', 'results', 'static_arrays']: ensure_directory(os.path.join(project_dir, d)) writer = CPPWriter(project_dir) logger.debug("Writing C++ standalone project to directory " + os.path.normpath(project_dir)) arange_arrays = sorted( [(var, start) for var, start in self.arange_arrays.iteritems()], key=lambda (var, start): var.name) # # Find np arrays in the namespaces and convert them into static # # arrays. Hopefully they are correctly used in the code: For example, # # this works for the namespaces for functions with C++ (e.g. TimedArray # # treats it as a C array) but does not work in places that are # # implicitly vectorized (state updaters, resets, etc.). But arrays # # shouldn't be used there anyway. for code_object in self.code_objects.itervalues(): for name, value in code_object.variables.iteritems(): if isinstance(value, np.ndarray): self.static_arrays[name] = value # write the static arrays logger.debug("static arrays: " + str(sorted(self.static_arrays.keys()))) static_array_specs = [] for name, arr in sorted(self.static_arrays.items()): arr.tofile(os.path.join(project_dir, 'static_arrays', name)) static_array_specs.append( (name, c_data_type(arr.dtype), arr.size, name)) # Write the global objects networks = [ net() for net in Network.__instances__() if net().name != '_fake_network' ] synapses = [S() for S in Synapses.__instances__()] arr_tmp = CPPStandaloneCodeObject.templater.objects( None, None, array_specs=self.arrays, dynamic_array_specs=self.dynamic_arrays, dynamic_array_2d_specs=self.dynamic_arrays_2d, zero_arrays=self.zero_arrays, arange_arrays=arange_arrays, synapses=synapses, clocks=self.clocks, static_array_specs=static_array_specs, networks=networks, ) writer.write('objects.*', arr_tmp) main_lines = [] procedures = [('', main_lines)] runfuncs = {} for func, args in self.main_queue: if func == 'run_code_object': codeobj, = args main_lines.append('_run_%s();' % codeobj.name) elif func == 'run_network': net, netcode = args main_lines.extend(netcode) elif func == 'set_by_array': arrayname, staticarrayname = args code = ''' for(int i=0; i<_num_{staticarrayname}; i++) {{ {arrayname}[i] = {staticarrayname}[i]; }} '''.format(arrayname=arrayname, staticarrayname=staticarrayname) main_lines.extend(code.split('\n')) elif func == 'set_array_by_array': arrayname, staticarrayname_index, staticarrayname_value = args code = ''' for(int i=0; i<_num_{staticarrayname_index}; i++) {{ {arrayname}[{staticarrayname_index}[i]] = {staticarrayname_value}[i]; }} '''.format(arrayname=arrayname, staticarrayname_index=staticarrayname_index, staticarrayname_value=staticarrayname_value) main_lines.extend(code.split('\n')) elif func == 'insert_code': main_lines.append(args) elif func == 'start_run_func': name, include_in_parent = args if include_in_parent: main_lines.append('%s();' % name) main_lines = [] procedures.append((name, main_lines)) elif func == 'end_run_func': name, include_in_parent = args name, main_lines = procedures.pop(-1) runfuncs[name] = main_lines name, main_lines = procedures[-1] else: raise NotImplementedError("Unknown main queue function type " + func) # generate the finalisations for codeobj in self.code_objects.itervalues(): if hasattr(codeobj.code, 'main_finalise'): main_lines.append(codeobj.code.main_finalise) # Generate data for non-constant values code_object_defs = defaultdict(list) for codeobj in self.code_objects.itervalues(): lines = [] for k, v in codeobj.variables.iteritems(): if isinstance(v, AttributeVariable): # We assume all attributes are implemented as property-like methods line = 'const {c_type} {varname} = {objname}.{attrname}();' lines.append( line.format(c_type=c_data_type(v.dtype), varname=k, objname=v.obj.name, attrname=v.attribute)) elif isinstance(v, ArrayVariable): try: if isinstance(v, DynamicArrayVariable): if v.dimensions == 1: dyn_array_name = self.dynamic_arrays[v] array_name = self.arrays[v] line = '{c_type}* const {array_name} = &{dyn_array_name}[0];' line = line.format( c_type=c_data_type(v.dtype), array_name=array_name, dyn_array_name=dyn_array_name) lines.append(line) line = 'const int _num{k} = {dyn_array_name}.size();' line = line.format( k=k, dyn_array_name=dyn_array_name) lines.append(line) else: lines.append('const int _num%s = %s;' % (k, v.size)) except TypeError: pass for line in lines: # Sometimes an array is referred to by to different keys in our # dictionary -- make sure to never add a line twice if not line in code_object_defs[codeobj.name]: code_object_defs[codeobj.name].append(line) # Generate the code objects for codeobj in self.code_objects.itervalues(): ns = codeobj.variables # TODO: fix these freeze/CONSTANTS hacks somehow - they work but not elegant. code = freeze(codeobj.code.cpp_file, ns) code = code.replace('%CONSTANTS%', '\n'.join(code_object_defs[codeobj.name])) code = '#include "objects.h"\n' + code writer.write('code_objects/' + codeobj.name + '.cpp', code) writer.write('code_objects/' + codeobj.name + '.h', codeobj.code.h_file) # The code_objects are passed in the right order to run them because they were # sorted by the Network object. To support multiple clocks we'll need to be # smarter about that. main_tmp = CPPStandaloneCodeObject.templater.main( None, None, main_lines=main_lines, code_objects=self.code_objects.values(), report_func=self.report_func, dt=float(defaultclock.dt), additional_headers=main_includes, ) writer.write('main.cpp', main_tmp) # Generate the run functions run_tmp = CPPStandaloneCodeObject.templater.run( None, None, run_funcs=runfuncs, code_objects=self.code_objects.values(), additional_headers=run_includes, ) writer.write('run.*', run_tmp) # Copy the brianlibdirectory brianlib_dir = os.path.join( os.path.split(inspect.getsourcefile(CPPStandaloneCodeObject))[0], 'brianlib') brianlib_files = copy_directory(brianlib_dir, os.path.join(project_dir, 'brianlib')) for file in brianlib_files: if file.lower().endswith('.cpp'): writer.source_files.append('brianlib/' + file) elif file.lower().endswith('.h'): writer.header_files.append('brianlib/' + file) # Copy the CSpikeQueue implementation spikequeue_h = os.path.join(project_dir, 'brianlib', 'spikequeue.h') shutil.copy2( os.path.join( os.path.split(inspect.getsourcefile(Synapses))[0], 'cspikequeue.cpp'), spikequeue_h) #writer.header_files.append(spikequeue_h) writer.source_files.extend(additional_source_files) writer.header_files.extend(additional_header_files) # Generate the makefile if os.name == 'nt': rm_cmd = 'del' else: rm_cmd = 'rm' makefile_tmp = CPPStandaloneCodeObject.templater.makefile( None, None, source_files=' '.join(writer.source_files), header_files=' '.join(writer.header_files), rm_cmd=rm_cmd) writer.write('makefile', makefile_tmp) # build the project if compile_project: with in_directory(project_dir): if debug: x = os.system('make debug') elif native: x = os.system('make native') else: x = os.system('make') if x == 0: if run_project: if not with_output: stdout = open(os.devnull, 'w') else: stdout = None if os.name == 'nt': x = subprocess.call(['main'] + run_args, stdout=stdout) else: x = subprocess.call(['./main'] + run_args, stdout=stdout) if x: raise RuntimeError("Project run failed") self.has_been_run = True else: raise RuntimeError("Project compilation failed")
def build(self, project_dir='output', compile_project=True, run_project=False, debug=True, with_output=True, native=True, additional_source_files=None, additional_header_files=None, main_includes=None, run_includes=None, run_args=None, ): ''' Build the project TODO: more details Parameters ---------- project_dir : str The output directory to write the project to, any existing files will be overwritten. compile_project : bool Whether or not to attempt to compile the project using GNU make. run_project : bool Whether or not to attempt to run the built project if it successfully builds. debug : bool Whether to compile in debug mode. with_output : bool Whether or not to show the ``stdout`` of the built program when run. native : bool Whether or not to compile natively using the ``--march=native`` gcc option. additional_source_files : list of str A list of additional ``.cpp`` files to include in the build. additional_header_files : list of str A list of additional ``.h`` files to include in the build. main_includes : list of str A list of additional header files to include in ``main.cpp``. run_includes : list of str A list of additional header files to include in ``run.cpp``. ''' if additional_source_files is None: additional_source_files = [] if additional_header_files is None: additional_header_files = [] if main_includes is None: main_includes = [] if run_includes is None: run_includes = [] if run_args is None: run_args = [] self.project_dir = project_dir ensure_directory(project_dir) for d in ['code_objects', 'results', 'static_arrays']: ensure_directory(os.path.join(project_dir, d)) writer = CPPWriter(project_dir) logger.debug("Writing C++ standalone project to directory "+os.path.normpath(project_dir)) arange_arrays = sorted([(var, start) for var, start in self.arange_arrays.iteritems()], key=lambda (var, start): var.name) # # Find np arrays in the namespaces and convert them into static # # arrays. Hopefully they are correctly used in the code: For example, # # this works for the namespaces for functions with C++ (e.g. TimedArray # # treats it as a C array) but does not work in places that are # # implicitly vectorized (state updaters, resets, etc.). But arrays # # shouldn't be used there anyway. for code_object in self.code_objects.itervalues(): for name, value in code_object.variables.iteritems(): if isinstance(value, np.ndarray): self.static_arrays[name] = value # write the static arrays logger.debug("static arrays: "+str(sorted(self.static_arrays.keys()))) static_array_specs = [] for name, arr in sorted(self.static_arrays.items()): arr.tofile(os.path.join(project_dir, 'static_arrays', name)) static_array_specs.append((name, c_data_type(arr.dtype), arr.size, name)) # Write the global objects networks = [net() for net in Network.__instances__() if net().name!='_fake_network'] synapses = [S() for S in Synapses.__instances__()] arr_tmp = CPPStandaloneCodeObject.templater.objects( None, None, array_specs=self.arrays, dynamic_array_specs=self.dynamic_arrays, dynamic_array_2d_specs=self.dynamic_arrays_2d, zero_arrays=self.zero_arrays, arange_arrays=arange_arrays, synapses=synapses, clocks=self.clocks, static_array_specs=static_array_specs, networks=networks, ) writer.write('objects.*', arr_tmp) main_lines = [] procedures = [('', main_lines)] runfuncs = {} for func, args in self.main_queue: if func=='run_code_object': codeobj, = args main_lines.append('_run_%s();' % codeobj.name) elif func=='run_network': net, netcode = args main_lines.extend(netcode) elif func=='set_by_array': arrayname, staticarrayname = args code = ''' for(int i=0; i<_num_{staticarrayname}; i++) {{ {arrayname}[i] = {staticarrayname}[i]; }} '''.format(arrayname=arrayname, staticarrayname=staticarrayname) main_lines.extend(code.split('\n')) elif func=='set_array_by_array': arrayname, staticarrayname_index, staticarrayname_value = args code = ''' for(int i=0; i<_num_{staticarrayname_index}; i++) {{ {arrayname}[{staticarrayname_index}[i]] = {staticarrayname_value}[i]; }} '''.format(arrayname=arrayname, staticarrayname_index=staticarrayname_index, staticarrayname_value=staticarrayname_value) main_lines.extend(code.split('\n')) elif func=='insert_code': main_lines.append(args) elif func=='start_run_func': name, include_in_parent = args if include_in_parent: main_lines.append('%s();' % name) main_lines = [] procedures.append((name, main_lines)) elif func=='end_run_func': name, include_in_parent = args name, main_lines = procedures.pop(-1) runfuncs[name] = main_lines name, main_lines = procedures[-1] else: raise NotImplementedError("Unknown main queue function type "+func) # generate the finalisations for codeobj in self.code_objects.itervalues(): if hasattr(codeobj.code, 'main_finalise'): main_lines.append(codeobj.code.main_finalise) # Generate data for non-constant values code_object_defs = defaultdict(list) for codeobj in self.code_objects.itervalues(): lines = [] for k, v in codeobj.variables.iteritems(): if isinstance(v, AttributeVariable): # We assume all attributes are implemented as property-like methods line = 'const {c_type} {varname} = {objname}.{attrname}();' lines.append(line.format(c_type=c_data_type(v.dtype), varname=k, objname=v.obj.name, attrname=v.attribute)) elif isinstance(v, ArrayVariable): try: if isinstance(v, DynamicArrayVariable): if v.dimensions == 1: dyn_array_name = self.dynamic_arrays[v] array_name = self.arrays[v] line = '{c_type}* const {array_name} = &{dyn_array_name}[0];' line = line.format(c_type=c_data_type(v.dtype), array_name=array_name, dyn_array_name=dyn_array_name) lines.append(line) line = 'const int _num{k} = {dyn_array_name}.size();' line = line.format(k=k, dyn_array_name=dyn_array_name) lines.append(line) else: lines.append('const int _num%s = %s;' % (k, v.size)) except TypeError: pass for line in lines: # Sometimes an array is referred to by to different keys in our # dictionary -- make sure to never add a line twice if not line in code_object_defs[codeobj.name]: code_object_defs[codeobj.name].append(line) # Generate the code objects for codeobj in self.code_objects.itervalues(): ns = codeobj.variables # TODO: fix these freeze/CONSTANTS hacks somehow - they work but not elegant. code = freeze(codeobj.code.cpp_file, ns) code = code.replace('%CONSTANTS%', '\n'.join(code_object_defs[codeobj.name])) code = '#include "objects.h"\n'+code writer.write('code_objects/'+codeobj.name+'.cpp', code) writer.write('code_objects/'+codeobj.name+'.h', codeobj.code.h_file) # The code_objects are passed in the right order to run them because they were # sorted by the Network object. To support multiple clocks we'll need to be # smarter about that. main_tmp = CPPStandaloneCodeObject.templater.main(None, None, main_lines=main_lines, code_objects=self.code_objects.values(), report_func=self.report_func, dt=float(defaultclock.dt), additional_headers=main_includes, ) writer.write('main.cpp', main_tmp) # Generate the run functions run_tmp = CPPStandaloneCodeObject.templater.run(None, None, run_funcs=runfuncs, code_objects=self.code_objects.values(), additional_headers=run_includes, ) writer.write('run.*', run_tmp) # Copy the brianlibdirectory brianlib_dir = os.path.join(os.path.split(inspect.getsourcefile(CPPStandaloneCodeObject))[0], 'brianlib') brianlib_files = copy_directory(brianlib_dir, os.path.join(project_dir, 'brianlib')) for file in brianlib_files: if file.lower().endswith('.cpp'): writer.source_files.append('brianlib/'+file) elif file.lower().endswith('.h'): writer.header_files.append('brianlib/'+file) # Copy the CSpikeQueue implementation spikequeue_h = os.path.join(project_dir, 'brianlib', 'spikequeue.h') shutil.copy2(os.path.join(os.path.split(inspect.getsourcefile(Synapses))[0], 'cspikequeue.cpp'), spikequeue_h) #writer.header_files.append(spikequeue_h) writer.source_files.extend(additional_source_files) writer.header_files.extend(additional_header_files) # Generate the makefile if os.name=='nt': rm_cmd = 'del' else: rm_cmd = 'rm' makefile_tmp = CPPStandaloneCodeObject.templater.makefile(None, None, source_files=' '.join(writer.source_files), header_files=' '.join(writer.header_files), rm_cmd=rm_cmd) writer.write('makefile', makefile_tmp) # build the project if compile_project: with in_directory(project_dir): if debug: x = os.system('make debug') elif native: x = os.system('make native') else: x = os.system('make') if x==0: if run_project: if not with_output: stdout = open(os.devnull, 'w') else: stdout = None if os.name=='nt': x = subprocess.call(['main'] + run_args, stdout=stdout) else: x = subprocess.call(['./main'] + run_args, stdout=stdout) if x: raise RuntimeError("Project run failed") self.has_been_run = True else: raise RuntimeError("Project compilation failed")
def build(self, project_dir='output', compile_project=True, run_project=False, debug=True, with_output=True): ensure_directory(project_dir) for d in ['code_objects', 'results', 'static_arrays']: ensure_directory(os.path.join(project_dir, d)) logger.debug("Writing C++ standalone project to directory "+os.path.normpath(project_dir)) # # Find numpy arrays in the namespaces and convert them into static # # arrays. Hopefully they are correctly used in the code: For example, # # this works for the namespaces for functions with C++ (e.g. TimedArray # # treats it as a C array) but does not work in places that are # # implicitly vectorized (state updaters, resets, etc.). But arrays # # shouldn't be used there anyway. for code_object in self.code_objects.itervalues(): for name, value in code_object.variables.iteritems(): if isinstance(value, numpy.ndarray): self.static_arrays[name] = value # write the static arrays logger.debug("static arrays: "+str(sorted(self.static_arrays.keys()))) static_array_specs = [] for name, arr in self.static_arrays.iteritems(): arr.tofile(os.path.join(project_dir, 'static_arrays', name)) static_array_specs.append((name, c_data_type(arr.dtype), arr.size, name)) # Write the global objects networks = [net() for net in Network.__instances__() if net().name!='_fake_network'] synapses = [S() for S in Synapses.__instances__()] arr_tmp = CPPStandaloneCodeObject.templater.objects(None, array_specs=self.arrays, dynamic_array_specs=self.dynamic_arrays, dynamic_array_2d_specs=self.dynamic_arrays_2d, zero_arrays=self.zero_arrays, arange_arrays=self.arange_arrays, synapses=synapses, clocks=self.clocks, static_array_specs=static_array_specs, networks=networks, ) logger.debug("objects: "+str(arr_tmp)) open(os.path.join(project_dir, 'objects.cpp'), 'w').write(arr_tmp.cpp_file) open(os.path.join(project_dir, 'objects.h'), 'w').write(arr_tmp.h_file) main_lines = [] for func, args in self.main_queue: if func=='run_code_object': codeobj, = args main_lines.append('_run_%s(t);' % codeobj.name) elif func=='run_network': net, netcode = args main_lines.extend(netcode) elif func=='set_by_array': arrayname, staticarrayname = args code = ''' for(int i=0; i<_num_{staticarrayname}; i++) {{ {arrayname}[i] = {staticarrayname}[i]; }} '''.format(arrayname=arrayname, staticarrayname=staticarrayname) main_lines.extend(code.split('\n')) elif func=='set_array_by_array': arrayname, staticarrayname_index, staticarrayname_value = args code = ''' for(int i=0; i<_num_{staticarrayname_index}; i++) {{ {arrayname}[{staticarrayname_index}[i]] = {staticarrayname_value}[i]; }} '''.format(arrayname=arrayname, staticarrayname_index=staticarrayname_index, staticarrayname_value=staticarrayname_value) main_lines.extend(code.split('\n')) elif func=='insert_code': main_lines.append(args) else: raise NotImplementedError("Unknown main queue function type "+func) # generate the finalisations for codeobj in self.code_objects.itervalues(): if hasattr(codeobj.code, 'main_finalise'): main_lines.append(codeobj.code.main_finalise) # Generate data for non-constant values handled_arrays = defaultdict(set) code_object_defs = defaultdict(list) for codeobj in self.code_objects.itervalues(): for k, v in codeobj.variables.iteritems(): if k=='t': pass elif isinstance(v, Subexpression): pass elif isinstance(v, AttributeVariable): c_type = c_data_type(v.dtype) # TODO: Handle dt in the correct way if v.attribute == 'dt_': code = ('const {c_type} {k} = ' '{value};').format(c_type=c_type, k=k, value=v.get_value()) else: code = ('const {c_type} {k} = ' '{name}.{attribute};').format(c_type=c_type, k=k, name=v.obj.name, attribute=v.attribute) code_object_defs[codeobj.name].append(code) elif isinstance(v, ArrayVariable): try: if isinstance(v, DynamicArrayVariable): if v.dimensions == 1: dyn_array_name = self.dynamic_arrays[v] array_name = self.arrays[v] code_object_defs[codeobj.name].append('{c_type}* const {array_name} = &{dyn_array_name}[0];'.format(c_type=c_data_type(v.dtype), array_name=array_name, dyn_array_name=dyn_array_name)) code_object_defs[codeobj.name].append('const int _num{k} = {dyn_array_name}.size();'.format(k=k, dyn_array_name=dyn_array_name)) else: code_object_defs[codeobj.name].append('const int _num%s = %s;' % (k, v.size)) except TypeError: pass # Generate the code objects for codeobj in self.code_objects.itervalues(): ns = codeobj.variables # TODO: fix these freeze/CONSTANTS hacks somehow - they work but not elegant. code = freeze(codeobj.code.cpp_file, ns) code = code.replace('%CONSTANTS%', '\n'.join(code_object_defs[codeobj.name])) code = '#include "objects.h"\n'+code open(os.path.join(project_dir, 'code_objects', codeobj.name+'.cpp'), 'w').write(code) open(os.path.join(project_dir, 'code_objects', codeobj.name+'.h'), 'w').write(codeobj.code.h_file) # The code_objects are passed in the right order to run them because they were # sorted by the Network object. To support multiple clocks we'll need to be # smarter about that. main_tmp = CPPStandaloneCodeObject.templater.main(None, main_lines=main_lines, code_objects=self.code_objects.values(), dt=float(defaultclock.dt), ) logger.debug("main: "+str(main_tmp)) open(os.path.join(project_dir, 'main.cpp'), 'w').write(main_tmp) # Copy the brianlibdirectory brianlib_dir = os.path.join(os.path.split(inspect.getsourcefile(CPPStandaloneCodeObject))[0], 'brianlib') copy_directory(brianlib_dir, os.path.join(project_dir, 'brianlib')) # Copy the CSpikeQueue implementation shutil.copy(os.path.join(os.path.split(inspect.getsourcefile(Synapses))[0], 'cspikequeue.cpp'), os.path.join(project_dir, 'brianlib', 'spikequeue.h')) # build the project if compile_project: with in_directory(project_dir): if debug: x = os.system('g++ -I. -g *.cpp code_objects/*.cpp brianlib/*.cpp -o main') else: x = os.system('g++ -I. -O3 -ffast-math -march=native *.cpp code_objects/*.cpp brianlib/*.cpp -o main') if x==0: if run_project: if not with_output: stdout = open(os.devnull, 'w') else: stdout = None if os.name=='nt': x = subprocess.call('main', stdout=stdout) else: x = subprocess.call('./main', stdout=stdout) if x: raise RuntimeError("Project run failed") else: raise RuntimeError("Project compilation failed")