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>
29#include <kiway.h>
30#include <kiway_mail.h>
31#include <paths.h>
33#include <reporter.h>
34#include <optional>
35#include <wx/process.h>
36#include <wx/txtstrm.h>
37#include <wx/sstream.h>
38#include <wx/wfstream.h>
39#include <wx/mstream.h>
40#include <wx/tokenzr.h>
41#include <gestfich.h>
42
43JOBS_RUNNER::JOBS_RUNNER( KIWAY* aKiway, JOBSET* aJobsFile, PROJECT* aProject,
44 REPORTER& aReporter, JOBS_PROGRESS_REPORTER* aProgressReporter ) :
45 m_kiway( aKiway ),
46 m_jobsFile( aJobsFile ),
47 m_reporter( aReporter ),
48 m_progressReporter( aProgressReporter ),
49 m_project( aProject )
50{
51}
52
53
55{
56 bool success = true;
57
58 for( JOBSET_DESTINATION& destination : m_jobsFile->GetDestinations() )
59 success &= RunJobsForDestination( &destination, aBail );
60
61 return success;
62}
63
64
65int JOBS_RUNNER::runSpecialExecute( const JOBSET_JOB* aJob, REPORTER* aReporter, PROJECT* aProject )
66{
67 JOB_SPECIAL_EXECUTE* specialJob = static_cast<JOB_SPECIAL_EXECUTE*>( aJob->m_job.get() );
68 wxString cmd = ExpandEnvVarSubstitutions( specialJob->m_command, m_project );
69
70 aReporter->Report( cmd, RPT_SEVERITY_INFO );
71 aReporter->Report( wxEmptyString, RPT_SEVERITY_INFO );
72
73 wxProcess process;
74 process.Redirect();
75
77
78 wxInputStream* inputStream = process.GetInputStream();
79 wxInputStream* errorStream = process.GetErrorStream();
80
81 // Reads wxInputStream into a wxMemoryBuffer
82 auto streamToBuf = []( wxInputStream& aIs )
83 {
84 wxMemoryOutputStream memOut;
85 aIs >> memOut;
86
87 wxMemoryBuffer buf;
88 buf.AppendData( memOut.GetOutputStreamBuffer()->GetBufferStart(),
89 memOut.GetOutputStreamBuffer()->GetIntPosition() );
90
91 return buf;
92 };
93
94 if( inputStream && errorStream )
95 {
96 wxMemoryBuffer memInBuf = streamToBuf( *inputStream );
97 wxMemoryBuffer memErrBuf = streamToBuf( *errorStream );
98
99 if( !memInBuf.IsEmpty() )
100 {
101 wxString str = wxString::FromUTF8( memInBuf, memInBuf.GetDataLen() );
102 wxStringTokenizer tokenizer( str, "\r\n" );
103
104 while( tokenizer.HasMoreTokens() )
105 aReporter->Report( tokenizer.GetNextToken(), RPT_SEVERITY_INFO );
106 }
107
108 if( !memErrBuf.IsEmpty() )
109 {
110 wxString str = wxString::FromUTF8( memErrBuf, memErrBuf.GetDataLen() );
111 wxStringTokenizer tokenizer( str, "\r\n" );
112
113 while( tokenizer.HasMoreTokens() )
114 aReporter->Report( tokenizer.GetNextToken(), RPT_SEVERITY_ERROR );
115 }
116
117 if( specialJob->m_recordOutput )
118 {
119 if( specialJob->GetConfiguredOutputPath().IsEmpty() )
120 {
121 wxFileName fn( aJob->m_id );
122 fn.SetExt( wxT( "log" ) );
123 specialJob->SetConfiguredOutputPath( fn.GetFullPath() );
124 }
125
126 wxFFileOutputStream procOutput( specialJob->GetFullOutputPath( aProject ) );
127
128 if( !procOutput.IsOk() )
130
131 procOutput.WriteAll( memInBuf, memInBuf.GetDataLen() );
132 }
133 }
134
135 if( specialJob->m_ignoreExitcode )
136 return CLI::EXIT_CODES::OK;
137
138 return result;
139}
140
141
143 std::vector<wxString>& aPathsWritten )
144{
145 wxString source = ResolveProjectPath( aJob->m_source, aProject );
146
147 if( source.IsEmpty() )
149
150 wxFileName sourceFn( source );
151
152 wxFileName destFn( aJob->GetFullOutputPath( aProject ) );
153
154 if( !aJob->m_dest.IsEmpty() )
155 destFn.AppendDir( ExpandPathVariables( aJob->m_dest, aProject ) );
156
157 wxString errors;
158 bool success = CopyFilesOrDirectory( sourceFn.GetFullPath(), destFn.GetFullPath(), aJob->m_overwriteDest,
159 errors, aPathsWritten );
160
161 if( !success )
163
164 if( aJob->m_generateErrorOnNoCopy && aPathsWritten.empty() )
166
167 return CLI::EXIT_CODES::OK;
168}
169
170
171int JOBS_RUNNER::runSpecialArchive( const JOBSET_JOB* aJob, REPORTER* aReporter, PROJECT* aProject )
172{
173 JOB_SPECIAL_ARCHIVE* archiveJob = static_cast<JOB_SPECIAL_ARCHIVE*>( aJob->m_job.get() );
174
175 if( archiveJob->GetConfiguredOutputPath().IsEmpty() )
176 archiveJob->SetConfiguredOutputPath( wxT( "${PROJECTNAME}.zip" ) );
177
178 wxString zipFile = archiveJob->GetFullOutputPath( aProject );
179
180 if( !PATHS::EnsurePathExists( zipFile, true ) )
181 {
182 aReporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
184 }
185
186 if( !PROJECT_ARCHIVER::Archive( aProject->GetProjectPath(), zipFile, *aReporter, true,
187 archiveJob->m_includeExtraFiles ) )
188 {
190 }
191
192 return CLI::EXIT_CODES::OK;
193}
194
195
197{
198 bool genOutputs = true;
199 bool success = true;
200 std::vector<JOBSET_JOB> jobsForDestination = m_jobsFile->GetJobsForDestination( aDestination );
201 wxString msg;
202
203 wxFileName tmp;
204 tmp.AssignDir( wxFileName::GetTempDir() );
205 tmp.AppendDir( KIID().AsString() );
206
207 aDestination->m_lastRunSuccessMap.clear();
208 aDestination->m_lastRunReporters.clear();
209 aDestination->m_lastResolvedOutputPath.reset();
210
211 wxString tempDirPath = tmp.GetFullPath();
212
213 if( !wxFileName::Mkdir( tempDirPath, wxS_DIR_DEFAULT ) )
214 {
215 msg = wxString::Format( wxT( "Failed to create temporary directory %s" ), tempDirPath );
216 m_reporter.Report( msg, RPT_SEVERITY_ERROR );
217
218 aDestination->m_lastRunSuccess = false;
219
220 return false;
221 }
222
223 bool continueOuput = aDestination->m_outputHandler->OutputPrecheck();
224
225 if( !continueOuput )
226 {
227 msg = wxString::Format( wxT( "Destination precheck failed for destination %s" ),
228 aDestination->m_id );
229 m_reporter.Report( msg, RPT_SEVERITY_ERROR );
230
231 aDestination->m_lastRunSuccess = false;
232 return false;
233 }
234
235 msg += wxT( "|--------------------------------\n" );
236 msg += wxT( "| " );
237 msg += wxString::Format( wxT( "Running jobs for destination %s" ), aDestination->m_id );
238 msg += wxT( "\n" );
239 msg += wxT( "|--------------------------------\n" );
240
241 msg += wxString::Format( wxT( "|%-5s | %-50s\n" ), wxT( "No." ), wxT( "Description" ) );
242
243 int jobNum = 1;
244
245 for( const JOBSET_JOB& job : jobsForDestination )
246 {
247 msg += wxString::Format( wxT( "|%-5d | %-50s\n" ), jobNum, job.GetDescription() );
248 jobNum++;
249 }
250
251 msg += wxT( "|--------------------------------\n" );
252 msg += wxT( "\n" );
253 msg += wxT( "\n" );
254
255 m_reporter.Report( msg, RPT_SEVERITY_INFO );
256
257 std::vector<wxString> pathsWithOverwriteDisallowed;
258 std::vector<JOB_OUTPUT> outputs;
259
260 jobNum = 1;
261 int failCount = 0;
262 int successCount = 0;
263
264 wxSetEnv( OUTPUT_TMP_PATH_VAR_NAME, tempDirPath );
265
266 for( const JOBSET_JOB& job : jobsForDestination )
267 {
268 msg = wxT( "|--------------------------------\n" );
269
270 msg += wxString::Format( wxT( "| Running job %d: %s" ), jobNum, job.GetDescription() );
271
272 msg += wxT( "\n" );
273 msg += wxT( "|--------------------------------\n" );
274
275 m_reporter.Report( msg, RPT_SEVERITY_INFO );
276
278 {
279 msg.Printf( _( "Running job %d: %s" ), jobNum, job.GetDescription() );
280 m_progressReporter->AdvanceJob( msg );
281 m_progressReporter->KeepRefreshing();
282 }
283
284 jobNum++;
285
286 KIWAY::FACE_T iface = JOB_REGISTRY::GetKifaceType( job.m_type );
287
288 job.m_job->SetTempOutputDirectory( tempDirPath );
289
290 REPORTER* targetReporter = &m_reporter;
291
292 if( targetReporter == &NULL_REPORTER::GetInstance() )
293 {
294 aDestination->m_lastRunReporters[job.m_id] =
295 std::make_shared<JOBSET_OUTPUT_REPORTER>( tempDirPath, m_progressReporter );
296
297 targetReporter = aDestination->m_lastRunReporters[job.m_id].get();
298 }
299
300 // Use a redirect reporter so we don't have error flags set after running previous jobs
301 REDIRECT_REPORTER isolatedReporter( targetReporter );
303
304 if( iface < KIWAY::KIWAY_FACE_COUNT )
305 {
306 result = m_kiway->ProcessJob( iface, job.m_job.get(), &isolatedReporter, m_progressReporter );
307 }
308 else
309 {
310 // special jobs
311 if( job.m_job->GetType() == "special_execute" )
312 {
313 result = runSpecialExecute( &job, &isolatedReporter, m_project );
314 }
315 else if( job.m_job->GetType() == "special_copyfiles" )
316 {
317 JOB_SPECIAL_COPYFILES* copyJob = static_cast<JOB_SPECIAL_COPYFILES*>( job.m_job.get() );
318 std::vector<wxString> pathsWritten;
319
320 result = runSpecialCopyFiles( copyJob, m_project, pathsWritten );
321
322 if( !copyJob->m_overwriteDest )
323 {
324 pathsWithOverwriteDisallowed.insert( pathsWithOverwriteDisallowed.end(), pathsWritten.begin(),
325 pathsWritten.end() );
326 }
327 }
328 else if( job.m_job->GetType() == "special_archive" )
329 {
330 result = runSpecialArchive( &job, &isolatedReporter, m_project );
331 }
332 else
333 {
334 msg = wxString::Format( wxT( "Unsupported job type '%s'" ), job.m_type );
335 isolatedReporter.Report( msg, RPT_SEVERITY_ERROR );
337 }
338 }
339
340 aDestination->m_lastRunSuccessMap[job.m_id] = ( result == CLI::EXIT_CODES::SUCCESS );
341
343 {
344 wxString msg_fmt = wxT( "\033[32;1m%s\033[0m\n" );
345 msg = wxString::Format( msg_fmt, _( "Job successful" ) );
346
347 successCount++;
348 }
349 else
350 {
351 wxString msg_fmt = wxT( "\033[31;1m%s\033[0m\n" );
352 msg = wxString::Format( msg_fmt, _( "Job failed" ) );
353
354 failCount++;
355 }
356
357 msg += wxT( "\n\n" );
358 m_reporter.Report( msg, RPT_SEVERITY_INFO );
359
361 {
362 success = false;
363
364 if( aBail )
365 break;
366 }
367 else if( result != CLI::EXIT_CODES::SUCCESS )
368 {
369 genOutputs = false;
370 success = false;
371
372 if( aBail )
373 break;
374 }
375 }
376
377 wxUnsetEnv( OUTPUT_TMP_PATH_VAR_NAME );
378
379 if( genOutputs )
380 {
381 success &= aDestination->m_outputHandler->HandleOutputs( tempDirPath, m_project, pathsWithOverwriteDisallowed,
382 outputs, aDestination->m_lastResolvedOutputPath );
383 }
384
385 aDestination->m_lastRunSuccess = success;
386
387 msg = wxString::Format( wxT( "\n\n\033[33;1m%d %s, %d %s\033[0m\n" ),
388 successCount,
389 wxT( "jobs succeeded" ),
390 failCount,
391 wxT( "job failed" ) );
392
393 m_reporter.Report( msg, RPT_SEVERITY_INFO );
394
395 return success;
396}
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:79
KIWAY * m_kiway
Definition jobs_runner.h:77
JOBSET * m_jobsFile
Definition jobs_runner.h:78
JOBS_PROGRESS_REPORTER * m_progressReporter
Definition jobs_runner.h:80
bool RunJobsForDestination(JOBSET_DESTINATION *aDestination, bool aBail=false)
int runSpecialCopyFiles(const JOB_SPECIAL_COPYFILES *aJob, PROJECT *aProject, std::vector< wxString > &aPathsWritten)
int runSpecialArchive(const JOBSET_JOB *aJob, REPORTER *aReporter, PROJECT *aProject)
PROJECT * m_project
Definition jobs_runner.h:81
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:156
wxString GetFullOutputPath(PROJECT *aProject) const
Returns the full output path for the job, taking into account the configured output path,...
Definition job.cpp:149
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
Definition job.h:235
Definition kiid.h:46
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:348
FACE_T
Known KIFACE implementations.
Definition kiway.h:354
@ KIWAY_FACE_COUNT
Definition kiway.h:361
static REPORTER & GetInstance()
Definition reporter.cpp:207
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:470
static bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true, bool aIncludeExtraFiles=false)
Create an archive of the project.
Container for project specific data.
Definition project.h:63
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:276
REPORTER & Report(const wxString &aMsg, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
Definition reporter.cpp:401
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
wxString ExpandPathVariables(const wxString &aPath, const PROJECT *aProject, const std::function< bool(wxString *)> *aTextResolver)
Expand text variables, environment variables and a leading '~' in a user-entered file path.
Definition common.cpp:798
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject, bool aAllowEscapes)
Replace any environment variable & text variable references with their values.
Definition common.cpp:777
wxString ResolveProjectPath(const wxString &aPath, const PROJECT *aProject, const std::function< bool(wxString *)> *aTextResolver)
Expand a user-entered file path as ExpandPathVariables() does, then anchor a relative result at the p...
Definition common.cpp:817
#define _(s)
int ExecuteCommandThroughShell(const wxString &aCommand, wxProcess *aProcess)
Run a user-supplied command line through the platform shell so glob expansion, pipes,...
Definition gestfich.cpp:277
bool CopyFilesOrDirectory(const wxString &aSourcePath, const wxString &aDestDir, bool aAllowOverwrites, wxString &aErrors, std::vector< wxString > &aPathsWritten)
Definition gestfich.cpp:559
std::string source
#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:143
std::shared_ptr< JOBS_OUTPUT_HANDLER > m_outputHandler
Definition jobset.h:137
std::optional< wxString > m_lastResolvedOutputPath
Definition jobset.h:144
std::optional< bool > m_lastRunSuccess
Definition jobset.h:141
std::unordered_map< wxString, std::optional< bool > > m_lastRunSuccessMap
Definition jobset.h:142
wxString m_id
Definition jobset.h:134
wxString m_id
Definition jobset.h:88
std::shared_ptr< JOB > m_job
Definition jobset.h:91
wxString result
Test unit parsing edge cases and error handling.