New kicad-cli will now be the cli interface

This commit is contained in:
Marek Roszko 2022-10-04 20:06:50 -04:00
parent 85623656a6
commit 3fe004fd1b
16 changed files with 686 additions and 250 deletions

View File

@ -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

View File

@ -18,13 +18,15 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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;
}

View File

@ -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;
};

View File

@ -18,14 +18,16 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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;
}

View File

@ -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;
};

View File

@ -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 );

View File

@ -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(), "" );

View File

@ -40,7 +40,6 @@
#include <wx/stdpaths.h>
#include <wx/snglinst.h>
#include <wx/html/htmlwin.h>
#include <argparse/argparse.hpp>
#include <kiway.h>
#include <pgm_base.h>
@ -49,14 +48,9 @@
#include <confirm.h>
#include <settings/settings_manager.h>
#include <kicad_build_version.h>
#include <kiplatform/app.h>
#include <kiplatform/environment.h>
#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<COMMAND_ENTRY> subCommands;
COMMAND_ENTRY( CLI::COMMAND* aHandler ) : handler( aHandler ){};
COMMAND_ENTRY( CLI::COMMAND* aHandler, std::vector<COMMAND_ENTRY> 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<COMMAND_ENTRY> commandStack = {
{ &exportCmd, { &stepCmd, &svgCmd } }
};
#else
static std::vector<COMMAND_ENTRY> 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<wxString> 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

View File

@ -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
$<TARGET_PROPERTY:thread-pool,INTERFACE_INCLUDE_DIRECTORIES>
)
install( TARGETS kicad
target_include_directories( kicad-cli PRIVATE
$<TARGET_PROPERTY:thread-pool,INTERFACE_INCLUDE_DIRECTORIES>
)
install( TARGETS kicad kicad-cli
DESTINATION ${KICAD_BIN}
COMPONENT binary
)
@ -107,14 +147,6 @@ if( KICAD_WIN32_INSTALL_PDBS )
install(FILES $<TARGET_PDB_FILE:kicad> 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" "$<TARGET_FILE_DIR:kicad>"
)
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

View File

@ -33,6 +33,7 @@
#include <wx/app.h>
#include <wx/stdpaths.h>
#include <wx/msgdlg.h>
#include <wx/cmdline.h>
#include <filehistory.h>
#include <hotkeys_basic.h>
@ -51,13 +52,9 @@
#include "pgm_kicad.h"
#include "kicad_manager_frame.h"
#include <kicad_build_version.h>
#include <kiplatform/app.h>
#include <kiplatform/environment.h>
#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 <kiface_base.h>
@ -93,25 +90,9 @@ PGM_KICAD& PgmTop()
return program;
}
struct COMMAND_ENTRY
{
CLI::COMMAND* handler;
std::vector<COMMAND_ENTRY> subCommands;
COMMAND_ENTRY( CLI::COMMAND* aHandler ) : handler( aHandler ){};
COMMAND_ENTRY( CLI::COMMAND* aHandler, std::vector<COMMAND_ENTRY> aSub ) :
handler( aHandler ), subCommands( aSub ){};
};
static CLI::EXPORT_STEP_COMMAND stepCmd{};
static CLI::EXPORT_KICAD_PCBNEW_COMMAND exportPcbCmd{};
static std::vector<COMMAND_ENTRY> 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<KICAD_SETTINGS*>( 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<wxString> 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();

432
kicad/kicad_cli.cpp Normal file
View File

@ -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 <wx/filename.h>
#include <wx/log.h>
#include <wx/app.h>
#include <wx/stdpaths.h>
#include <wx/msgdlg.h>
#include <kiway.h>
#include <macros.h>
#include <paths.h>
#include <settings/settings_manager.h>
#include <settings/kicad_settings.h>
#include <systemdirsappend.h>
#include <trace_helpers.h>
#include <stdexcept>
#include "pgm_kicad.h"
#include "kicad_manager_frame.h"
#include <kicad_build_version.h>
#include <kiplatform/app.h>
#include <kiplatform/environment.h>
#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.h>
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<COMMAND_ENTRY> subCommands;
COMMAND_ENTRY( CLI::COMMAND* aHandler ) : handler( aHandler ){};
COMMAND_ENTRY( CLI::COMMAND* aHandler, std::vector<COMMAND_ENTRY> 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<COMMAND_ENTRY> 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<wxShowEvent&>( aEvent );
wxDialog* dialog = dynamic_cast<wxDialog*>( 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<wxKeyEvent*>( &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();
}

View File

@ -48,6 +48,7 @@ public:
bool OnPgmInit();
void OnPgmExit();
int OnPgmRun();
void MacOpenFile( const wxString& aFileName ) override;

View File

@ -756,12 +756,6 @@ if( WIN32 )
add_custom_command( TARGET pcbnew POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:kicad_3dsg>" "$<TARGET_FILE_DIR:pcbnew>"
)
add_custom_command( TARGET pcbnew POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/resources/msw/cmd-wrappers/pcbnew.cmd" "$<TARGET_FILE_DIR:pcbnew>"
)
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:

View File

@ -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" ) );

View File

@ -1 +0,0 @@
@"%~dp0kicad.exe" %*

View File

@ -1 +0,0 @@
@"%~dp0pcbnew.exe" %*