source: calamares/trunk/fuentes/src/libcalamares/PythonHelper.cpp @ 7538

Last change on this file since 7538 was 7538, checked in by kbut, 17 months ago

sync with github

File size: 9.7 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014, Teo Mrnjavac <teo@kde.org>
4 *   Copyright 2017, Adriaan de Groot <groot@kde.org>
5 *
6 *   Calamares is free software: you can redistribute it and/or modify
7 *   it under the terms of the GNU General Public License as published by
8 *   the Free Software Foundation, either version 3 of the License, or
9 *   (at your option) any later version.
10 *
11 *   Calamares is distributed in the hope that it will be useful,
12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 *   GNU General Public License for more details.
15 *
16 *   You should have received a copy of the GNU General Public License
17 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "PythonHelper.h"
21
22#include "utils/CalamaresUtils.h"
23#include "utils/Logger.h"
24
25#include <QDir>
26#include <QFileInfo>
27
28#undef slots
29#include <boost/python.hpp>
30
31namespace bp = boost::python;
32
33namespace CalamaresPython
34{
35
36
37boost::python::object
38variantToPyObject( const QVariant& variant )
39{
40    switch ( variant.type() )
41    {
42    case QVariant::Map:
43        return variantMapToPyDict( variant.toMap() );
44
45    case QVariant::Hash:
46        return variantHashToPyDict( variant.toHash() );
47
48    case QVariant::List:
49    case QVariant::StringList:
50        return variantListToPyList( variant.toList() );
51
52    case QVariant::Int:
53        return bp::object( variant.toInt() );
54
55    case QVariant::Double:
56        return bp::object( variant.toDouble() );
57
58    case QVariant::String:
59        return bp::object( variant.toString().toStdString() );
60
61    case QVariant::Bool:
62        return bp::object( variant.toBool() );
63
64    default:
65        return bp::object();
66    }
67}
68
69
70QVariant
71variantFromPyObject( const boost::python::object& pyObject )
72{
73    std::string pyType = bp::extract< std::string >( pyObject.attr( "__class__" ).attr( "__name__" ) );
74    if ( pyType == "dict" )
75        return variantMapFromPyDict( bp::extract< bp::dict >( pyObject ) );
76
77    else if ( pyType == "list" )
78        return variantListFromPyList( bp::extract< bp::list >( pyObject ) );
79
80    else if ( pyType == "int" )
81        return QVariant( bp::extract< int >( pyObject ) );
82
83    else if ( pyType == "float" )
84        return QVariant( bp::extract< double >( pyObject ) );
85
86    else if ( pyType == "str" )
87        return QVariant( QString::fromStdString( bp::extract< std::string >( pyObject ) ) );
88
89    else if ( pyType == "bool" )
90        return QVariant( bp::extract< bool >( pyObject ) );
91
92    else
93        return QVariant();
94}
95
96
97boost::python::list
98variantListToPyList( const QVariantList& variantList )
99{
100    bp::list pyList;
101    for ( const QVariant& variant : variantList )
102        pyList.append( variantToPyObject( variant ) );
103    return pyList;
104}
105
106
107QVariantList
108variantListFromPyList( const boost::python::list& pyList )
109{
110    QVariantList list;
111    for ( int i = 0; i < bp::len( pyList ); ++i )
112        list.append( variantFromPyObject( pyList[ i ] ) );
113    return list;
114}
115
116
117boost::python::dict
118variantMapToPyDict( const QVariantMap& variantMap )
119{
120    bp::dict pyDict;
121    for ( auto it = variantMap.constBegin(); it != variantMap.constEnd(); ++it )
122        pyDict[ it.key().toStdString() ] = variantToPyObject( it.value() );
123    return pyDict;
124}
125
126
127QVariantMap
128variantMapFromPyDict( const boost::python::dict& pyDict )
129{
130    QVariantMap map;
131    bp::list keys = pyDict.keys();
132    for ( int i = 0; i < bp::len( keys ); ++i )
133    {
134        bp::extract< std::string > extracted_key( keys[ i ] );
135        if ( !extracted_key.check() )
136        {
137            cDebug() << "Key invalid, map might be incomplete.";
138            continue;
139        }
140
141        std::string key = extracted_key;
142
143        bp::object obj = pyDict[ key ];
144
145        map.insert( QString::fromStdString( key ), variantFromPyObject( obj ) );
146    }
147    return map;
148}
149
150boost::python::dict
151variantHashToPyDict( const QVariantHash& variantHash )
152{
153    bp::dict pyDict;
154    for ( auto it = variantHash.constBegin(); it != variantHash.constEnd(); ++it )
155        pyDict[ it.key().toStdString() ] = variantToPyObject( it.value() );
156    return pyDict;
157}
158
159
160QVariantHash
161variantHashFromPyDict( const boost::python::dict& pyDict )
162{
163    QVariantHash hash;
164    bp::list keys = pyDict.keys();
165    for ( int i = 0; i < bp::len( keys ); ++i )
166    {
167        bp::extract< std::string > extracted_key( keys[ i ] );
168        if ( !extracted_key.check() )
169        {
170            cDebug() << "Key invalid, map might be incomplete.";
171            continue;
172        }
173
174        std::string key = extracted_key;
175
176        bp::object obj = pyDict[ key ];
177
178        hash.insert( QString::fromStdString( key ), variantFromPyObject( obj ) );
179    }
180    return hash;
181}
182
183
184
185Helper* Helper::s_instance = nullptr;
186
187static inline void add_if_lib_exists( const QDir& dir, const char* name, QStringList& list )
188{
189    if ( ! ( dir.exists() && dir.isReadable() ) )
190        return;
191
192    QFileInfo fi( dir.absoluteFilePath( name ) );
193    if ( fi.exists() && fi.isReadable() )
194        list.append( fi.dir().absolutePath() );
195}
196
197Helper::Helper( QObject* parent )
198    : QObject( parent )
199{
200    // Let's make extra sure we only call Py_Initialize once
201    if ( !s_instance )
202    {
203        if ( !Py_IsInitialized() )
204            Py_Initialize();
205
206        m_mainModule = bp::import( "__main__" );
207        m_mainNamespace = m_mainModule.attr( "__dict__" );
208
209        // If we're running from the build dir
210        add_if_lib_exists( QDir::current(), "libcalamares.so", m_pythonPaths );
211
212        QDir calaPythonPath( CalamaresUtils::systemLibDir().absolutePath() +
213                             QDir::separator() + "calamares" );
214        add_if_lib_exists( calaPythonPath, "libcalamares.so", m_pythonPaths );
215
216        bp::object sys = bp::import( "sys" );
217
218        foreach ( QString path, m_pythonPaths )
219        {
220            bp::str dir = path.toLocal8Bit().data();
221            sys.attr( "path" ).attr( "append" )( dir );
222        }
223    }
224    else
225    {
226        cWarning() << "creating PythonHelper more than once. This is very bad.";
227        return;
228    }
229
230    s_instance = this;
231}
232
233Helper::~Helper()
234{
235    s_instance = nullptr;
236}
237
238
239boost::python::dict
240Helper::createCleanNamespace()
241{
242    // To make sure we run each script with a clean namespace, we only fetch the
243    // builtin namespace from the interpreter as it was when freshly initialized.
244    bp::dict scriptNamespace;
245    scriptNamespace[ "__builtins__" ] = m_mainNamespace[ "__builtins__" ];
246
247    return scriptNamespace;
248}
249
250
251QString
252Helper::handleLastError()
253{
254    PyObject* type = nullptr, *val = nullptr, *traceback_p = nullptr;
255    PyErr_Fetch( &type, &val, &traceback_p );
256
257    Logger::CDebug debug;
258    debug.noquote() << "Python Error:\n";
259
260    QString typeMsg;
261    if ( type != nullptr )
262    {
263        bp::handle<> h_type( type );
264        bp::str pystr( h_type );
265        bp::extract< std::string > extracted( pystr );
266        if ( extracted.check() )
267            typeMsg = QString::fromStdString( extracted() ).trimmed();
268
269        if ( typeMsg.isEmpty() )
270            typeMsg = tr( "Unknown exception type" );
271        debug << typeMsg << '\n';
272    }
273
274    QString valMsg;
275    if ( val != nullptr )
276    {
277        bp::handle<> h_val( val );
278        bp::str pystr( h_val );
279        bp::extract< std::string > extracted( pystr );
280        if ( extracted.check() )
281            valMsg = QString::fromStdString( extracted() ).trimmed();
282
283        if ( valMsg.isEmpty() )
284            valMsg = tr( "unparseable Python error" );
285
286        // Special-case: CalledProcessError has an attribute "output" with the command output,
287        // add that to the printed message.
288        if ( typeMsg.contains( "CalledProcessError" ) )
289        {
290            bp::object exceptionObject( h_val );
291            auto a = exceptionObject.attr( "output" );
292            bp::str outputString( a );
293            bp::extract< std::string > extractedOutput( outputString );
294
295            QString output;
296            if ( extractedOutput.check() )
297            {
298                output = QString::fromStdString( extractedOutput() ).trimmed();
299            }
300            if ( !output.isEmpty() )
301            {
302                // Replace the Type of the error by the warning string,
303                // and use the output of the command (e.g. its stderr) as value.
304                typeMsg = valMsg;
305                valMsg = output;
306            }
307        }
308        debug << valMsg << '\n';
309    }
310
311    QString tbMsg;
312    if ( traceback_p != nullptr )
313    {
314        bp::handle<> h_tb( traceback_p );
315        bp::object traceback_module( bp::import( "traceback" ) );
316        bp::object format_tb( traceback_module.attr( "format_tb" ) );
317        bp::object tb_list( format_tb( h_tb ) );
318        bp::object pystr( bp::str( "\n" ).join( tb_list ) );
319        bp::extract< std::string > extracted( pystr );
320        if ( extracted.check() )
321            tbMsg = QString::fromStdString( extracted() ).trimmed();
322
323        if ( tbMsg.isEmpty() )
324            tbMsg = tr( "unparseable Python traceback" );
325        debug << tbMsg << '\n';
326    }
327
328    if ( typeMsg.isEmpty() && valMsg.isEmpty() && tbMsg.isEmpty() )
329        return tr( "Unfetchable Python error." );
330
331
332    QStringList msgList;
333    if ( !typeMsg.isEmpty() )
334        msgList.append( QString( "<strong>%1</strong>" ).arg( typeMsg.toHtmlEscaped() ) );
335    if ( !valMsg.isEmpty() )
336        msgList.append( valMsg.toHtmlEscaped() );
337
338    if ( !tbMsg.isEmpty() )
339    {
340        msgList.append( QStringLiteral( "<br/>Traceback:" ) );
341        msgList.append( QString( "<pre>%1</pre>" ).arg( tbMsg.toHtmlEscaped() ) );
342    }
343
344    // Return a string made of the msgList items, wrapped in <div> tags
345    return QString( "<div>%1</div>" ).arg( msgList.join( "</div><div>" ) );
346}
347
348
349} // namespace CalamaresPython
Note: See TracBrowser for help on using the repository browser.