def restart(self): """Restart the napari application in a detached process.""" process = QProcess() process.setProgram(sys.executable) if not running_as_bundled_app(): process.setArguments(sys.argv) process.startDetached() self.close(quit_app=True)
def launch(project_path, command): """ Handle launching commands from projects. """ logger.debug(str((project_path, command))) command = command.format(PREFIX=project_path) command = command.replace('\\', '/') if os.name == 'nt': command = command.replace('/bin', '/Scripts') if '.ipynb' in command: filename = command.replace('ipython notebook ', '') filename = filename.replace('jupyter notebook ', '') run_notebook(project_path=project_path, filename=filename) elif command.startswith('python '): filename = command.replace('python ', '') run_python_file(project_path, filename=filename) else: proc = QProcess() logger.debug(command) proc.startDetached(command)
class RunPopup(QDialog): def __init__(self, solver, parent): super(RunPopup, self).__init__(parent) self.commandline_option_exe = solver if solver else None self.mfix_available = False self.mfix_exe_cache = {} self.solver_list = {} self.template_values = {} self.cmdline = [] # List of strings self.parent = parent self.project = parent.project self.settings = parent.settings self.project_dir = parent.get_project_dir() self.gui_comments = self.project.mfix_gui_comments self.flag_processes = {} self.title = 'Run Solver' # load ui ui = self.ui = get_ui('run_popup.ui', self) ui.layout.setSizeConstraint(ui.layout.SetFixedSize) ui.toolbutton_browse.clicked.connect(self.handle_browse_exe) ui.toolbutton_browse.setIcon(get_icon('add.svg')) ui.toolbutton_browse.setIconSize(sub_icon_size()) ui.toolbutton_remove.clicked.connect(self.handle_remove_exe) ui.toolbutton_remove.setIcon(get_icon('remove.svg')) ui.toolbutton_remove.setIconSize(sub_icon_size()) ui.toolbutton_view_error.clicked.connect(self.show_solver_error) ui.listwidget_solver_list.itemSelectionChanged.connect( self.update_dialog_options) ui.combobox_restart.addItems(RESTART_TYPES.keys()) ui.combobox_restart.hide() ui.button_run.clicked.connect(self.handle_run) ui.button_cancel.clicked.connect(self.handle_abort) ui.pushbutton_browse_template.clicked.connect( self.handle_browse_template) n_cpus = multiprocessing.cpu_count() ui.groupbox_smp_options.setTitle("SMP Options (%s available locally)" % plural(n_cpus, "core")) ui.groupbox_queue.toggled.connect(self.toggle_run_btn_text) ui.widget_queue_options.hide() self.initialize_ui() self.init_templates() @property def solver(self): """The currently selected solver""" item = self.ui.listwidget_solver_list.currentItem() if item is None: solver = None else: solver = item.text() if not solver: solver = None return solver def toggle_run_btn_text(self): ui = self.ui ui.button_run.setText( 'Submit' if ui.groupbox_queue.isChecked() else 'Run') def update_gui_comment(self, key, val): if self.gui_comments.get(key) == val: return self.gui_comments[key] = val self.parent.set_unsaved_flag() # UI update functions def initialize_ui(self): ui = self.ui self.setWindowTitle(self.title) get_value = self.parent.project.get_value update_keyword = self.parent.update_keyword # restart spx_files = self.parent.get_output_files(SPX_GLOB) res = self.parent.get_res_files() enable = bool(spx_files) or bool(res) ui.groupbox_restart.setEnabled(enable) if not enable: ui.groupbox_restart.setChecked(False) ui.groupbox_restart.setTitle('Restart - no restart files found') restart_1 = bool(spx_files) and bool(res) if not restart_1: ui.combobox_restart.setCurrentIndex(1) self.enable_restart_item(RESTART_TYPES_INVS['restart_1'], restart_1) self.enable_restart_item(RESTART_TYPES_INVS['restart_2'], bool(res)) # set OMP_NUM_THREADS project_threads = self.gui_comments.get('OMP_NUM_THREADS', '1') env_threads = os.environ.get('OMP_NUM_THREADS', None) if env_threads: project_threads = env_threads ui.spinbox_threads.setValue(safe_int(project_threads, default=1)) # migrate from comments back to keywords, undo issues 149 gui_comment_nodes = self.gui_comments.pop('OMP_NODES', None) if gui_comment_nodes: self.parent.set_unsaved_flag() # modified project nodes = gui_comment_nodes.split(os.path.pathsep) if len(nodes) == 3: nodes = (nodesi, nodesj, nodesk) = [safe_int(n, default=1) for n in nodes] else: nodes = (nodesi, nodesj, nodesk) = 1, 1, 1 update_keyword('nodesi', nodesi) update_keyword('nodesj', nodesj) update_keyword('nodesk', nodesk) else: nodes = (nodesi, nodesj, nodesk) = [ get_value('nodes' + c, default=1) for c in 'ijk' ] for val, spin in zip( nodes, [ui.spinbox_nodesi, ui.spinbox_nodesj, ui.spinbox_nodesk]): spin.setValue(val) # local/queue ui.groupbox_queue.setChecked( int(self.gui_comments.get('submit_to_queue', 0))) # create initial executable list self.get_solver_list() if self.solver_list: self.mfix_available = True self.populate_combobox_solver() for exe in self.solver_list.keys(): self.get_exe_flags(exe) # select solver self.ui.listwidget_solver_list.setCurrentRow(0) self.update_dialog_options() def init_templates(self): # look for templates in MFIX_HOME/queue_templates search_p = os.path.join(get_mfix_templates(), 'queue_templates') self.templates = {} for root, _, files in os.walk(search_p): for f in files: p = os.path.join(root, f) self.add_queue_template(p) # look for recent templates temp_paths = self.settings.value('queue_templates') if temp_paths: for temp_path in temp_paths.split('|'): if os.path.exists(temp_path): self.add_queue_template(temp_path) self.ui.combobox_template.currentIndexChanged.connect( self.update_queue_widgets) temp = self.gui_comments.get('queue_template') if temp: self.template_values = json.loads(temp) t_name = self.template_values.get('template') if t_name: self.set_current_template(t_name) self.update_queue_widgets() def save_template(self): '''Save the current template data''' self.collect_template_values() template_txt = self.ui.combobox_template.currentText() self.template_values['template'] = template_txt self.update_gui_comment('queue_template', json.dumps(self.template_values)) def collect_template_values(self): template_txt = self.ui.combobox_template.currentText() template = self.templates.get(template_txt, {}) replace_dict = {} for name, wid in template.items(): if not isinstance(wid, dict): continue if 'widget_obj' in wid: wid_obj = wid['widget_obj'] if isinstance(wid_obj, (QSpinBox, QDoubleSpinBox)): self.template_values[name] = v = wid_obj.value() elif isinstance(wid_obj, QCheckBox): self.template_values[name] = v = wid_obj.value if v: v = wid.get('true', '') else: v = wid.get('false', '') elif isinstance(wid_obj, QListWidget): self.template_values[name] = v = wid_obj.value v = ' '.join(v) else: self.template_values[name] = v = wid_obj.value replace_dict[name] = v return replace_dict def set_current_template(self, name): '''set the template file combobox''' cb = self.ui.combobox_template for itm in range(cb.count()): if str(name).lower() == str(cb.itemText(itm)).lower(): cb.setCurrentIndex(itm) break def add_queue_template(self, path, select=False): config, script = extract_config(path) c = configparser.ConfigParser() c.readfp(StringIO(config)) d = OrderedDict([(s, dict(c.items(s))) for s in c.sections()]) d['path'] = path d['script'] = script name = os.path.basename(path) if 'options' in d: name = d['options'].get('name', name) self.templates[name] = d self.ui.combobox_template.clear() self.ui.combobox_template.addItems(list(self.templates.keys())) if select: self.set_current_template(name) def update_queue_widgets(self): l = self.ui.groupbox_queue_options_gridlayout clear_layout(l) tp = self.ui.combobox_template.currentText() wids_data = self.templates.get(tp, None) if wids_data is None: return # check to see if qsub command present cmd = wids_data.get('options', {}).get('submit', None) if cmd is not None: cmd = cmd.strip().split()[0] if spawn.find_executable(cmd) is None: label = QLabel( 'The submission command "{}" does not exist in ' 'the current environment. Please select another ' 'template, edit the template, and/or check your ' 'environment.'.format(cmd)) label.setStyleSheet('color:red') label.setWordWrap(True) l.addWidget(label, 0, 0) return # add the widgets for i, wid in enumerate(list(wids_data.keys())): wd = wids_data[wid] if not isinstance(wd, dict) or wid == 'options': continue label = QLabel(wd.get('label', wid)) l.addWidget(label, i, 0) widget = BASE_WIDGETS.get(wd.get('widget', 'lineedit'), BASE_WIDGETS['lineedit'])() items = [it.strip() for it in wd.get('items', '').split('|')] v = self.template_values.get(wid) if not v or self.template_values.get('template') != tp: v = wd.get('value') if isinstance(widget, QComboBox) and items: widget.addItems(items) if v not in items: v = items[0] elif isinstance(widget, QListWidget) and items: widget.add_items(items) widget.setMaximumHeight(100) widget.updateValue('', v) widget.help_text = wd.get('help', 'No help available.') l.addWidget(widget, i, 1) wd['widget_obj'] = widget def populate_combobox_solver(self): """ Add items from self.solver_list to combobox, select the first item """ ui = self.ui ui.listwidget_solver_list.clear() ui.listwidget_solver_list.addItems(self.solver_list.keys()) def enable_restart_item(self, text, enable): cb = self.ui.combobox_restart model = cb.model() index = cb.findText(text) item = model.item(index) if not enable: flags = Qt.NoItemFlags else: flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled item.setFlags(flags) def update_dialog_options(self): """ Enable or disable options based on self.solver features, local or remote settings """ ui = self.ui status_text = '' get_value = self.parent.project.get_value # show status/errors error_msg = self.get_error() status = self.get_status() error = status == ERROR ui.toolbutton_view_error.setVisible(error) if error and error_msg: status_text = 'Solver Error:' # Enable/disable widgets enable = self.mfix_available and self.solver is not None and not error ui.button_run.setEnabled(enable) ui.widget_queue_options.setEnabled(enable) dmp = self.dmp_enabled() smp = self.smp_enabled() ui.groupbox_smp_options.setEnabled(enable and smp) ui.groupbox_dmp_options.setEnabled(enable and dmp) python = self.python_enabled() if status == LOOKING: status_text = 'Checking Solver.' elif not python and not error: status_text = 'Warning: Can not talk to selected solver (not python enabled).' ui.spinbox_nodesi.setEnabled(dmp) ui.spinbox_nodesj.setEnabled(dmp) ui.spinbox_nodesk.setEnabled(dmp and not get_value('no_k')) ui.spinbox_threads.setEnabled(smp) ui.label_solver_error.setText(status_text) def show_solver_error(self): error_msg = '\n'.join(self.get_error()) self.parent.message( text='The solver test failed with the following error:', info_text=error_msg) def popup(self): self.show() self.raise_() self.activateWindow() def closeEvent(self, event): """save information on close""" ui = self.ui # save solver list self.save_selected_exe() # queue self.save_template() self.update_gui_comment('submit_to_queue', int(ui.groupbox_queue.isChecked())) self.update_gui_comment('OMP_NUM_THREADS', str(ui.spinbox_threads.value())) self.parent.save_project() # event handlers def handle_abort(self): self.close() def finish_with_dialog(self): """ save run options in project file, then emit run signal """ ui = self.ui update_keyword = self.parent.update_keyword get_value = self.parent.project.get_value project_dir = self.parent.get_project_dir() udfs = glob(os.path.join(project_dir, "*.f")) udf_msg = ("Warning: Fortran source files exist for this project, but" " the selected mfixsolver is not in the project directory." " This case probably won't run correctly unless this" " project's custom mfixsolver is selected. Proceed anyway?") if udfs and "[project]" + os.sep + "mfixsolver" not in self.solver: response = self.parent.message(title='Warning', icon='question', text=udf_msg, buttons=['yes', 'no'], default='no') if response != 'yes': return thread_count = str(ui.spinbox_threads.value()) # FIXME: should not pollute local env (see saved_env) os.environ['OMP_NUM_THREADS'] = thread_count log.info('SMP enabled with OMP_NUM_THREADS=%s', os.environ["OMP_NUM_THREADS"]) self.update_gui_comment('OMP_NUM_THREADS', thread_count) # restart if ui.groupbox_restart.isChecked(): restart_type = RESTART_TYPES.get(ui.combobox_restart.currentText(), 'restart_1') update_keyword('run_type', restart_type) # restart_2 if restart_type == 'restart_2': spx_files = self.parent.get_output_files(RESTART_2_REMOVE_GLOB) if not self.parent.remove_output_files(spx_files, force_remove=True): log.debug('SP* files exist and run was canceled') return False # normal run else: update_keyword('run_type', 'new') output_files = self.parent.get_output_files() if output_files: message = 'Starting a new run requires the following files to be deleted from the run directory.' if not self.parent.remove_output_files( output_files, message_text=message, force_remove=True): log.info('output files exist and run was canceled') return False # collect nodes[ijk] nodesi = ui.spinbox_nodesi.value() nodesj = ui.spinbox_nodesj.value() nodesk = ui.spinbox_nodesk.value() if not self.dmp_enabled(): nodesi = nodesj = nodesk = 1 # write the correct nodes[ijk] to project file update_keyword('nodesi', nodesi) update_keyword('nodesj', nodesj) if not get_value('no_k'): update_keyword('nodesk', nodesk) else: update_keyword('nodesk', 1) if self.parent.unsaved_flag: # run_type keyword updated and/or nodesi/nodesj/nodesk self.parent.save_project() self.parent.update_source_view() else: stl = os.path.join(self.parent.get_project_dir(), 'geometry.stl') # is this needed? self.parent.vtkwidget.export_stl(stl) self.close() self.parent.signal_update_runbuttons.emit('') return True def handle_run(self): if not self.finish_with_dialog(): self.parent.slot_update_runbuttons() return # reset plots self.parent.reset_plots() self.parent.job_manager.stopping = False self.parent.job_manager.pausing = False self.parent.last_run_msg_time = 0.0 if self.ui.groupbox_queue.isChecked(): self.submit() else: self.run() self.parent.slot_update_runbuttons() def run(self): self.run_cmd = self.get_run_command() msg = 'Starting %s' % ' '.join(self.run_cmd) self.parent.print_internal(msg, color='blue') self.start_command(cmd=self.run_cmd, cwd=self.parent.get_project_dir(), env=os.environ) def submit(self): msg = 'Submitting to queue' self.parent.print_internal(msg, color='blue') self.submit_command(*self.get_submit_command()) def handle_remove_exe(self): ui = self.ui row = ui.listwidget_solver_list.currentRow() item = ui.listwidget_solver_list.currentItem() path = item.text() if not path.startswith('[default]') and not path.startswith( '[project'): ui.listwidget_solver_list.takeItem(row) def handle_browse_exe(self): """ Handle file open dialog for user specified exe """ new_exe, ignore = QFileDialog.getOpenFileName( self, "Select Executable", directory=self.project_dir, options=QFileDialog.DontResolveSymlinks) if not new_exe: return key = self.replace_solver_path(new_exe) lw = self.ui.listwidget_solver_list items = [lw.item(i).text() for i in range(0, lw.count())] if key in items: self.parent.message( text='The selected solver is already in the list of ' 'available solvers.') return # check solver ok, message = self.check_exe(new_exe) if not ok: self.parent.message(text=message) return self.save_selected_exe(new_exe) self.mfix_available = True lw.insertItem(0, key) lw.setCurrentRow(0) log.debug('selected new exe %s', key) def handle_browse_template(self): """ Handle file open dialog for user specified exe """ new_temp, ignore = QFileDialog.getOpenFileName( self, "Select a Template", directory=self.project_dir) if not new_temp: return self.add_queue_template(new_temp, select=True) # add it to the recent settings temp_paths = self.settings.value('queue_templates') good_paths = [os.path.realpath(new_temp)] if temp_paths: for temp_path in temp_paths.split('|'): if os.path.exists(temp_path): good_paths.append(temp_path) self.settings.setValue( 'queue_templates', '|'.join(list(set(good_paths))[:RECENT_EXE_LIMIT])) # utils def save_selected_exe(self, new_solver=None): """ add new executable to recent list, save in project file and config, send signal(s) """ if new_solver is None: new_solver = self.solver if new_solver is None: self.parent.warn('No solver selected') return key = self.replace_solver_path(new_solver) self.settings.setValue('mfix_exe', key) self.update_gui_comment('mfix_exe', key) lw = self.ui.listwidget_solver_list recent_list = [lw.item(i).text() for i in range(0, lw.count())] # truncate to maximum recent_list = recent_list[:RECENT_EXE_LIMIT] # make new solver in front if new_solver is not None: if new_solver in recent_list: recent_list.pop(recent_list.index(new_solver)) recent_list.insert(0, new_solver) # add to solver list nl = OrderedDict([(key, new_solver)]) for key in recent_list: val = self.solver_list.get(key, None) if val is not None: nl[key] = val self.solver_list = nl # save self.settings.setValue('recent_executables', str(os.pathsep).join(recent_list)) def get_solver_list(self): """ assemble list of executables from: - command line - project file 'mfix_exe' - project dir - config item 'recent_executables' - default install location """ def recently_used_executables(): recent_list = self.settings.value('recent_executables') if recent_list: # limit recently used exes to RECENT_EXE_LIMIT recent_lim = recent_list.split(os.pathsep)[:RECENT_EXE_LIMIT] recent_list = [ exe for exe in recent_lim if os.path.exists(exe) ] for recent_exe in recent_list: yield recent_exe def project_directory_executables(): for name in MFIXSOLVER_GLOB_NAMES: for exe in glob(os.path.join(self.project_dir, name)): yield os.path.realpath(exe) def project_file_executable(): project_exe = self.gui_comments.get('mfix_exe', None) if project_exe: yield project_exe def python_path(): for d in sys.path: # filter out empty strings and current directory from $PATH if d and d != os.path.curdir and os.path.isdir(d): for name in MFIXSOLVER_GLOB_NAMES: for exe in glob(os.path.join(d, name)): yield exe def os_path(): PATH = os.environ.get("PATH") if PATH: # using OrderedDict to preserve PATH order dirs = OrderedDict.fromkeys(PATH.split(os.pathsep)) else: dirs = OrderedDict() for d in dirs.keys(): # filter out empty strings and current directory from $PATH if d and d != os.path.curdir and os.path.isdir(d): for name in MFIXSOLVER_GLOB_NAMES: for exe in glob(os.path.join(d, name)): yield exe def mfix_build_directories(): for d in set([get_mfix_home()]): for name in MFIXSOLVER_GLOB_NAMES: for exe in glob(os.path.join(d, name)): yield exe def get_saved_exe(): last_exe = self.settings.value('mfix_exe') if last_exe and os.path.exists(last_exe): yield last_exe def command_line_option(): if self.commandline_option_exe and os.path.exists( self.commandline_option_exe): yield self.commandline_option_exe # Why this order? Shouldn't command_line_option or proj* be first? exe_list_order = [ command_line_option, project_file_executable, project_directory_executables, python_path, os_path, recently_used_executables, mfix_build_directories, get_saved_exe, ] # use an ordered dict because it acts like an ordered set self.solver_list = od = OrderedDict() # look for executables in the order listed in exe_list_order for exe_spec in exe_list_order: for exe in exe_spec(): # expand short hand if '[project]' in exe: exe = exe.replace('[project]', self.prj_dir()) elif '[default]' in exe: exe = exe.replace('[default]', self.def_dir()) # make sure it is an abs path exe = os.path.realpath(exe) # simple checking ok, message = self.check_exe(exe) # truncate paths to [project]/mfixsolver etc. key = self.replace_solver_path(exe) if not ok: self.parent.warn(message) elif key not in od: od[key] = exe def prj_dir(self): return os.path.realpath(self.project_dir) def def_dir(self): return os.path.dirname(os.path.realpath(sys.executable)) def replace_solver_path(self, path): if path.startswith(self.prj_dir()): path = path.replace(self.prj_dir(), '[project]') elif path.startswith(self.def_dir()): path = path.replace(self.def_dir(), '[default]') return path def check_exe(self, path): if not os.path.isfile(path): return False, '{} is not a file.'.format(path) # try executable if not os.access(path, os.X_OK): return False, '{} is not a executable.'.format(path) # windows, check extension if os.name == 'nt': ext = os.path.splitext(path)[-1] if not (ext.endswith('.exe') or ext.endswith('.bat')): return False, 'Extension {} is not recognized, must be .exe or .bat'.format( ext) return True, 'ok' def get_solver_key(self, solver): if solver is None: return None try: stat = os.stat(self.solver_list.get(solver)) except OSError as e: log.debug(str(e)) return None key = (stat, solver) return key def get_exe_flags(self, solver): """ get and cache (and update) executable features """ key = self.get_solver_key(solver) if key is None: return None # stat will have changed if the exe has been modified since last check if key in self.mfix_exe_cache: info = self.mfix_exe_cache[key] return info.get('flags') # spawn process to get flags else: self.set_solver_icon(solver, LOOKING) self.mfix_exe_cache[key] = { 'status': LOOKING, 'flags': None, 'stdout': [], 'stderror': [], 'error': [], } self.spawn_flag_process(*key) return LOOKING def spawn_flag_process(self, stat, solver): log.debug('Feature testing MFiX %s', solver) key = (stat, solver) proc = self.flag_processes.get(key, None) if proc is not None: proc.kill() exe = self.solver_list.get(solver) exe_dir = os.path.dirname(exe) proc = self.flag_processes[key] = QProcess() proc.setProcessEnvironment(QProcessEnvironment.systemEnvironment()) proc.readyReadStandardOutput.connect( lambda k=key: self.flag_process_out(k)) proc.readyReadStandardError.connect( lambda k=key: self.flag_process_error(k)) proc.finished.connect(lambda ecode, estat, k=key: self. flag_process_finished(k, ecode, estat)) proc.error.connect(lambda e, k=key: self.flag_process_error(k, e)) proc.setWorkingDirectory(exe_dir) proc.start(exe, ["--print-flags"]) def flag_process_error(self, key, error=None): info = self.mfix_exe_cache.get(key) self.set_solver_icon(key[1], ERROR) info['status'] = ERROR if error is None: error = bytes( self.flag_processes[key].readAllStandardError()).decode( 'utf-8', errors='ignore') info['stderror'].append(error) else: info['error'].append(error) log.debug("could not run {} --print-flags: {}".format(key[1], error)) def flag_process_out(self, key): info = self.mfix_exe_cache.get(key) out = bytes(self.flag_processes[key].readAllStandardOutput()).decode( 'utf-8', errors='ignore') info['stdout'].append(out) info['flags'] = str('\n'.join(info['stdout'])).strip() log.debug("stdout: {} --print-flags: {}".format(key[1], out)) def flag_process_finished(self, key, exit_code, exit_status): info = self.mfix_exe_cache.get(key, {}) status = info.get('status', ERROR) if info.get('stderror', []): status = ERROR if status == LOOKING: status = OK info['status'] = status self.set_solver_icon(key[1], status) if self.solver == key[1]: self.update_dialog_options() log.debug("finished: {} --print-flags: {}, {}".format( key[1], exit_code, exit_status)) def set_solver_icon(self, solver, status): items = self.ui.listwidget_solver_list.findItems( solver, Qt.MatchExactly) if not items: return item = items[0] icon = 'error_outline.svg' if status == LOOKING: icon = 'timelapse.svg' elif status == OK: icon = 'check_outline.svg' item.setIcon(get_icon(icon)) def dmp_enabled(self): flags = self.get_exe_flags(self.solver) dmp = False if flags is not None: dmp = 'dmp' in str(flags) return dmp def smp_enabled(self): flags = self.get_exe_flags(self.solver) smp = False if flags is not None: smp = 'smp' in str(flags) return smp def python_enabled(self): flags = self.get_exe_flags(self.solver) python = False if flags is not None: python = 'python' in str(flags) return python def get_error(self): key = self.get_solver_key(self.solver) if key is None: return None info = self.mfix_exe_cache.get(key, {}) return info.get('stderror', None) def get_status(self): key = self.get_solver_key(self.solver) if key is None: return None info = self.mfix_exe_cache.get(key, {}) return info.get('status', None) def get_run_command(self): get_value = self.parent.project.get_value # collect nodes[ijk] from project to guarantee that mpirun matches nodesi = get_value('nodesi', 1) nodesj = get_value('nodesj', 1) nodesk = get_value('nodesk', 1) np = nodesi * nodesj * nodesk if self.dmp_enabled() and np > 1: dmp = [ 'mpirun', # '-quiet', # '-mca', 'orte_create_session_dirs', 'true', '-mca', 'mpi_warn_on_fork', '0', '-np', str(nodesi * nodesj * nodesk) ] else: dmp = [] #if self.dmp_enabled(): # run_cmd += ['nodesi=%s'%nodesi, # 'nodesj=%s'%nodesj] # if not self.parent.project.get_value('no_k'): # run_cmd += ['nodesk=%s'%nodesk] if self.smp_enabled(): num_threads = str(self.ui.spinbox_threads.value()) smp = ['env', 'OMP_NUM_THREADS=%s' % num_threads] else: smp = [] run_cmd = smp + dmp + [self.solver_list.get(self.solver)] # Add 'server' flag to start HTTP server run_cmd += ['-s'] # Specify project file project_filename = self.parent.get_project_file() run_cmd += ['-f', project_filename] return run_cmd def get_submit_command(self): cmd = self.get_run_command() template_txt = self.ui.combobox_template.currentText() template = self.templates[template_txt] # collect widget values replace_dict = self.collect_template_values() replace_dict.update({ 'PROJECT_NAME': self.parent.project.get_value('run_name', default=''), 'COMMAND': ' '.join(cmd), 'MFIX_HOME': get_mfix_home(), }) # replace twice to make sure that any references added the first time # get replaced script = replace_with_dict(template['script'], replace_dict) script = replace_with_dict(script, replace_dict) sub_cmd = template['options'].get('submit', False) delete_cmd = template['options'].get('delete', False) # XXX status_cmd = template['options'].get('status', False) job_id_regex = template['options'].get('job_id_regex', None) ## FIXME, return something nicer than this 6-tuple return script, sub_cmd, delete_cmd, status_cmd, job_id_regex, replace_dict def submit_command(self, script, sub_cmd, delete_cmd, status_cmd, job_id_regex, replace_dict): self.remove_mfix_stop() if not sub_cmd: template_txt = self.ui.combobox_template.currentText() self.parent.error( ('The template file at: {}\n' 'does not have a submit_cmd defined').format(template_txt)) return self.parent.job_manager.submit_command(script, sub_cmd, delete_cmd, status_cmd, job_id_regex, replace_dict) def remove_mfix_stop(self): mfix_stop_file = os.path.join(self.parent.get_project_dir(), 'MFIX.STOP') if os.path.exists(mfix_stop_file): try: os.remove(mfix_stop_file) except OSError: self.parent.warn("Cannot remove %s", mfix_stop_file) return def start_command(self, cmd, cwd, env): """Start MFIX in QProcess""" self.cmdline = cmd # List of strings, same as psutil self.remove_mfix_stop() self.mfixproc = QProcess() if not self.mfixproc: log.warning("QProcess creation failed") return self.mfixproc.setWorkingDirectory(cwd) def slot_start(): # processId was only added in qt 5.3 if StrictVersion(QT_VERSION) > StrictVersion('5.3'): pid = self.mfixproc.processId() else: pid = self.mfixproc.pid() msg = "MFiX process %d is running" % pid self.parent.signal_update_runbuttons.emit(msg) def slot_read_out(): # Why convert to bytes then decode? out_str = bytes(self.mfixproc.readAllStandardOutput()).decode( 'utf-8', errors='ignore') self.parent.stdout_signal.emit(out_str) def slot_read_err(): err_str = bytes(self.mfixproc.readAllStandardError()).decode( 'utf-8', errors='ignore') self.parent.stderr_signal.emit(err_str) def slot_finish(status): # This should really be in the job manager if self.parent.job_manager.job: self.parent.job_manager.job.cleanup_and_exit() self.parent.job_manager.job = None msg = "MFiX process has stopped" self.parent.signal_update_runbuttons.emit(msg) if self.parent.job_manager.pidfile: try: os.unlink(self.parent.job_manager.pidfile) self.parent.job_manager.pidfile = None except OSError as e: if e.errno != errno.ENOENT: raise def slot_error(error): cmd_str = ' '.join(self.cmdline) if error == QProcess.FailedToStart: msg = "Process failed to start " + cmd_str elif error == QProcess.Crashed: msg = "Process exit " + cmd_str elif error == QProcess.Timedout: msg = "Process timeout " + cmd_str elif error in (QProcess.WriteError, QProcess.ReadError): msg = "Process communication error " + cmd_str else: msg = "Unknown error " + cmd_str log.warning(msg) # make the message print in red self.parent.stderr_signal.emit(msg) self.mfixproc.started.connect(slot_start) self.mfixproc.readyReadStandardOutput.connect(slot_read_out) self.mfixproc.readyReadStandardError.connect(slot_read_err) self.mfixproc.finished.connect(slot_finish) self.mfixproc.error.connect(slot_error) start_detached = True #if sys.platform.startswith('win') or 'mpirun' not in cmd: # start_detached = False # On Windows, start_detached gives a DOS box # What was the issue with mpirun? start_detached = False # https://bugreports.qt.io/browse/QTBUG-2284 # QProcessEnvironment does not work with startDetached, # fixed in Qt5.10 which we aren't using yet saved_env = None if not start_detached: process_env = QProcessEnvironment() add_env = process_env.insert else: add_env = os.environ.__setitem__ saved_env = os.environ.copy() for key, val in env.items(): add_env(key, val) add_env('MFIX_RUN_CMD', ' '.join(cmd)) if not start_detached: self.mfixproc.setProcessEnvironment(process_env) self.mfixproc.start(cmd[0], cmd[1:]) else: self.mfixproc.startDetached(cmd[0], cmd[1:]) # restore environment if saved_env: for (k, v) in list(os.environ.items()): if k not in saved_env: del os.environ[k] elif v != saved_env[k]: os.environ[k] = saved_env[k] # give gui a reference self.parent.mfix_process = self.mfixproc self.parent.slot_rundir_timer()