Code_TYMPAN  4.4.0
Industrial site acoustic simulation
subprocess_util.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) <2014> <EDF-R&D> <FRANCE>
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10  * See the GNU General Public License for more details.
11  * You should have received a copy of the GNU General Public License along
12  * with this program; if not, write to the Free Software Foundation, Inc.,
13  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
14  */
15 
22 #include <QFile>
23 #include <QCoreApplication>
24 #include <QProcess>
25 #include <QDir>
26 
27 #include "Tympan/core/defines.h"
28 #include "Tympan/core/chrono.h"
29 #include "Tympan/core/logging.h"
30 #include "Tympan/core/exceptions.h"
31 #include "subprocess_util.h"
32 
33 #if defined _DEBUG
34  #define TYMPAN_REL_CYTHON_PATH "cython_d"
35 #else // undefined _DEBUG (release install)
36  #define TYMPAN_REL_CYTHON_PATH "cython"
37 #endif // defined _DEBUG
38 
39 #define COMPUTATION_TIMEOUT 10000 // In ms
40 
42 {
43  QStringList env(QProcess::systemEnvironment());
44  // Absolute path to Tympan install directory
45  QString cythonlibs_path(QCoreApplication::applicationDirPath());
46  // Relative path from Tympan install directory to cython modules
47  cythonlibs_path.append("/");
48  cythonlibs_path.append(TYMPAN_REL_CYTHON_PATH);
49  cythonlibs_path = QDir::toNativeSeparators(cythonlibs_path);
50  // Set new PYTHONPATH
51  QRegExp pythonpath_regexp("^PYTHONPATH=(.*)", Qt::CaseInsensitive);
52  int pythonpath_index = env.indexOf(pythonpath_regexp);
53  QString pythonpath;
54  if (pythonpath_index > 0)
55  {
56  pythonpath = env[pythonpath_index];
57  // Check the presence of cython libs in the PYTHONPATH
58  if (pythonpath != "PYTHONPATH=")
59  {
60 #if TY_PLATFORM == TY_PLATFORM_WIN32 || TY_PLATFORM == TY_PLATFORM_WIN64
61  pythonpath.append(";");
62 #else
63  pythonpath.append(":");
64 #endif
65  }
66  pythonpath.append(cythonlibs_path);
67  env.removeAt(pythonpath_index);
68  }
69  else
70  {
71  pythonpath = "PYTHONPATH=";
72  pythonpath.append(cythonlibs_path);
73  }
74  env.append(pythonpath);
75 #if TY_PLATFORM == TY_PLATFORM_WIN32 || TY_PLATFORM == TY_PLATFORM_WIN64
76  // Add path to dynamic libraries needed by Tympan and thus needed by pytam (cython library)
77  QRegExp path_regexp("^Path=(.*)", Qt::CaseInsensitive);
78  int path_index = env.indexOf(path_regexp);
79  QString path = env[path_index];
80  QRegExp equal_regexp("=", Qt::CaseInsensitive); // Position of the "=" symbol in the path QString
81  int equal_index = path.indexOf(equal_regexp) + 1;
82  QString application_path(QCoreApplication::applicationDirPath());
83  application_path = QDir::toNativeSeparators(application_path);
84  path.insert(equal_index, application_path + ";"); // Insert the application path right after "PATH =".
85  env.removeAt(path_index);
86  env.append(path);
87 #endif
88  return env;
89 }
90 
92 {
93 #if TY_PLATFORM == TY_PLATFORM_WIN32 || TY_PLATFORM == TY_PLATFORM_WIN64
94  QStringList env(QProcess::systemEnvironment());
95  // TYMPAN_PYTHON_INTERP environment variable must be set to the path to
96  // python 3 interpreter (ex: "C:\Python34\python.exe")
97  int python_interp_idx = env.indexOf(QRegExp("^TYMPAN_PYTHON_INTERP=(.*)"));
98  if (python_interp_idx < 0)
99  {
100  throw tympan::invalid_data(
101  "Can't access python interpreter. TYMPAN_PYTHON_INTERP environment variable is not set.");
102  }
103  QString python_interp_path = env.at(python_interp_idx).split('=')[1].remove("\"");
104  QFile python_interp(python_interp_path);
105  if (!python_interp.exists())
106  {
107  throw tympan::invalid_data("Can't access python interpreter. TYMPAN_PYTHON_INTERP environment "
108  "variable is not correctly set.");
109  }
110  return python_interp_path;
111 #else
112  return QString("python3");
113 #endif
114 }
115 
116 std::string _read_environment_variables(QStringList env)
117 {
118  std::string variables = "\nVariables d'environnement:\n";
119  int pythonpath_index = env.indexOf(QRegExp("^PYTHONPATH=(.*)", Qt::CaseInsensitive));
120  if (pythonpath_index >= 0)
121  {
122  variables += env[pythonpath_index].toStdString() + "\n";
123  }
124  else
125  {
126  variables += "PYTHONPATH absente\n";
127  }
128 #if TY_PLATFORM == TY_PLATFORM_WIN32 || TY_PLATFORM == TY_PLATFORM_WIN64
129  int path_index = env.indexOf(QRegExp("^Path=(.*)", Qt::CaseInsensitive));
130  if (path_index >= 0)
131  {
132  variables += env[path_index].toStdString() + "\n";
133  }
134  else
135  {
136  variables += "Path absente\n";
137  }
138  int python_interp_index = env.indexOf(QRegExp("^TYMPAN_PYTHON_INTERP=(.*)"));
139  if (python_interp_index >= 0)
140  {
141  variables += env[python_interp_index].toStdString() + "\n";
142  }
143  else
144  {
145  variables += "TYMPAN_PYTHON_INTERP absente\n";
146  }
147 #endif
148  return variables;
149 }
150 
152 {
153  QStringList appli_env(QProcess::systemEnvironment());
154  int tympan_debug_idx = appli_env.indexOf(QRegExp("^TYMPAN_DEBUG=(.*)"));
155  if (tympan_debug_idx >= 0)
156  {
157  QString debug_option = appli_env[tympan_debug_idx].split('=')[1];
158  if (debug_option.contains("keep_tmp_files", Qt::CaseInsensitive))
159  {
160  return true;
161  }
162  }
163  return false;
164 }
165 
166 bool init_tmp_file(QTemporaryFile& tmp_file, bool keep_file)
167 {
168  if (!tmp_file.open())
169  {
170  return false;
171  }
172  tmp_file.close();
173  // Prevent from automatic file removal
174  if (keep_file)
175  tmp_file.setAutoRemove(false);
176  return true;
177 }
178 
179 bool python(QStringList args, std::string& error_msg)
180 {
182  // Start chrono
183  OChronoTime startTime;
184  logger.debug("Lancement du script python: %s", args.join(" ").toStdString().c_str());
185  QProcess python;
186  float comp_duration(0.);
187  bool comp_finished(false);
188  // Set PYTHONPATH to python subprocess
189  QStringList env(_python_qprocess_environment());
190  python.setEnvironment(env);
191  // Since scripts passed to QProcess are not launched through cmd.exe
192  // under windows, we have to give QProcess the path to the python interpreter
193  QString python_interp;
194  try
195  {
196  python_interp = _get_python_interp();
197  }
198  catch (const tympan::invalid_data&)
199  {
200  error_msg = "L'interpreteur python n'a pas pu etre trouve.\nVeuillez verifier que la variable "
201  "d'environnement TYMPAN_PYTHON_INTERP est correctement positionnee\n";
202  error_msg.append(_read_environment_variables(env));
203  return false;
204  }
205  python.start(python_interp, args);
206  do
207  {
208  comp_finished = python.waitForFinished(COMPUTATION_TIMEOUT);
209  if (comp_finished)
210  {
211  break;
212  }
213  if (python.error() == QProcess::Timedout)
214  {
215  // Computation still running
216  comp_duration += (COMPUTATION_TIMEOUT / 1000);
217  logger.info("Le script python s'execute encore apres %.3f secondes", comp_duration);
218  continue;
219  }
220  else
221  {
222  // Will have a bad exit status and will be handled below
223  break;
224  }
225  } while (!comp_finished);
226 
227  QString std_error(python.readAllStandardError());
228  int exit_code = python.exitCode();
229  if (python.exitStatus() != QProcess::NormalExit || exit_code != 0)
230  {
231  error_msg = "Le sous-process python s'est terminé avec le code d'erreur ";
232  error_msg.append(std::to_string(static_cast<long long>(exit_code)));
233  error_msg.append("\n");
234  error_msg.append(std_error.toStdString());
235  error_msg.append(_read_environment_variables(env));
236  error_msg.append("Veuillez lire tympan.log pour plus d'information.\n");
237  logger.error(error_msg.c_str());
238  return false;
239  }
240  else
241  {
242  logger.info("Le sous-processus Python s'est terminé correctement");
243  if (!std_error.isEmpty())
244  logger.warning(std_error.toStdString().c_str());
245  }
246 
247  // Compute and display computation time
248  OChronoTime endTime;
249  OChronoTime duration = endTime - startTime;
250  unsigned long second = duration.getTime() / 1000;
251  unsigned long millisecond = duration.getTime() - second * 1000;
252  logger.info("Temps de calcul : %02ld,%03ld sec. (%ld msec.)", second, millisecond, duration.getTime());
253  return true;
254 }
unsigned long getTime() const
Definition: chrono.h:42
virtual void debug(const char *message,...)
Definition: logging.cpp:151
virtual void warning(const char *message,...)
Definition: logging.cpp:119
virtual void error(const char *message,...)
Definition: logging.cpp:127
static OMessageManager * get()
Definition: logging.cpp:108
virtual void info(const char *message,...)
Definition: logging.cpp:143
Utilities to handle exceptions and to pretty-print value.
The base exception class for errors due to invalid data.
Definition: exceptions.h:60
bool python(QStringList args, std::string &error_msg)
bool must_keep_tmp_files()
bool init_tmp_file(QTemporaryFile &tmp_file, bool keep_file)
QString _get_python_interp()
std::string _read_environment_variables(QStringList env)
QStringList _python_qprocess_environment()
#define TYMPAN_REL_CYTHON_PATH
#define COMPUTATION_TIMEOUT
Utilitaires pour les interactions entre l'application tympan et des sous- processus python.