From 3fe004fd1b3b8b678d6266c52ba7e50f583dd29c Mon Sep 17 00:00:00 2001 From: Marek Roszko Date: Tue, 4 Oct 2022 20:06:50 -0400 Subject: [PATCH] New kicad-cli will now be the cli interface --- common/CMakeLists.txt | 4 +- ...mand_export_pcbnew.cpp => command_pcb.cpp} | 10 +- ...{command_export_pcbnew.h => command_pcb.h} | 4 +- ...icad_pcbnew.cpp => command_pcb_export.cpp} | 10 +- ...rt_kicad_pcbnew.h => command_pcb_export.h} | 4 +- common/pgm_base.cpp | 7 +- common/settings/settings_manager.cpp | 2 +- common/single_top.cpp | 126 +---- kicad/CMakeLists.txt | 52 ++- kicad/kicad.cpp | 265 ++++++----- kicad/kicad_cli.cpp | 432 ++++++++++++++++++ kicad/pgm_kicad.h | 1 + pcbnew/CMakeLists.txt | 6 - pcbnew/dialogs/dialog_export_step.cpp | 11 +- resources/msw/cmd-wrappers/kicad.cmd | 1 - resources/msw/cmd-wrappers/pcbnew.cmd | 1 - 16 files changed, 686 insertions(+), 250 deletions(-) rename common/cli/{command_export_pcbnew.cpp => command_pcb.cpp} (82%) rename common/cli/{command_export_pcbnew.h => command_pcb.h} (92%) rename common/cli/{command_export_kicad_pcbnew.cpp => command_pcb_export.cpp} (80%) rename common/cli/{command_export_kicad_pcbnew.h => command_pcb_export.h} (91%) create mode 100644 kicad/kicad_cli.cpp delete mode 100644 resources/msw/cmd-wrappers/kicad.cmd delete mode 100644 resources/msw/cmd-wrappers/pcbnew.cmd diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 2680b39ac1..53a9e0f1b2 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -287,9 +287,9 @@ set( COMMON_SRCS ${PLUGINS_CADSTAR_SRCS} ${PLUGINS_EAGLE_SRCS} ${FONT_SRCS} - cli/command_export_kicad_pcbnew.cpp cli/command_export_pcb_svg.cpp - cli/command_export_pcbnew.cpp + cli/command_pcb.cpp + cli/command_pcb_export.cpp cli/command_export_step.cpp jobs/job_export_step.cpp jobs/job_dispatcher.cpp diff --git a/common/cli/command_export_pcbnew.cpp b/common/cli/command_pcb.cpp similarity index 82% rename from common/cli/command_export_pcbnew.cpp rename to common/cli/command_pcb.cpp index 309f092997..88bb5f510f 100644 --- a/common/cli/command_export_pcbnew.cpp +++ b/common/cli/command_pcb.cpp @@ -18,13 +18,15 @@ * with this program. If not, see . */ -#include "command_export_pcbnew.h" +#include "command_pcb.h" -CLI::EXPORT_PCBNEW_COMMAND::EXPORT_PCBNEW_COMMAND() : COMMAND( "export" ) +CLI::PCB_COMMAND::PCB_COMMAND() : COMMAND( "pcb" ) { } -int CLI::EXPORT_PCBNEW_COMMAND::Perform( KIWAY& aKiway ) const +int CLI::PCB_COMMAND::Perform( KIWAY& aKiway ) const { - return 0; + std::cout << m_argParser; + + return 1; } \ No newline at end of file diff --git a/common/cli/command_export_pcbnew.h b/common/cli/command_pcb.h similarity index 92% rename from common/cli/command_export_pcbnew.h rename to common/cli/command_pcb.h index eb797ee169..b3ab2c4174 100644 --- a/common/cli/command_export_pcbnew.h +++ b/common/cli/command_pcb.h @@ -25,9 +25,9 @@ namespace CLI { -struct EXPORT_PCBNEW_COMMAND : public COMMAND +struct PCB_COMMAND : public COMMAND { - EXPORT_PCBNEW_COMMAND(); + PCB_COMMAND(); int Perform( KIWAY& aKiway ) const override; }; diff --git a/common/cli/command_export_kicad_pcbnew.cpp b/common/cli/command_pcb_export.cpp similarity index 80% rename from common/cli/command_export_kicad_pcbnew.cpp rename to common/cli/command_pcb_export.cpp index 126e660a02..aaa02863eb 100644 --- a/common/cli/command_export_kicad_pcbnew.cpp +++ b/common/cli/command_pcb_export.cpp @@ -18,14 +18,16 @@ * with this program. If not, see . */ -#include "command_export_kicad_pcbnew.h" +#include "command_pcb_export.h" -CLI::EXPORT_KICAD_PCBNEW_COMMAND::EXPORT_KICAD_PCBNEW_COMMAND() : COMMAND( "export-pcb" ) +CLI::EXPORT_PCB_COMMAND::EXPORT_PCB_COMMAND() : COMMAND( "export" ) { } -int CLI::EXPORT_KICAD_PCBNEW_COMMAND::Perform( KIWAY& aKiway ) const +int CLI::EXPORT_PCB_COMMAND::Perform( KIWAY& aKiway ) const { - return 0; + std::cout << m_argParser; + + return 1; } \ No newline at end of file diff --git a/common/cli/command_export_kicad_pcbnew.h b/common/cli/command_pcb_export.h similarity index 91% rename from common/cli/command_export_kicad_pcbnew.h rename to common/cli/command_pcb_export.h index efb9048f19..eed2fae71d 100644 --- a/common/cli/command_export_kicad_pcbnew.h +++ b/common/cli/command_pcb_export.h @@ -25,9 +25,9 @@ namespace CLI { -struct EXPORT_KICAD_PCBNEW_COMMAND : public COMMAND +struct EXPORT_PCB_COMMAND : public COMMAND { - EXPORT_KICAD_PCBNEW_COMMAND(); + EXPORT_PCB_COMMAND(); int Perform( KIWAY& aKiway ) const override; }; diff --git a/common/pgm_base.cpp b/common/pgm_base.cpp index 6263434e94..4bfda6435e 100644 --- a/common/pgm_base.cpp +++ b/common/pgm_base.cpp @@ -506,8 +506,11 @@ bool PGM_BASE::InitPgm( bool aHeadless, bool aSkipPyInit ) // This sets the maximum tooltip display duration to 10s (up from 5) but only affects // Windows as other platforms display tooltips while the mouse is not moving - wxToolTip::Enable( true ); - wxToolTip::SetAutoPop( 10000 ); + if( !aHeadless ) + { + wxToolTip::Enable( true ); + wxToolTip::SetAutoPop( 10000 ); + } if( ADVANCED_CFG::GetCfg().m_UpdateUIEventInterval != 0 ) wxUpdateUIEvent::SetUpdateInterval( ADVANCED_CFG::GetCfg().m_UpdateUIEventInterval ); diff --git a/common/settings/settings_manager.cpp b/common/settings/settings_manager.cpp index 83553ebd9a..b4f9fe79f7 100644 --- a/common/settings/settings_manager.cpp +++ b/common/settings/settings_manager.cpp @@ -483,7 +483,7 @@ bool SETTINGS_MANAGER::MigrateIfNeeded() if( m_headless ) { wxLogTrace( traceSettings, wxT( "Settings migration not checked; running headless" ) ); - return false; + return true; } wxFileName path( GetUserSettingsPath(), "" ); diff --git a/common/single_top.cpp b/common/single_top.cpp index a81f38a138..4d910a1ccb 100644 --- a/common/single_top.cpp +++ b/common/single_top.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include @@ -49,14 +48,9 @@ #include #include -#include #include #include -#include "cli/command_export_pcbnew.h" -#include "cli/command_export_pcb_svg.h" -#include "cli/command_export_step.h" -#include "cli/exit_codes.h" // Only a single KIWAY is supported in this single_top top level component, // which is dedicated to loading only a single DSO. @@ -280,36 +274,8 @@ struct APP_SINGLE_TOP : public wxApp IMPLEMENT_APP( APP_SINGLE_TOP ) - -struct COMMAND_ENTRY -{ - CLI::COMMAND* handler; - - std::vector subCommands; - - COMMAND_ENTRY( CLI::COMMAND* aHandler ) : handler( aHandler ){}; - COMMAND_ENTRY( CLI::COMMAND* aHandler, std::vector aSub ) : - handler( aHandler ), subCommands( aSub ) {}; -}; - -#ifdef PCBNEW -static CLI::EXPORT_STEP_COMMAND stepCmd{}; -static CLI::EXPORT_PCB_SVG_COMMAND svgCmd{}; -static CLI::EXPORT_PCBNEW_COMMAND exportCmd{}; - -static std::vector commandStack = { - { &exportCmd, { &stepCmd, &svgCmd } } -}; -#else - -static std::vector commandStack = { -}; -#endif - bool PGM_SINGLE_TOP::OnPgmInit() { - PGM_BASE::BuildArgvUtf8(); - #if defined(DEBUG) wxString absoluteArgv0 = wxStandardPaths::Get().GetExecutablePath(); @@ -319,60 +285,10 @@ bool PGM_SINGLE_TOP::OnPgmInit() return false; } #endif - wxString pgm_name; - if( App().argc == 0 ) - pgm_name = wxT( "kicad" ); - else - pgm_name = wxFileName( App().argv[0] ).GetName().Lower(); - - argparse::ArgumentParser argParser( std::string( pgm_name.utf8_str() ), KICAD_MAJOR_MINOR_VERSION ); - - for(COMMAND_ENTRY& entry : commandStack) - { - argParser.add_subparser( entry.handler->GetArgParser() ); - - for( COMMAND_ENTRY& subentry : entry.subCommands ) - { - entry.handler->GetArgParser().add_subparser( subentry.handler->GetArgParser() ); - } - } - - try - { - argParser.parse_args( m_argcUtf8, m_argvUtf8 ); - } - catch( const std::runtime_error& ) - { - // Ignore any argParser "errors" - // unforunately there are cases like the only arg being a file (double click open) - // that we need to fall through - } - - bool cliCmdRequested = false; - CLI::COMMAND* cliCmd = nullptr; - for( COMMAND_ENTRY& entry : commandStack ) - { - if( argParser.is_subcommand_used( entry.handler->GetName() ) ) - { - for( COMMAND_ENTRY& subentry : entry.subCommands ) - { - if( entry.handler->GetArgParser().is_subcommand_used( subentry.handler->GetName() ) ) - { - cliCmd = subentry.handler; - cliCmdRequested = true; - } - } - - if( !cliCmdRequested ) - { - cliCmd = entry.handler; - } - } - } // Not all kicad applications use the python stuff. skip python init // for these apps. - bool skip_python_initialization = cliCmdRequested; + bool skip_python_initialization = false; #if defined( BITMAP_2_CMP ) || defined( PL_EDITOR ) || defined( GERBVIEW ) ||\ defined( PCB_CALCULATOR_BUILD ) skip_python_initialization = true; @@ -404,33 +320,13 @@ bool PGM_SINGLE_TOP::OnPgmInit() Kiway.set_kiface( KIWAY::KifaceType( TOP_FRAME ), kiface ); #endif - FRAME_T appType = TOP_FRAME; - // Tell the settings manager about the current Kiway GetSettingsManager().SetKiway( &Kiway ); - if( cliCmdRequested ) - { - int exitCode = CLI::EXIT_CODES::ERR_UNKNOWN; - if( cliCmd ) - { - exitCode = cliCmd->Perform( Kiway ); - } - - if( exitCode != CLI::EXIT_CODES::AVOID_CLOSING ) - { - std::exit( exitCode ); - } - else - { - return true; - } - } - // Use KIWAY to create a top window, which registers its existence also. // "TOP_FRAME" is a macro that is passed on compiler command line from CMake, // and is one of the types in FRAME_T. - KIWAY_PLAYER* frame = Kiway.Player( appType, true ); + KIWAY_PLAYER* frame = Kiway.Player( TOP_FRAME, true ); if( frame == nullptr ) { @@ -453,7 +349,17 @@ bool PGM_SINGLE_TOP::OnPgmInit() // Now after the frame processing, the rest of the positional args are files std::vector fileArgs; - if( App().argc > 1 ) + + static const wxCmdLineEntryDesc desc[] = { + { wxCMD_LINE_PARAM, nullptr, nullptr, "File to load", wxCMD_LINE_VAL_STRING, + wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_NONE, nullptr, nullptr, nullptr, wxCMD_LINE_VAL_NONE, 0 } + }; + + wxCmdLineParser parser( App().argc, App().argv ); + parser.SetDesc( desc ); + parser.Parse( false ); + if( parser.GetParamCount() ) { /* gerbview handles multiple project data files, i.e. gerber files on @@ -464,15 +370,15 @@ bool PGM_SINGLE_TOP::OnPgmInit() launcher. */ - for( int i = 1; i < App().argc; i++ ) - fileArgs.push_back( App().argv[i] ); + for( size_t i = 0; i < parser.GetParamCount(); i++ ) + fileArgs.push_back( parser.GetParam( i ) ); // special attention to a single argument: argv[1] (==argSet[0]) if( fileArgs.size() == 1 ) { wxFileName argv1( fileArgs[0] ); -#if defined( PGM_DATA_FILE_EXT ) +#if defined(PGM_DATA_FILE_EXT) // PGM_DATA_FILE_EXT, if present, may be different for each compile, // it may come from CMake on the compiler command line, but often does not. // This facility is mostly useful for those program footprints diff --git a/kicad/CMakeLists.txt b/kicad/CMakeLists.txt index c3f292f8b7..38961937bc 100644 --- a/kicad/CMakeLists.txt +++ b/kicad/CMakeLists.txt @@ -23,7 +23,6 @@ set( KICAD_SRCS files-io.cpp import_proj.cpp import_project.cpp - kicad.cpp kicad_manager_frame.cpp menubar.cpp project_template.cpp @@ -57,11 +56,29 @@ if( APPLE ) endif() add_executable( kicad WIN32 MACOSX_BUNDLE + kicad.cpp ${KICAD_SRCS} ${KICAD_EXTRA_SRCS} ${KICAD_RESOURCES} ) +add_executable( kicad-cli WIN32 MACOSX_BUNDLE + kicad_cli.cpp + ${KICAD_SRCS} + ${KICAD_EXTRA_SRCS} + ${KICAD_RESOURCES} + ) + +if( MSVC ) + # The cli needs subsystem:console or else we can't link wmain/main + set_target_properties(kicad-cli PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE") + set_target_properties(kicad-cli PROPERTIES COMPILE_DEFINITIONS_RELWITHDEBINFO "_CONSOLE") + set_target_properties(kicad-cli PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") + set_target_properties(kicad-cli PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") + set_target_properties(kicad-cli PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE") + set_target_properties(kicad-cli PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE") +endif() + if( UNIX ) # for build directory: create kiface symlinks so kicad (exe) can be run in-situ add_custom_target( kiface_sym_links @@ -81,6 +98,16 @@ if( APPLE ) common ${wxWidgets_LIBRARIES} ) + + set_target_properties( kicad-cli PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${PROJECT_BINARY_DIR}/kicad/Info.plist + ) + + target_link_libraries( kicad-cli + nlohmann_json + common + ${wxWidgets_LIBRARIES} + ) else() target_link_libraries( kicad nlohmann_json @@ -89,15 +116,28 @@ else() common #repeated due to a circular dependency between gal and common ${wxWidgets_LIBRARIES} ) + + target_link_libraries( kicad-cli + nlohmann_json + common + gal + common #repeated due to a circular dependency between gal and common + ${wxWidgets_LIBRARIES} + ) endif() target_link_libraries( kicad pcm ) +target_link_libraries( kicad-cli pcm ) target_include_directories( kicad PRIVATE $ ) -install( TARGETS kicad +target_include_directories( kicad-cli PRIVATE + $ + ) + +install( TARGETS kicad kicad-cli DESTINATION ${KICAD_BIN} COMPONENT binary ) @@ -107,14 +147,6 @@ if( KICAD_WIN32_INSTALL_PDBS ) install(FILES $ DESTINATION ${KICAD_BIN}) endif() -if( WIN32 ) - add_custom_command( TARGET kicad POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/resources/msw/cmd-wrappers/kicad.cmd" "$" - ) - - install(FILES "${CMAKE_SOURCE_DIR}/resources/msw/cmd-wrappers/kicad.cmd" DESTINATION ${KICAD_BIN}) -endif() - if( APPLE ) # "install( CODE ... )" will launch its own CMake, so no variables from # this CMake instance are accessible... use helper to transfer diff --git a/kicad/kicad.cpp b/kicad/kicad.cpp index fcccbb3ed4..5a5dbf974e 100644 --- a/kicad/kicad.cpp +++ b/kicad/kicad.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -51,13 +52,9 @@ #include "pgm_kicad.h" #include "kicad_manager_frame.h" -#include #include #include -#include "cli/command_export_kicad_pcbnew.h" -#include "cli/command_export_step.h" -#include "cli/exit_codes.h" // a dummy to quiet linking with EDA_BASE_FRAME::config(); #include @@ -93,25 +90,9 @@ PGM_KICAD& PgmTop() return program; } -struct COMMAND_ENTRY -{ - CLI::COMMAND* handler; - - std::vector subCommands; - - COMMAND_ENTRY( CLI::COMMAND* aHandler ) : handler( aHandler ){}; - COMMAND_ENTRY( CLI::COMMAND* aHandler, std::vector aSub ) : - handler( aHandler ), subCommands( aSub ){}; -}; - -static CLI::EXPORT_STEP_COMMAND stepCmd{}; -static CLI::EXPORT_KICAD_PCBNEW_COMMAND exportPcbCmd{}; - -static std::vector commandStack = { { &exportPcbCmd, { &stepCmd } } }; bool PGM_KICAD::OnPgmInit() { - PGM_BASE::BuildArgvUtf8(); App().SetAppDisplayName( wxT( "KiCad" ) ); #if defined(DEBUG) @@ -124,54 +105,60 @@ bool PGM_KICAD::OnPgmInit() } #endif - argparse::ArgumentParser argParser( std::string( "kicad" ), - KICAD_MAJOR_MINOR_VERSION ); + static const wxCmdLineEntryDesc desc[] = { + { wxCMD_LINE_OPTION, "f", "frame", "Frame to load", wxCMD_LINE_VAL_STRING, 0 }, + { wxCMD_LINE_PARAM, nullptr, nullptr, "File to load", wxCMD_LINE_VAL_STRING, + wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_NONE, nullptr, nullptr, nullptr, wxCMD_LINE_VAL_NONE, 0 } + }; - for( COMMAND_ENTRY& entry : commandStack ) + wxCmdLineParser parser( App().argc, App().argv ); + parser.SetDesc( desc ); + parser.Parse( false ); + + FRAME_T appType = KICAD_MAIN_FRAME_T; + + const struct { - argParser.add_subparser( entry.handler->GetArgParser() ); + wxString name; + FRAME_T type; + } frameTypes[] = { { wxT( "pcb" ), FRAME_PCB_EDITOR }, + { wxT( "fpedit" ), FRAME_FOOTPRINT_EDITOR }, + { wxT( "sch" ), FRAME_SCH }, + { wxT( "calc" ), FRAME_CALC }, + { wxT( "bm2cmp" ), FRAME_BM2CMP }, + { wxT( "ds" ), FRAME_PL_EDITOR }, + { wxT( "gerb" ), FRAME_GERBER }, + { wxT( "" ), FRAME_T_COUNT } }; - for( COMMAND_ENTRY& subentry : entry.subCommands ) + wxString frameName; + + if( parser.Found( "frame", &frameName ) ) + { + appType = FRAME_T_COUNT; + + for( const auto& it : frameTypes ) { - entry.handler->GetArgParser().add_subparser( subentry.handler->GetArgParser() ); + if( it.name == frameName ) + appType = it.type; + } + + if( appType == FRAME_T_COUNT ) + { + wxLogError( wxT( "Unknown frame: %s" ), frameName ); + // Clean up + OnPgmExit(); + return false; } } - try - { - argParser.parse_args( m_argcUtf8, m_argvUtf8 ); - } - catch( const std::runtime_error& err ) - { - // Ignore any argParser "errors" - // unforunately there are cases like the only arg being a file (double click open) - // that we need to fall through - } + bool skipPythonInit = false; - bool cliCmdRequested = false; - CLI::COMMAND* cliCmd = nullptr; - for( COMMAND_ENTRY& entry : commandStack ) - { - if( argParser.is_subcommand_used( entry.handler->GetName() ) ) - { - for( COMMAND_ENTRY& subentry : entry.subCommands ) - { - if( entry.handler->GetArgParser().is_subcommand_used( - subentry.handler->GetName() ) ) - { - cliCmd = subentry.handler; - cliCmdRequested = true; - } - } + if( appType == FRAME_BM2CMP || appType == FRAME_PL_EDITOR || appType == FRAME_GERBER + || appType == FRAME_CALC ) + skipPythonInit = true; - if( !cliCmdRequested ) - { - cliCmd = entry.handler; - } - } - } - - if( !InitPgm() ) + if( !InitPgm( false, skipPythonInit ) ) return false; m_bm.InitSettings( new KICAD_SETTINGS ); @@ -215,76 +202,142 @@ bool PGM_KICAD::OnPgmInit() m_bm.m_search.Insert( it->second.GetValue(), 0 ); } - if( cliCmdRequested ) - { - int exitCode = CLI::EXIT_CODES::ERR_UNKNOWN; - if( cliCmd ) - { - exitCode = cliCmd->Perform( Kiway ); - } + wxFrame* frame = nullptr; + KIWAY_PLAYER* playerFrame = nullptr; + KICAD_MANAGER_FRAME* managerFrame = nullptr; - if( exitCode != CLI::EXIT_CODES::AVOID_CLOSING ) + if( appType == KICAD_MAIN_FRAME_T ) + { + managerFrame = new KICAD_MANAGER_FRAME( nullptr, wxT( "KiCad" ), wxDefaultPosition, + wxSize( 775, -1 ) ); + frame = managerFrame; + } + else + { + // Use KIWAY to create a top window, which registers its existence also. + // "TOP_FRAME" is a macro that is passed on compiler command line from CMake, + // and is one of the types in FRAME_T. + playerFrame = Kiway.Player( appType, true ); + frame = playerFrame; + + if( frame == nullptr ) { - std::exit( exitCode ); - } - else - { - return true; + return false; } } - KICAD_MANAGER_FRAME* frame = new KICAD_MANAGER_FRAME( nullptr, wxT( "KiCad" ), - wxDefaultPosition, wxSize( 775, -1 ) ); App().SetTopWindow( frame ); + if( playerFrame ) + App().SetAppDisplayName( playerFrame->GetAboutTitle() ); + Kiway.SetTop( frame ); KICAD_SETTINGS* settings = static_cast( PgmSettings() ); wxString projToLoad; - if( App().argc > 1 ) + + if( playerFrame && parser.GetParamCount() ) { - wxFileName tmp = App().argv[1]; + // Now after the frame processing, the rest of the positional args are files + std::vector fileArgs; + /* + gerbview handles multiple project data files, i.e. gerber files on + cmd line. Others currently do not, they handle only one. For common + code simplicity we simply pass all the arguments in however, each + program module can do with them what they want, ignore, complain + whatever. We don't establish policy here, as this is a multi-purpose + launcher. + */ - if( tmp.GetExt() != ProjectFileExtension && tmp.GetExt() != LegacyProjectFileExtension ) + for( size_t i = 0; i < parser.GetParamCount(); i++ ) + fileArgs.push_back( parser.GetParam( i ) ); + + // special attention to a single argument: argv[1] (==argSet[0]) + if( fileArgs.size() == 1 ) { - wxString msg; + wxFileName argv1( fileArgs[0] ); - msg.Printf( _( "File '%s'\ndoes not appear to be a valid KiCad project file." ), - tmp.GetFullPath() ); - wxMessageDialog dlg( nullptr, msg, _( "Error" ), wxOK | wxICON_EXCLAMATION ); - dlg.ShowModal(); +#if defined( PGM_DATA_FILE_EXT ) + // PGM_DATA_FILE_EXT, if present, may be different for each compile, + // it may come from CMake on the compiler command line, but often does not. + // This facility is mostly useful for those program footprints + // supporting a single argv[1]. + if( !argv1.GetExt() ) + argv1.SetExt( wxT( PGM_DATA_FILE_EXT ) ); +#endif + argv1.MakeAbsolute(); + + fileArgs[0] = argv1.GetFullPath(); } - else + + // Use the KIWAY_PLAYER::OpenProjectFiles() API function: + if( !playerFrame->OpenProjectFiles( fileArgs ) ) { - projToLoad = tmp.GetFullPath(); + // OpenProjectFiles() API asks that it report failure to the UI. + // Nothing further to say here. + + // We've already initialized things at this point, but wx won't call OnExit if + // we fail out. Call our own cleanup routine here to ensure the relevant resources + // are freed at the right time (if they aren't, segfaults will occur). + OnPgmExit(); + + // Fail the process startup if the file could not be opened, + // although this is an optional choice, one that can be reversed + // also in the KIFACE specific OpenProjectFiles() return value. + return false; } } - - // If no file was given as an argument, check that there was a file open. - if( projToLoad.IsEmpty() && settings->m_OpenProjects.size() ) + else if( managerFrame ) { - wxString last_pro = settings->m_OpenProjects.front(); - settings->m_OpenProjects.erase( settings->m_OpenProjects.begin() ); - - if( wxFileExists( last_pro ) ) + if( App().argc > 1 ) { - // Try to open the last opened project, - // if a project name is not given when starting Kicad - projToLoad = last_pro; + wxFileName tmp = App().argv[1]; + + if( tmp.GetExt() != ProjectFileExtension && tmp.GetExt() != LegacyProjectFileExtension ) + { + wxString msg; + + msg.Printf( _( "File '%s'\ndoes not appear to be a valid KiCad project file." ), + tmp.GetFullPath() ); + wxMessageDialog dlg( nullptr, msg, _( "Error" ), wxOK | wxICON_EXCLAMATION ); + dlg.ShowModal(); + } + else + { + projToLoad = tmp.GetFullPath(); + } } - } - // Do not attempt to load a non-existent project file. - if( !projToLoad.empty() ) - { - wxFileName fn( projToLoad ); - - if( fn.Exists() ) + // If no file was given as an argument, check that there was a file open. + if( projToLoad.IsEmpty() && settings->m_OpenProjects.size() ) { - fn.MakeAbsolute(); - frame->LoadProject( fn ); + wxString last_pro = settings->m_OpenProjects.front(); + settings->m_OpenProjects.erase( settings->m_OpenProjects.begin() ); + + if( wxFileExists( last_pro ) ) + { + // Try to open the last opened project, + // if a project name is not given when starting Kicad + projToLoad = last_pro; + } + } + + // Do not attempt to load a non-existent project file. + if( !projToLoad.empty() ) + { + wxFileName fn( projToLoad ); + + if( fn.Exists() ) + { + fn.MakeAbsolute(); + + if( appType == KICAD_MAIN_FRAME_T ) + { + managerFrame->LoadProject( fn ); + } + } } } @@ -295,6 +348,12 @@ bool PGM_KICAD::OnPgmInit() } +int PGM_KICAD::OnPgmRun() +{ + return 0; +} + + void PGM_KICAD::OnPgmExit() { Kiway.OnKiwayEnd(); diff --git a/kicad/kicad_cli.cpp b/kicad/kicad_cli.cpp new file mode 100644 index 0000000000..d3235dc7fe --- /dev/null +++ b/kicad/kicad_cli.cpp @@ -0,0 +1,432 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2004-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr + * Copyright (C) 2004-2021 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file kicad.cpp + * Main KiCad project manager file. + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pgm_kicad.h" +#include "kicad_manager_frame.h" + +#include +#include +#include + +#include "cli/command_pcb.h" +#include "cli/command_pcb_export.h" +#include "cli/command_export_pcb_svg.h" +#include "cli/command_export_step.h" +#include "cli/exit_codes.h" + +// a dummy to quiet linking with EDA_BASE_FRAME::config(); +#include +KIFACE_BASE& Kiface() +{ + // This function should never be called. It is only referenced from + // EDA_BASE_FRAME::config() and this is only provided to satisfy the linker, + // not to be actually called. + wxLogFatalError( wxT( "Unexpected call to Kiface() in kicad/kicad.cpp" ) ); + + throw std::logic_error( "Unexpected call to Kiface() in kicad/kicad.cpp" ); +} + + +static PGM_KICAD program; + + +PGM_BASE& Pgm() +{ + return program; +} + + +// Similar to PGM_BASE& Pgm(), but return nullptr when a *.ki_face is run from a python script. +PGM_BASE* PgmOrNull() +{ + return &program; +} + + +PGM_KICAD& PgmTop() +{ + return program; +} + +struct COMMAND_ENTRY +{ + CLI::COMMAND* handler; + + std::vector subCommands; + + COMMAND_ENTRY( CLI::COMMAND* aHandler ) : handler( aHandler ){}; + COMMAND_ENTRY( CLI::COMMAND* aHandler, std::vector aSub ) : + handler( aHandler ), subCommands( aSub ){}; +}; + +static CLI::EXPORT_STEP_COMMAND stepCmd{}; +static CLI::EXPORT_PCB_SVG_COMMAND svgCmd{}; +static CLI::EXPORT_PCB_COMMAND exportPcbCmd{}; +static CLI::PCB_COMMAND pcbCmd{}; + +static std::vector commandStack = { + { + &pcbCmd, + { + { &exportPcbCmd, + { + &stepCmd, + &svgCmd + } + } + } + } +}; + + +static void recurseArgParserBuild( argparse::ArgumentParser& aArgParser, COMMAND_ENTRY& aEntry ) +{ + aArgParser.add_subparser( aEntry.handler->GetArgParser() ); + + for( COMMAND_ENTRY& subEntry : aEntry.subCommands ) + { + recurseArgParserBuild( aEntry.handler->GetArgParser(), subEntry ); + } +} + + +static COMMAND_ENTRY* recurseArgParserSubCommandUsed( argparse::ArgumentParser& aArgParser, + COMMAND_ENTRY& aEntry ) +{ + COMMAND_ENTRY* cliCmd = nullptr; + + if( aArgParser.is_subcommand_used( aEntry.handler->GetName() ) ) + { + for( COMMAND_ENTRY& subentry : aEntry.subCommands ) + { + cliCmd = recurseArgParserSubCommandUsed( aEntry.handler->GetArgParser(), subentry ); + if( cliCmd ) + break; + } + + if(!cliCmd) + cliCmd = &aEntry; + } + + return cliCmd; +} + + +bool PGM_KICAD::OnPgmInit() +{ + PGM_BASE::BuildArgvUtf8(); + App().SetAppDisplayName( wxT( "KiCad" ) ); + +#if defined( DEBUG ) + wxString absoluteArgv0 = wxStandardPaths::Get().GetExecutablePath(); + + if( !wxIsAbsolutePath( absoluteArgv0 ) ) + { + wxLogError( wxT( "No meaningful argv[0]" ) ); + return false; + } +#endif + + if( !InitPgm( true, true) ) + return false; + + m_bm.InitSettings( new KICAD_SETTINGS ); + GetSettingsManager().RegisterSettings( PgmSettings() ); + GetSettingsManager().SetKiway( &Kiway ); + m_bm.Init(); + + + return true; +} + + +int PGM_KICAD::OnPgmRun() +{ + argparse::ArgumentParser argParser( std::string( "kicad-cli" ), KICAD_MAJOR_MINOR_VERSION ); + + for( COMMAND_ENTRY& entry : commandStack ) + { + recurseArgParserBuild( argParser, entry ); + } + + try + { + argParser.parse_args( m_argcUtf8, m_argvUtf8 ); + } + catch( const std::runtime_error& err ) + { + std::cout << err.what() << std::endl; + std::cout << argParser; + return CLI::EXIT_CODES::ERR_ARGS; + } + + COMMAND_ENTRY* cliCmd = nullptr; + for( COMMAND_ENTRY& entry : commandStack ) + { + if( argParser.is_subcommand_used( entry.handler->GetName() ) ) + { + cliCmd = recurseArgParserSubCommandUsed( argParser, entry ); + } + } + + if( cliCmd ) + { + int exitCode = CLI::EXIT_CODES::ERR_UNKNOWN; + if( cliCmd ) + { + exitCode = cliCmd->handler->Perform( Kiway ); + } + + if( exitCode != CLI::EXIT_CODES::AVOID_CLOSING ) + { + return exitCode; + } + else + { + return 0; + } + } + else + { + std::cout << argParser; + return CLI::EXIT_CODES::ERR_ARGS; + } +} + + +void PGM_KICAD::OnPgmExit() +{ + Kiway.OnKiwayEnd(); + + if( m_settings_manager && m_settings_manager->IsOK() ) + { + SaveCommonSettings(); + m_settings_manager->Save(); + } + + // Destroy everything in PGM_KICAD, + // especially wxSingleInstanceCheckerImpl earlier than wxApp and earlier + // than static destruction would. + Destroy(); +} + + +void PGM_KICAD::MacOpenFile( const wxString& aFileName ) +{ +#if defined( __WXMAC__ ) + + KICAD_MANAGER_FRAME* frame = (KICAD_MANAGER_FRAME*) App().GetTopWindow(); + + if( !aFileName.empty() && wxFileExists( aFileName ) ) + frame->LoadProject( wxFileName( aFileName ) ); + +#endif +} + + +void PGM_KICAD::Destroy() +{ + // unlike a normal destructor, this is designed to be called more + // than once safely: + + m_bm.End(); + + PGM_BASE::Destroy(); +} + + +KIWAY Kiway( &Pgm(), KFCTL_CPP_PROJECT_SUITE ); + + +/** + * Not publicly visible because most of the action is in #PGM_KICAD these days. + */ +struct APP_KICAD_CLI : public wxAppConsole +{ + APP_KICAD_CLI() : wxAppConsole() + { + // Init the environment each platform wants + KIPLATFORM::ENV::Init(); + } + + + bool OnInit() override + { + // Perform platform-specific init tasks + if( !KIPLATFORM::APP::Init() ) + return false; + + if( !program.OnPgmInit() ) + { + program.OnPgmExit(); + return false; + } + + return true; + } + + int OnExit() override + { + program.OnPgmExit(); + +#if defined( __FreeBSD__ ) + // Avoid wxLog crashing when used in destructors. + wxLog::EnableLogging( false ); +#endif + + return wxAppConsole::OnExit(); + } + + int OnRun() override + { + try + { + return program.OnPgmRun(); + } + catch( const std::exception& e ) + { + wxLogError( wxT( "Unhandled exception class: %s what: %s" ), + FROM_UTF8( typeid( e ).name() ), FROM_UTF8( e.what() ) ); + } + catch( const IO_ERROR& ioe ) + { + wxLogError( ioe.What() ); + } + catch( ... ) + { + wxLogError( wxT( "Unhandled exception of unknown type" ) ); + } + + return -1; + } + + int FilterEvent( wxEvent& aEvent ) override + { + if( aEvent.GetEventType() == wxEVT_SHOW ) + { + wxShowEvent& event = static_cast( aEvent ); + wxDialog* dialog = dynamic_cast( event.GetEventObject() ); + + if( dialog && dialog->IsModal() ) + Pgm().m_ModalDialogCount += event.IsShown() ? 1 : -1; + } + + return Event_Skip; + } + +#if defined( DEBUG ) + /** + * Process any unhandled events at the application level. + */ + bool ProcessEvent( wxEvent& aEvent ) override + { + if( aEvent.GetEventType() == wxEVT_CHAR || aEvent.GetEventType() == wxEVT_CHAR_HOOK ) + { + wxKeyEvent* keyEvent = static_cast( &aEvent ); + + if( keyEvent ) + { + wxLogTrace( kicadTraceKeyEvent, "APP_KICAD::ProcessEvent %s", dump( *keyEvent ) ); + } + } + + aEvent.Skip(); + return false; + } + + /** + * Override main loop exception handling on debug builds. + * + * It can be painfully difficult to debug exceptions that happen in wxUpdateUIEvent + * handlers. The override provides a bit more useful information about the exception + * and a breakpoint can be set to pin point the event where the exception was thrown. + */ + bool OnExceptionInMainLoop() override + { + try + { + throw; + } + catch( const std::exception& e ) + { + wxLogError( "Unhandled exception class: %s what: %s", FROM_UTF8( typeid( e ).name() ), + FROM_UTF8( e.what() ) ); + } + catch( const IO_ERROR& ioe ) + { + wxLogError( ioe.What() ); + } + catch( ... ) + { + wxLogError( "Unhandled exception of unknown type" ); + } + + return false; // continue on. Return false to abort program + } +#endif + + /** + * Set MacOS file associations. + * + * @see http://wiki.wxwidgets.org/WxMac-specific_topics + */ +#if defined( __WXMAC__ ) + void MacOpenFile( const wxString& aFileName ) override + { + Pgm().MacOpenFile( aFileName ); + } +#endif +}; + +IMPLEMENT_APP_CONSOLE( APP_KICAD_CLI ) + + +// The C++ project manager supports one open PROJECT, so Prj() calls within +// this link image need this function. +PROJECT& Prj() +{ + return Kiway.Prj(); +} diff --git a/kicad/pgm_kicad.h b/kicad/pgm_kicad.h index e9bb87bec5..12e98c7451 100644 --- a/kicad/pgm_kicad.h +++ b/kicad/pgm_kicad.h @@ -48,6 +48,7 @@ public: bool OnPgmInit(); void OnPgmExit(); + int OnPgmRun(); void MacOpenFile( const wxString& aFileName ) override; diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 158bc232a2..03f00bb759 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -756,12 +756,6 @@ if( WIN32 ) add_custom_command( TARGET pcbnew POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$" ) - - add_custom_command( TARGET pcbnew POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/resources/msw/cmd-wrappers/pcbnew.cmd" "$" - ) - - install(FILES "${CMAKE_SOURCE_DIR}/resources/msw/cmd-wrappers/pcbnew.cmd" DESTINATION ${KICAD_BIN}) endif() # these 2 binaries are a matched set, keep them together: diff --git a/pcbnew/dialogs/dialog_export_step.cpp b/pcbnew/dialogs/dialog_export_step.cpp index 800e65b604..8ace2a5ad0 100644 --- a/pcbnew/dialogs/dialog_export_step.cpp +++ b/pcbnew/dialogs/dialog_export_step.cpp @@ -380,17 +380,24 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent ) appK2S.AppendDir( wxT( ".." ) ); appK2S.AppendDir( wxT( "MacOS" ) ); } +#else + if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) ) + { + fn.RemoveLastDir(); + fn.AppendDir( "kicad" ); + } #endif - appK2S.SetName( wxT( "pcbnew" ) ); + + appK2S.SetName( wxT( "kicad-cli" ) ); wxString cmdK2S = wxT( "\"" ); cmdK2S.Append( appK2S.GetFullPath() ); cmdK2S.Append( wxT( "\"" ) ); + cmdK2S.Append( wxT( " pcb" ) ); cmdK2S.Append( wxT( " export" ) ); cmdK2S.Append( wxT( " step" ) ); - cmdK2S.Append( wxT( " --gui" ) ); if( GetNoVirtOption() ) cmdK2S.Append( wxT( " --no-virtual" ) ); diff --git a/resources/msw/cmd-wrappers/kicad.cmd b/resources/msw/cmd-wrappers/kicad.cmd deleted file mode 100644 index 8e901e2438..0000000000 --- a/resources/msw/cmd-wrappers/kicad.cmd +++ /dev/null @@ -1 +0,0 @@ -@"%~dp0kicad.exe" %* \ No newline at end of file diff --git a/resources/msw/cmd-wrappers/pcbnew.cmd b/resources/msw/cmd-wrappers/pcbnew.cmd deleted file mode 100644 index 6530ba7d60..0000000000 --- a/resources/msw/cmd-wrappers/pcbnew.cmd +++ /dev/null @@ -1 +0,0 @@ -@"%~dp0pcbnew.exe" %* \ No newline at end of file