Improve error reporting when running Python action plugins.

Python error traceback is now logged to stderr.
On Windows, a console window will pop up if wxTheApp gets destroyed,
because message dialogs crash due to hooks that wxWidgets sets up.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/15520
This commit is contained in:
Alex Shvartzkop 2023-08-29 07:05:46 +03:00
parent b43f037a91
commit a9a2d4aa7a
6 changed files with 124 additions and 44 deletions

View File

@ -48,6 +48,13 @@ bool KIPLATFORM::APP::Init()
} }
bool KIPLATFORM::APP::AttachConsole( bool aTryAlloc )
{
// Not implemented on this platform
return true;
}
bool KIPLATFORM::APP::IsOperatingSystemUnsupported() bool KIPLATFORM::APP::IsOperatingSystemUnsupported()
{ {
// Not implemented on this platform // Not implemented on this platform

View File

@ -36,6 +36,14 @@ namespace KIPLATFORM
*/ */
bool Init(); bool Init();
/**
* Tries to attach a console window with stdout, stderr and stdin.
*
* @param aTryAlloc try to allocate the console if cannot attach to it.
* @return true if attach successful, false if unsuccessful
*/
bool AttachConsole( bool aTryAlloc );
/** /**
* Checks if the Operating System is explicitly unsupported and we want to prevent * Checks if the Operating System is explicitly unsupported and we want to prevent
* users from sending bug reports and show them a disclaimer on startup. * users from sending bug reports and show them a disclaimer on startup.

View File

@ -84,43 +84,7 @@ bool KIPLATFORM::APP::Init()
// In order to support GUI and CLI // In order to support GUI and CLI
// Let's attach to console when it's possible, or allocate if requested. // Let's attach to console when it's possible, or allocate if requested.
bool tryAlloc = wxGetEnv( wxS( "KICAD_ALLOC_CONSOLE" ), nullptr ); AttachConsole( wxGetEnv( wxS( "KICAD_ALLOC_CONSOLE" ), nullptr ) );
HANDLE handle;
if( AttachConsole( ATTACH_PARENT_PROCESS ) || ( tryAlloc && AllocConsole() ) )
{
#if !defined( __MINGW32__ ) // These redirections create problems on mingw:
// Nothing is printed to the console
if( GetStdHandle( STD_INPUT_HANDLE ) != INVALID_HANDLE_VALUE )
{
freopen( "CONIN$", "r", stdin );
setvbuf( stdin, NULL, _IONBF, 0 );
}
if( GetStdHandle( STD_OUTPUT_HANDLE ) != INVALID_HANDLE_VALUE )
{
freopen( "CONOUT$", "w", stdout );
setvbuf( stdout, NULL, _IONBF, 0 );
}
if( GetStdHandle( STD_ERROR_HANDLE ) != INVALID_HANDLE_VALUE )
{
freopen( "CONOUT$", "w", stderr );
setvbuf( stderr, NULL, _IONBF, 0 );
}
#endif
std::ios::sync_with_stdio( true );
std::wcout.clear();
std::cout.clear();
std::wcerr.clear();
std::cerr.clear();
std::wcin.clear();
std::cin.clear();
}
// It may be useful to log up to traces in a console, but in Release builds the log level changes to Info // It may be useful to log up to traces in a console, but in Release builds the log level changes to Info
// Also we have to force the active target to stderr or else it goes to the void // Also we have to force the active target to stderr or else it goes to the void
@ -138,6 +102,48 @@ bool KIPLATFORM::APP::Init()
} }
bool KIPLATFORM::APP::AttachConsole( bool aTryAlloc )
{
if( ::AttachConsole( ATTACH_PARENT_PROCESS ) || ( aTryAlloc && ::AllocConsole() ) )
{
#if !defined( __MINGW32__ ) // These redirections create problems on mingw:
// Nothing is printed to the console
if( ::GetStdHandle( STD_INPUT_HANDLE ) != INVALID_HANDLE_VALUE )
{
freopen( "CONIN$", "r", stdin );
setvbuf( stdin, NULL, _IONBF, 0 );
}
if( ::GetStdHandle( STD_OUTPUT_HANDLE ) != INVALID_HANDLE_VALUE )
{
freopen( "CONOUT$", "w", stdout );
setvbuf( stdout, NULL, _IONBF, 0 );
}
if( ::GetStdHandle( STD_ERROR_HANDLE ) != INVALID_HANDLE_VALUE )
{
freopen( "CONOUT$", "w", stderr );
setvbuf( stderr, NULL, _IONBF, 0 );
}
#endif
std::ios::sync_with_stdio( true );
std::wcout.clear();
std::cout.clear();
std::wcerr.clear();
std::cerr.clear();
std::wcin.clear();
std::cin.clear();
return true;
}
return false;
}
bool KIPLATFORM::APP::IsOperatingSystemUnsupported() bool KIPLATFORM::APP::IsOperatingSystemUnsupported()
{ {
#if defined( PYTHON_VERSION_MAJOR ) && ( ( PYTHON_VERSION_MAJOR == 3 && PYTHON_VERSION_MINOR >= 8 ) \ #if defined( PYTHON_VERSION_MAJOR ) && ( ( PYTHON_VERSION_MAJOR == 3 && PYTHON_VERSION_MINOR >= 8 ) \

View File

@ -32,6 +32,13 @@ bool KIPLATFORM::APP::Init()
} }
bool KIPLATFORM::APP::AttachConsole( bool aTryAlloc )
{
// Not implemented on this platform
return true;
}
bool KIPLATFORM::APP::IsOperatingSystemUnsupported() bool KIPLATFORM::APP::IsOperatingSystemUnsupported()
{ {
// Not implemented on this platform // Not implemented on this platform

View File

@ -1,7 +1,7 @@
/* /*
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 2017-2023 KiCad Developers, see AUTHORS.txt for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -41,6 +41,7 @@
#include <tools/pcb_selection_tool.h> #include <tools/pcb_selection_tool.h>
#include <pcb_painter.h> #include <pcb_painter.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <kiplatform/app.h>
#include "../../scripting/python_scripting.h" #include "../../scripting/python_scripting.h"
PYTHON_ACTION_PLUGIN::PYTHON_ACTION_PLUGIN( PyObject* aAction ) PYTHON_ACTION_PLUGIN::PYTHON_ACTION_PLUGIN( PyObject* aAction )
@ -73,11 +74,57 @@ PyObject* PYTHON_ACTION_PLUGIN::CallMethod( const char* aMethod, PyObject* aArgl
{ {
PyObject* result = PyObject_CallObject( pFunc, aArglist ); PyObject* result = PyObject_CallObject( pFunc, aArglist );
if( !wxTheApp )
KIPLATFORM::APP::AttachConsole( true );
if( PyErr_Occurred() ) if( PyErr_Occurred() )
{ {
wxMessageBox( PyErrStringWithTraceback(), wxString message = _HKI( "Exception on python action plugin code" );
_( "Exception on python action plugin code" ), wxString traceback = PyErrStringWithTraceback();
wxICON_ERROR | wxOK );
std::cerr << message << std::endl << std::endl;
std::cerr << traceback << std::endl;
if( wxTheApp )
wxSafeShowMessage( wxGetTranslation( message ), traceback );
}
if( !wxTheApp )
{
wxArrayString messages;
messages.Add( "Fatal error");
messages.Add( wxString::Format(
"The application handle was destroyed after running Python plugin '%s'.",
m_cachedName ) );
// Poor man's ASCII message box
{
int maxLen = 0;
for( const wxString& msg : messages )
if( msg.length() > maxLen )
maxLen = msg.length();
wxChar ch = '*';
wxString border( ch, 3 );
std::cerr << wxString( ch, maxLen + 2 + border.length() * 2 ) << std::endl;
std::cerr << border << ' ' << wxString( ' ', maxLen ) << ' ' << border << std::endl;
for( wxString msg : messages )
std::cerr << border << ' ' << msg.Pad( maxLen - msg.length() ) << ' ' << border
<< std::endl;
std::cerr << border << ' ' << wxString( ' ', maxLen ) << ' ' << border << std::endl;
std::cerr << wxString( ch, maxLen + 2 + border.length() * 2 ) << std::endl;
}
#ifdef _WIN32
std::cerr << std::endl << "Press any key to abort..." << std::endl;
(void) std::getchar();
#endif
abort();
} }
if( result ) if( result )
@ -127,7 +174,10 @@ wxString PYTHON_ACTION_PLUGIN::GetName()
{ {
PyLOCK lock; PyLOCK lock;
return CallRetStrMethod( "GetName" ); wxString name = CallRetStrMethod( "GetName" );
m_cachedName = name;
return name;
} }

View File

@ -1,7 +1,7 @@
/* /*
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 2017-2023 KiCad Developers, see AUTHORS.txt for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -52,9 +52,11 @@ public:
void* GetObject() override; void* GetObject() override;
private: private:
wxString m_cachedName;
PyObject* m_PyAction; PyObject* m_PyAction;
PyObject* CallMethod( const char* aMethod, PyObject* aArglist = nullptr ); PyObject* CallMethod( const char* aMethod, PyObject* aArglist = nullptr );
wxString CallRetStrMethod( const char* aMethod, PyObject* aArglist = nullptr ); wxString CallRetStrMethod( const char* aMethod, PyObject* aArglist = nullptr );
}; };