KiCad PCB EDA Suite
Loading...
Searching...
No Matches
jobs_runner.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2024 Mark Roszko <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <common.h>
22#include <cli/exit_codes.h>
23#include <jobs_runner.h>
24#include <jobs/job_registry.h>
25#include <jobs/jobset.h>
28#include <kiway.h>
29#include <kiway_mail.h>
30#include <reporter.h>
31#include <optional>
32#include <wx/process.h>
33#include <wx/txtstrm.h>
34#include <wx/sstream.h>
35#include <wx/wfstream.h>
36#include <gestfich.h>
37
38JOBS_RUNNER::JOBS_RUNNER( KIWAY* aKiway, JOBSET* aJobsFile, PROJECT* aProject,
39 REPORTER& aReporter, JOBS_PROGRESS_REPORTER* aProgressReporter ) :
40 m_kiway( aKiway ),
41 m_jobsFile( aJobsFile ),
42 m_reporter( aReporter ),
43 m_progressReporter( aProgressReporter ),
44 m_project( aProject )
45{
46}
47
48
50{
51 bool success = true;
52
53 for( JOBSET_DESTINATION& destination : m_jobsFile->GetDestinations() )
54 success &= RunJobsForDestination( &destination, aBail );
55
56 return success;
57}
58
59
60int JOBS_RUNNER::runSpecialExecute( const JOBSET_JOB* aJob, REPORTER* aReporter, PROJECT* aProject )
61{
62 JOB_SPECIAL_EXECUTE* specialJob = static_cast<JOB_SPECIAL_EXECUTE*>( aJob->m_job.get() );
63 wxString cmd = ExpandEnvVarSubstitutions( specialJob->m_command, m_project );
64
65 aReporter->Report( cmd, RPT_SEVERITY_INFO );
66 aReporter->Report( wxEmptyString, RPT_SEVERITY_INFO );
67
68 wxProcess process;
69 process.Redirect();
70
71 // wxExecute with a plain string argument calls execvp() directly on Unix, bypassing the
72 // shell. This means glob expansion, pipes, and other shell features don't work for direct
73 // binaries. Wrap the command in the platform shell so shell features work consistently.
74 //
75 // On Windows we use the string form of wxExecute ("cmd.exe /c " + cmd): the argv-array form
76 // of wxExecute re-escapes embedded double quotes as \" (see wxExecuteImpl in wxWidgets'
77 // src/msw/utilsexc.cpp), which cmd.exe does not understand, breaking any quoted executable
78 // path containing spaces. The string form passes the command line straight to CreateProcess
79 // without modification, preserving the user's original quoting.
80 // On Unix we use the argv-array form invoking /bin/sh -c, where the shell re-parses the
81 // joined command line and backslash-escaping is valid.
82#ifdef __WXMSW__
83 // static cast required because wx uses `long` which is 64-bit on Linux but 32-bit on Windows
84 int result = static_cast<int>(
85 wxExecute( wxS( "cmd.exe /c " ) + cmd, wxEXEC_SYNC, &process ) );
86#else
87 const wchar_t* argv[] = { wxS( "/bin/sh" ), wxS( "-c" ), cmd.wc_str(), nullptr };
88
89 // static cast required because wx uses `long` which is 64-bit on Linux but 32-bit on Windows
90 int result = static_cast<int>( wxExecute( argv, wxEXEC_SYNC, &process ) );
91#endif
92
93 wxInputStream* inputStream = process.GetInputStream();
94 wxInputStream* errorStream = process.GetErrorStream();
95
96 if( inputStream && errorStream )
97 {
98 wxTextInputStream inputTextStream( *inputStream );
99 wxTextInputStream errorTextStream( *errorStream );
100
101 while( !inputStream->Eof() )
102 aReporter->Report( inputTextStream.ReadLine(), RPT_SEVERITY_INFO );
103
104 while( !errorStream->Eof() )
105 aReporter->Report( errorTextStream.ReadLine(), RPT_SEVERITY_ERROR );
106
107 if( specialJob->m_recordOutput )
108 {
109 if( specialJob->GetConfiguredOutputPath().IsEmpty() )
110 {
111 wxFileName fn( aJob->m_id );
112 fn.SetExt( wxT( "log" ) );
113 specialJob->SetConfiguredOutputPath( fn.GetFullPath() );
114 }
115
116 wxFFileOutputStream procOutput( specialJob->GetFullOutputPath( aProject ) );
117
118 if( !procOutput.IsOk() )
120
121 inputStream->Reset();
122 *inputStream >> procOutput;
123 }
124 }
125
126 if( specialJob->m_ignoreExitcode )
127 return CLI::EXIT_CODES::OK;
128
129 return result;
130}
131
132
134 std::vector<wxString>& aPathsWritten )
135{
136 wxString source = ExpandEnvVarSubstitutions( aJob->m_source, aProject );
137
138 if( source.IsEmpty() )
140
141 wxString projectPath = aProject->GetProjectPath();
142 wxFileName sourceFn( source );
143 sourceFn.MakeAbsolute( projectPath );
144
145 wxFileName destFn( aJob->GetFullOutputPath( aProject ) );
146
147 if( !aJob->m_dest.IsEmpty() )
148 destFn.AppendDir( aJob->m_dest );
149
150 wxString errors;
151 bool success = CopyFilesOrDirectory( sourceFn.GetFullPath(), destFn.GetFullPath(), aJob->m_overwriteDest,
152 errors, aPathsWritten );
153
154 if( !success )
156
157 if( aJob->m_generateErrorOnNoCopy && aPathsWritten.empty() )
159
160 return CLI::EXIT_CODES::OK;
161}
162
163
165{
166 bool genOutputs = true;
167 bool success = true;
168 std::vector<JOBSET_JOB> jobsForDestination = m_jobsFile->GetJobsForDestination( aDestination );
169 wxString msg;
170
171 wxFileName tmp;
172 tmp.AssignDir( wxFileName::GetTempDir() );
173 tmp.AppendDir( KIID().AsString() );
174
175 aDestination->m_lastRunSuccessMap.clear();
176 aDestination->m_lastRunReporters.clear();
177 aDestination->m_lastResolvedOutputPath.reset();
178
179 wxString tempDirPath = tmp.GetFullPath();
180
181 if( !wxFileName::Mkdir( tempDirPath, wxS_DIR_DEFAULT ) )
182 {
183 msg = wxString::Format( wxT( "Failed to create temporary directory %s" ), tempDirPath );
184 m_reporter.Report( msg, RPT_SEVERITY_ERROR );
185
186 aDestination->m_lastRunSuccess = false;
187
188 return false;
189 }
190
191 bool continueOuput = aDestination->m_outputHandler->OutputPrecheck();
192
193 if( !continueOuput )
194 {
195 msg = wxString::Format( wxT( "Destination precheck failed for destination %s" ),
196 aDestination->m_id );
197 m_reporter.Report( msg, RPT_SEVERITY_ERROR );
198
199 aDestination->m_lastRunSuccess = false;
200 return false;
201 }
202
203 msg += wxT( "|--------------------------------\n" );
204 msg += wxT( "| " );
205 msg += wxString::Format( wxT( "Running jobs for destination %s" ), aDestination->m_id );
206 msg += wxT( "\n" );
207 msg += wxT( "|--------------------------------\n" );
208
209 msg += wxString::Format( wxT( "|%-5s | %-50s\n" ), wxT( "No." ), wxT( "Description" ) );
210
211 int jobNum = 1;
212
213 for( const JOBSET_JOB& job : jobsForDestination )
214 {
215 msg += wxString::Format( wxT( "|%-5d | %-50s\n" ), jobNum, job.GetDescription() );
216 jobNum++;
217 }
218
219 msg += wxT( "|--------------------------------\n" );
220 msg += wxT( "\n" );
221 msg += wxT( "\n" );
222
223 m_reporter.Report( msg, RPT_SEVERITY_INFO );
224
225 std::vector<wxString> pathsWithOverwriteDisallowed;
226 std::vector<JOB_OUTPUT> outputs;
227
228 jobNum = 1;
229 int failCount = 0;
230 int successCount = 0;
231
232 wxSetEnv( OUTPUT_TMP_PATH_VAR_NAME, tempDirPath );
233
234 for( const JOBSET_JOB& job : jobsForDestination )
235 {
236 msg = wxT( "|--------------------------------\n" );
237
238 msg += wxString::Format( wxT( "| Running job %d: %s" ), jobNum, job.GetDescription() );
239
240 msg += wxT( "\n" );
241 msg += wxT( "|--------------------------------\n" );
242
243 m_reporter.Report( msg, RPT_SEVERITY_INFO );
244
246 {
247 msg.Printf( _( "Running job %d: %s" ), jobNum, job.GetDescription() );
248 m_progressReporter->AdvanceJob( msg );
249 m_progressReporter->KeepRefreshing();
250 }
251
252 jobNum++;
253
254 KIWAY::FACE_T iface = JOB_REGISTRY::GetKifaceType( job.m_type );
255
256 job.m_job->SetTempOutputDirectory( tempDirPath );
257
258 REPORTER* targetReporter = &m_reporter;
259
260 if( targetReporter == &NULL_REPORTER::GetInstance() )
261 {
262 aDestination->m_lastRunReporters[job.m_id] =
263 std::make_shared<JOBSET_OUTPUT_REPORTER>( tempDirPath, m_progressReporter );
264
265 targetReporter = aDestination->m_lastRunReporters[job.m_id].get();
266 }
267
268 // Use a redirect reporter so we don't have error flags set after running previous jobs
269 REDIRECT_REPORTER isolatedReporter( targetReporter );
271
272 if( iface < KIWAY::KIWAY_FACE_COUNT )
273 {
274 result = m_kiway->ProcessJob( iface, job.m_job.get(), &isolatedReporter, m_progressReporter );
275 }
276 else
277 {
278 // special jobs
279 if( job.m_job->GetType() == "special_execute" )
280 {
281 result = runSpecialExecute( &job, &isolatedReporter, m_project );
282 }
283 else if( job.m_job->GetType() == "special_copyfiles" )
284 {
285 JOB_SPECIAL_COPYFILES* copyJob = static_cast<JOB_SPECIAL_COPYFILES*>( job.m_job.get() );
286 std::vector<wxString> pathsWritten;
287
288 result = runSpecialCopyFiles( copyJob, m_project, pathsWritten );
289
290 if( !copyJob->m_overwriteDest )
291 {
292 pathsWithOverwriteDisallowed.insert( pathsWithOverwriteDisallowed.end(), pathsWritten.begin(),
293 pathsWritten.end() );
294 }
295 }
296 }
297
298 aDestination->m_lastRunSuccessMap[job.m_id] = ( result == CLI::EXIT_CODES::SUCCESS );
299
301 {
302 wxString msg_fmt = wxT( "\033[32;1m%s\033[0m\n" );
303 msg = wxString::Format( msg_fmt, _( "Job successful" ) );
304
305 successCount++;
306 }
307 else
308 {
309 wxString msg_fmt = wxT( "\033[31;1m%s\033[0m\n" );
310 msg = wxString::Format( msg_fmt, _( "Job failed" ) );
311
312 failCount++;
313 }
314
315 msg += wxT( "\n\n" );
316 m_reporter.Report( msg, RPT_SEVERITY_INFO );
317
319 {
320 success = false;
321
322 if( aBail )
323 break;
324 }
325 else if( result != CLI::EXIT_CODES::SUCCESS )
326 {
327 genOutputs = false;
328 success = false;
329
330 if( aBail )
331 break;
332 }
333 }
334
335 wxUnsetEnv( OUTPUT_TMP_PATH_VAR_NAME );
336
337 if( genOutputs )
338 {
339 success &= aDestination->m_outputHandler->HandleOutputs( tempDirPath, m_project, pathsWithOverwriteDisallowed,
340 outputs, aDestination->m_lastResolvedOutputPath );
341 }
342
343 aDestination->m_lastRunSuccess = success;
344
345 msg = wxString::Format( wxT( "\n\n\033[33;1m%d %s, %d %s\033[0m\n" ),
346 successCount,
347 wxT( "jobs succeeded" ),
348 failCount,
349 wxT( "job failed" ) );
350
351 m_reporter.Report( msg, RPT_SEVERITY_INFO );
352
353 return success;
354}
virtual bool OutputPrecheck()
Checks if the output process can proceed before doing anything else This can include user prompts.
Definition jobs_output.h:45
virtual bool HandleOutputs(const wxString &aBaseTempPath, PROJECT *aProject, const std::vector< wxString > &aPathsWithOverwriteDisallowed, const std::vector< JOB_OUTPUT > &aOutputsToHandle, std::optional< wxString > &aResolvedOutputPath)=0
bool RunJobsAllDestinations(bool aBail=false)
REPORTER & m_reporter
Definition jobs_runner.h:78
KIWAY * m_kiway
Definition jobs_runner.h:76
JOBSET * m_jobsFile
Definition jobs_runner.h:77
JOBS_PROGRESS_REPORTER * m_progressReporter
Definition jobs_runner.h:79
bool RunJobsForDestination(JOBSET_DESTINATION *aDestination, bool aBail=false)
int runSpecialCopyFiles(const JOB_SPECIAL_COPYFILES *aJob, PROJECT *aProject, std::vector< wxString > &aPathsWritten)
PROJECT * m_project
Definition jobs_runner.h:80
int runSpecialExecute(const JOBSET_JOB *aJob, REPORTER *aReporter, PROJECT *aProject)
JOBS_RUNNER(KIWAY *aKiway, JOBSET *aJobsFile, PROJECT *aProject, REPORTER &aReporter, JOBS_PROGRESS_REPORTER *aProgressReporter)
static KIWAY::FACE_T GetKifaceType(const wxString &aName)
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
Definition job.cpp:163
wxString GetFullOutputPath(PROJECT *aProject) const
Returns the full output path for the job, taking into account the configured output path,...
Definition job.cpp:156
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
Definition job.h:235
Definition kiid.h:44
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:311
FACE_T
Known KIFACE implementations.
Definition kiway.h:317
@ KIWAY_FACE_COUNT
Definition kiway.h:326
static REPORTER & GetInstance()
Definition reporter.cpp:120
Container for project specific data.
Definition project.h:62
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:183
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:71
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:100
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:704
The common library.
#define _(s)
bool CopyFilesOrDirectory(const wxString &aSourcePath, const wxString &aDestDir, bool aAllowOverwrites, wxString &aErrors, std::vector< wxString > &aPathsWritten)
Definition gestfich.cpp:505
#define OUTPUT_TMP_PATH_VAR_NAME
static const int ERR_ARGS
Definition exit_codes.h:31
static const int OK
Definition exit_codes.h:30
static const int ERR_RC_VIOLATIONS
Rules check violation count was greater than 0.
Definition exit_codes.h:37
static const int SUCCESS
Definition exit_codes.h:29
static const int ERR_INVALID_OUTPUT_CONFLICT
Definition exit_codes.h:34
static const int ERR_UNKNOWN
Definition exit_codes.h:32
static PGM_BASE * process
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
std::unordered_map< wxString, std::shared_ptr< JOBSET_OUTPUT_REPORTER > > m_lastRunReporters
Definition jobset.h:142
std::shared_ptr< JOBS_OUTPUT_HANDLER > m_outputHandler
Definition jobset.h:136
std::optional< wxString > m_lastResolvedOutputPath
Definition jobset.h:143
std::optional< bool > m_lastRunSuccess
Definition jobset.h:140
std::unordered_map< wxString, std::optional< bool > > m_lastRunSuccessMap
Definition jobset.h:141
wxString m_id
Definition jobset.h:133
wxString m_id
Definition jobset.h:87
std::shared_ptr< JOB > m_job
Definition jobset.h:90
wxString result
Test unit parsing edge cases and error handling.