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

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

sync with github

File size: 14.3 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014-2016, Teo Mrnjavac <teo@kde.org>
4 *
5 *   Calamares is free software: you can redistribute it and/or modify
6 *   it under the terms of the GNU General Public License as published by
7 *   the Free Software Foundation, either version 3 of the License, or
8 *   (at your option) any later version.
9 *
10 *   Calamares is distributed in the hope that it will be useful,
11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 *   GNU General Public License for more details.
14 *
15 *   You should have received a copy of the GNU General Public License
16 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "PythonJob.h"
20
21#include "PythonHelper.h"
22#include "utils/Logger.h"
23#include "GlobalStorage.h"
24#include "JobQueue.h"
25
26#include <QDir>
27
28#undef slots
29#include <boost/python.hpp>
30#include <boost/python/args.hpp>
31
32#include "PythonJobApi.h"
33
34
35namespace bp = boost::python;
36
37BOOST_PYTHON_FUNCTION_OVERLOADS( mount_overloads,
38                                 CalamaresPython::mount,
39                                 2, 4 );
40BOOST_PYTHON_FUNCTION_OVERLOADS( target_env_call_str_overloads,
41                                 CalamaresPython::target_env_call,
42                                 1, 3 );
43BOOST_PYTHON_FUNCTION_OVERLOADS( target_env_call_list_overloads,
44                                 CalamaresPython::target_env_call,
45                                 1, 3 );
46BOOST_PYTHON_FUNCTION_OVERLOADS( check_target_env_call_str_overloads,
47                                 CalamaresPython::check_target_env_call,
48                                 1, 3 );
49BOOST_PYTHON_FUNCTION_OVERLOADS( check_target_env_call_list_overloads,
50                                 CalamaresPython::check_target_env_call,
51                                 1, 3 );
52BOOST_PYTHON_FUNCTION_OVERLOADS( check_target_env_output_str_overloads,
53                                 CalamaresPython::check_target_env_output,
54                                 1, 3 );
55BOOST_PYTHON_FUNCTION_OVERLOADS( check_target_env_output_list_overloads,
56                                 CalamaresPython::check_target_env_output,
57                                 1, 3 );
58BOOST_PYTHON_MODULE( libcalamares )
59{
60    bp::object package = bp::scope();
61    package.attr( "__path__" ) = "libcalamares";
62
63    bp::scope().attr( "ORGANIZATION_NAME" ) = CALAMARES_ORGANIZATION_NAME;
64    bp::scope().attr( "ORGANIZATION_DOMAIN" ) = CALAMARES_ORGANIZATION_DOMAIN;
65    bp::scope().attr( "APPLICATION_NAME" ) = CALAMARES_APPLICATION_NAME;
66    bp::scope().attr( "VERSION" ) = CALAMARES_VERSION;
67    bp::scope().attr( "VERSION_SHORT" ) = CALAMARES_VERSION_SHORT;
68
69    bp::class_< CalamaresPython::PythonJobInterface >( "Job", bp::init< Calamares::PythonJob* >() )
70        .def_readonly( "module_name",   &CalamaresPython::PythonJobInterface::moduleName )
71        .def_readonly( "pretty_name",   &CalamaresPython::PythonJobInterface::prettyName )
72        .def_readonly( "working_path",  &CalamaresPython::PythonJobInterface::workingPath )
73        .def_readonly( "configuration", &CalamaresPython::PythonJobInterface::configuration )
74        .def(
75            "setprogress",
76            &CalamaresPython::PythonJobInterface::setprogress,
77            bp::args( "progress" ),
78            "Reports the progress status of this job to Calamares, "
79            "as a real number between 0 and 1."
80        );
81
82    bp::class_< CalamaresPython::GlobalStoragePythonWrapper >( "GlobalStorage", bp::init< Calamares::GlobalStorage* >() )
83        .def( "contains",   &CalamaresPython::GlobalStoragePythonWrapper::contains )
84        .def( "count",      &CalamaresPython::GlobalStoragePythonWrapper::count )
85        .def( "insert",     &CalamaresPython::GlobalStoragePythonWrapper::insert )
86        .def( "keys",       &CalamaresPython::GlobalStoragePythonWrapper::keys )
87        .def( "remove",     &CalamaresPython::GlobalStoragePythonWrapper::remove )
88        .def( "value",      &CalamaresPython::GlobalStoragePythonWrapper::value );
89
90    // libcalamares.utils submodule starts here
91    bp::object utilsModule( bp::handle<>( bp::borrowed( PyImport_AddModule( "libcalamares.utils" ) ) ) );
92    bp::scope().attr( "utils" ) = utilsModule;
93    bp::scope utilsScope = utilsModule;
94    Q_UNUSED( utilsScope );
95
96    bp::def(
97        "debug",
98        &CalamaresPython::debug,
99        bp::args( "s" ),
100        "Writes the given string to the Calamares debug stream."
101    );
102    bp::def(
103        "warning",
104        &CalamaresPython::warning,
105        bp::args( "s" ),
106        "Writes the given string to the Calamares warning stream."
107    );
108
109    bp::def(
110        "mount",
111        &CalamaresPython::mount,
112        mount_overloads(
113            bp::args( "device_path",
114                      "mount_point",
115                      "filesystem_name",
116                      "options" ),
117            "Runs the mount utility with the specified parameters.\n"
118            "Returns the program's exit code, or:\n"
119            "-1 = QProcess crash\n"
120            "-2 = QProcess cannot start\n"
121            "-3 = bad arguments"
122        )
123    );
124    bp::def(
125        "target_env_call",
126        static_cast< int (*)( const std::string&,
127                              const std::string&,
128                              int ) >( &CalamaresPython::target_env_call ),
129        target_env_call_str_overloads(
130            bp::args( "command",
131                      "stdin",
132                      "timeout" ),
133            "Runs the specified command in the chroot of the target system.\n"
134            "Returns the program's exit code, or:\n"
135            "-1 = QProcess crash\n"
136            "-2 = QProcess cannot start\n"
137            "-3 = bad arguments\n"
138            "-4 = QProcess timeout"
139        )
140    );
141    bp::def(
142        "target_env_call",
143        static_cast< int (*)( const bp::list&,
144                              const std::string&,
145                              int ) >( &CalamaresPython::target_env_call ),
146        target_env_call_list_overloads(
147            bp::args( "args",
148                      "stdin",
149                      "timeout" ),
150            "Runs the specified command in the chroot of the target system.\n"
151            "Returns the program's exit code, or:\n"
152            "-1 = QProcess crash\n"
153            "-2 = QProcess cannot start\n"
154            "-3 = bad arguments\n"
155            "-4 = QProcess timeout"
156        )
157    );
158
159    bp::def(
160        "check_target_env_call",
161        static_cast< int (*)( const std::string&,
162                              const std::string&,
163                              int ) >( &CalamaresPython::check_target_env_call ),
164        check_target_env_call_str_overloads(
165            bp::args( "command",
166                      "stdin",
167                      "timeout" ),
168            "Runs the specified command in the chroot of the target system.\n"
169            "Returns 0, which is program's exit code if the program exited "
170            "successfully, or raises a subprocess.CalledProcessError."
171        )
172    );
173    bp::def(
174        "check_target_env_call",
175        static_cast< int (*)( const bp::list&,
176                              const std::string&,
177                              int ) >( &CalamaresPython::check_target_env_call ),
178        check_target_env_call_list_overloads(
179            bp::args( "args",
180                      "stdin",
181                      "timeout" ),
182            "Runs the specified command in the chroot of the target system.\n"
183            "Returns 0, which is program's exit code if the program exited "
184            "successfully, or raises a subprocess.CalledProcessError."
185        )
186    );
187
188    bp::def(
189        "check_target_env_output",
190        static_cast< std::string (*)( const std::string&,
191                                      const std::string&,
192                                      int ) >( &CalamaresPython::check_target_env_output ),
193        check_target_env_output_str_overloads(
194            bp::args( "command",
195                      "stdin",
196                      "timeout" ),
197            "Runs the specified command in the chroot of the target system.\n"
198            "Returns the program's standard output, and raises a "
199            "subprocess.CalledProcessError if something went wrong."
200        )
201    );
202    bp::def(
203        "check_target_env_output",
204        static_cast< std::string (*)( const bp::list&,
205                                      const std::string&,
206                                      int ) >( &CalamaresPython::check_target_env_output ),
207        check_target_env_output_list_overloads(
208            bp::args( "args",
209                      "stdin",
210                      "timeout" ),
211            "Runs the specified command in the chroot of the target system.\n"
212            "Returns the program's standard output, and raises a "
213            "subprocess.CalledProcessError if something went wrong."
214        )
215    );
216    bp::def(
217        "obscure",
218        &CalamaresPython::obscure,
219        bp::args( "s" ),
220        "Simple string obfuscation function based on KStringHandler::obscure.\n"
221        "Returns a string, generated using a simple symmetric encryption.\n"
222        "Applying the function to a string obscured by this function will result "
223        "in the original string."
224    );
225
226
227    bp::def(
228        "gettext_languages",
229        &CalamaresPython::gettext_languages,
230        "Returns list of languages (most to least-specific) for gettext."
231    );
232
233    bp::def(
234        "gettext_path",
235        &CalamaresPython::gettext_path,
236        "Returns path for gettext search."
237    );
238}
239
240
241namespace Calamares {
242
243
244PythonJob::PythonJob( const QString& scriptFile,
245                      const QString& workingPath,
246                      const QVariantMap& moduleConfiguration,
247                      QObject* parent )
248    : Job( parent )
249    , m_scriptFile( scriptFile )
250    , m_workingPath( workingPath )
251    , m_description()
252    , m_configurationMap( moduleConfiguration )
253{
254}
255
256
257PythonJob::~PythonJob()
258{}
259
260
261QString
262PythonJob::prettyName() const
263{
264    return QDir( m_workingPath ).dirName();
265}
266
267
268QString
269PythonJob::prettyStatusMessage() const
270{
271    if ( m_description.isEmpty() )
272        return tr( "Running %1 operation." )
273                .arg( QDir( m_workingPath ).dirName() );
274    else
275        return m_description;
276}
277
278
279JobResult
280PythonJob::exec()
281{
282    // We assume m_scriptFile to be relative to m_workingPath.
283    QDir workingDir( m_workingPath );
284    if ( !workingDir.exists() ||
285         !workingDir.isReadable() )
286    {
287        return JobResult::error( tr( "Bad working directory path" ),
288                                 tr( "Working directory %1 for python job %2 is not readable." )
289                                    .arg( m_workingPath )
290                                    .arg( prettyName() ) );
291    }
292
293    QFileInfo scriptFI( workingDir.absoluteFilePath( m_scriptFile ) );
294    if ( !scriptFI.exists() ||
295         !scriptFI.isFile() ||
296         !scriptFI.isReadable() )
297    {
298        return JobResult::error( tr( "Bad main script file" ),
299                                 tr( "Main script file %1 for python job %2 is not readable." )
300                                    .arg( scriptFI.absoluteFilePath() )
301                                    .arg( prettyName() ) );
302    }
303
304    try
305    {
306        bp::dict scriptNamespace = helper()->createCleanNamespace();
307
308        bp::object calamaresModule = bp::import( "libcalamares" );
309        bp::dict calamaresNamespace = bp::extract< bp::dict >( calamaresModule.attr( "__dict__" ) );
310
311        calamaresNamespace[ "job" ] = CalamaresPython::PythonJobInterface( this );
312        calamaresNamespace[ "globalstorage" ] = CalamaresPython::GlobalStoragePythonWrapper(
313                                                JobQueue::instance()->globalStorage() );
314
315        bp::object execResult = bp::exec_file( scriptFI.absoluteFilePath().toLocal8Bit().data(),
316                                           scriptNamespace,
317                                           scriptNamespace );
318
319        bp::object entryPoint = scriptNamespace[ "run" ];
320        bp::object prettyNameFunc = scriptNamespace.get("pretty_name", bp::object());
321
322        cDebug() << "Job file" << scriptFI.absoluteFilePath();
323        if ( !prettyNameFunc.is_none() )
324        {
325            bp::extract< std::string > prettyNameResult( prettyNameFunc() );
326            if ( prettyNameResult.check() )
327            {
328                m_description = QString::fromStdString( prettyNameResult() ).trimmed();
329            }
330            if ( !m_description.isEmpty() )
331            {
332                cDebug() << "Job description from pretty_name" << prettyName() << "=" << m_description;
333                emit progress( 0 );
334            }
335        }
336
337        if ( m_description.isEmpty() )
338        {
339            bp::extract< std::string > entryPoint_doc_attr(entryPoint.attr( "__doc__" ) );
340
341            if ( entryPoint_doc_attr.check() )
342            {
343                m_description = QString::fromStdString( entryPoint_doc_attr() ).trimmed();
344                auto i_newline = m_description.indexOf('\n');
345                if ( i_newline > 0 )
346                    m_description.truncate( i_newline );
347                cDebug() << "Job description from __doc__" << prettyName() << "=" << m_description;
348                emit progress( 0 );
349            }
350        }
351
352        bp::object runResult = entryPoint();
353
354        if ( runResult.is_none() )
355        {
356            return JobResult::ok();
357        }
358        else // Something happened in the Python job
359        {
360            bp::tuple resultTuple = bp::extract< bp::tuple >( runResult );
361            QString message = QString::fromStdString( bp::extract< std::string >( resultTuple[ 0 ] ) );
362            QString description = QString::fromStdString( bp::extract< std::string >( resultTuple[ 1 ] ) );
363            return JobResult::error( message, description );
364        }
365    }
366    catch ( bp::error_already_set )
367    {
368        QString msg;
369        if ( PyErr_Occurred() )
370        {
371            msg = helper()->handleLastError();
372        }
373        bp::handle_exception();
374        PyErr_Clear();
375        return JobResult::error( tr( "Boost.Python error in job \"%1\"." ).arg( prettyName() ),
376                                 msg );
377    }
378}
379
380
381void
382PythonJob::emitProgress( qreal progressValue )
383{
384    emit progress( progressValue );
385}
386
387
388CalamaresPython::Helper*
389PythonJob::helper()
390{
391    auto ptr = CalamaresPython::Helper::s_instance;
392    if (!ptr)
393        ptr = new CalamaresPython::Helper;
394    return ptr;
395}
396
397
398} // namespace Calamares
Note: See TracBrowser for help on using the repository browser.