KiCad PCB EDA Suite
common.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) 2014-2020 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2008 Wayne Stambaugh <[email protected]>
6 * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <eda_base_frame.h>
27#include <kiplatform/app.h>
28#include <project.h>
29#include <common.h>
30#include <reporter.h>
31#include <macros.h>
32#include <mutex>
33#include <wx/config.h>
34#include <wx/log.h>
35#include <wx/msgdlg.h>
36#include <wx/stdpaths.h>
37#include <wx/url.h>
38#include <wx/utils.h>
39
40#ifdef _WIN32
41#include <Windows.h>
42#endif
43
44
46{
50#ifdef __WINDOWS__
51 Bracket_Windows = '%', // yeah, Windows people are a bit strange ;-)
52#endif
54};
55
56
57wxString ExpandTextVars( const wxString& aSource, const PROJECT* aProject )
58{
59 return ExpandTextVars( aSource, nullptr, nullptr, aProject );
60}
61
62
63wxString ExpandTextVars( const wxString& aSource,
64 const std::function<bool( wxString* )>* aLocalResolver,
65 const std::function<bool( wxString* )>* aFallbackResolver,
66 const PROJECT* aProject )
67{
68 wxString newbuf;
69 size_t sourceLen = aSource.length();
70
71 newbuf.Alloc( sourceLen ); // best guess (improves performance)
72
73 for( size_t i = 0; i < sourceLen; ++i )
74 {
75 if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
76 {
77 wxString token;
78
79 for( i = i + 2; i < sourceLen; ++i )
80 {
81 if( aSource[i] == '}' )
82 break;
83 else
84 token.append( aSource[i] );
85 }
86
87 if( token.IsEmpty() )
88 continue;
89
90 if( aLocalResolver && (*aLocalResolver)( &token ) )
91 {
92 newbuf.append( token );
93 }
94 else if( aProject && aProject->TextVarResolver( &token ) )
95 {
96 newbuf.append( token );
97 }
98 else if( aFallbackResolver && (*aFallbackResolver)( &token ) )
99 {
100 newbuf.append( token );
101 }
102 else
103 {
104 // Token not resolved: leave the reference unchanged
105 newbuf.append( "${" + token + "}" );
106 }
107 }
108 else
109 {
110 newbuf.append( aSource[i] );
111 }
112 }
113
114 return newbuf;
115}
116
117
118//
119// Stolen from wxExpandEnvVars and then heavily optimized
120//
121wxString KIwxExpandEnvVars( const wxString& str, const PROJECT* aProject )
122{
123 size_t strlen = str.length();
124
125 wxString strResult;
126 strResult.Alloc( strlen ); // best guess (improves performance)
127
128 for( size_t n = 0; n < strlen; n++ )
129 {
130 wxUniChar str_n = str[n];
131
132 switch( str_n.GetValue() )
133 {
134#ifdef __WINDOWS__
135 case wxT( '%' ):
136#endif // __WINDOWS__
137 case wxT( '$' ):
138 {
139 Bracket bracket;
140#ifdef __WINDOWS__
141 if( str_n == wxT( '%' ) )
142 bracket = Bracket_Windows;
143 else
144#endif // __WINDOWS__
145 if( n == strlen - 1 )
146 {
147 bracket = Bracket_None;
148 }
149 else
150 {
151 switch( str[n + 1].GetValue() )
152 {
153 case wxT( '(' ):
154 bracket = Bracket_Normal;
155 str_n = str[++n]; // skip the bracket
156 break;
157
158 case wxT( '{' ):
159 bracket = Bracket_Curly;
160 str_n = str[++n]; // skip the bracket
161 break;
162
163 default:
164 bracket = Bracket_None;
165 }
166 }
167
168 size_t m = n + 1;
169
170 if( m >= strlen )
171 break;
172
173 wxUniChar str_m = str[m];
174
175 while( wxIsalnum( str_m ) || str_m == wxT( '_' ) || str_m == wxT( ':' ) )
176 {
177 if( ++m == strlen )
178 {
179 str_m = 0;
180 break;
181 }
182
183 str_m = str[m];
184 }
185
186 wxString strVarName( str.c_str() + n + 1, m - n - 1 );
187
188 // NB: use wxGetEnv instead of wxGetenv as otherwise variables
189 // set through wxSetEnv may not be read correctly!
190 bool expanded = false;
191 wxString tmp = strVarName;
192
193 if( aProject && aProject->TextVarResolver( &tmp ) )
194 {
195 strResult += tmp;
196 expanded = true;
197 }
198 else if( wxGetEnv( strVarName, &tmp ) )
199 {
200 strResult += tmp;
201 expanded = true;
202 }
203 else
204 {
205 // variable doesn't exist => don't change anything
206#ifdef __WINDOWS__
207 if ( bracket != Bracket_Windows )
208#endif
209 if ( bracket != Bracket_None )
210 strResult << str[n - 1];
211
212 strResult << str_n << strVarName;
213 }
214
215 // check the closing bracket
216 if( bracket != Bracket_None )
217 {
218 if( m == strlen || str_m != (wxChar)bracket )
219 {
220 // under MSW it's common to have '%' characters in the registry
221 // and it's annoying to have warnings about them each time, so
222 // ignore them silently if they are not used for env vars
223 //
224 // under Unix, OTOH, this warning could be useful for the user to
225 // understand why isn't the variable expanded as intended
226#ifndef __WINDOWS__
227 wxLogWarning( _( "Environment variables expansion failed: missing '%c' "
228 "at position %u in '%s'." ),
229 (char)bracket, (unsigned int) (m + 1), str.c_str() );
230#endif // __WINDOWS__
231 }
232 else
233 {
234 // skip closing bracket unless the variables wasn't expanded
235 if( !expanded )
236 strResult << (wxChar)bracket;
237
238 m++;
239 }
240 }
241
242 n = m - 1; // skip variable name
243 str_n = str[n];
244 }
245 break;
246
247 case wxT( '\\' ):
248 // backslash can be used to suppress special meaning of % and $
249 if( n < strlen - 1 && (str[n + 1] == wxT( '%' ) || str[n + 1] == wxT( '$' )) )
250 {
251 str_n = str[++n];
252 strResult += str_n;
253
254 break;
255 }
257
258 default:
259 strResult += str_n;
260 }
261 }
262
263 return strResult;
264}
265
266
267const wxString ExpandEnvVarSubstitutions( const wxString& aString, const PROJECT* aProject )
268{
269 // wxGetenv( wchar_t* ) is not re-entrant on linux.
270 // Put a lock on multithreaded use of wxGetenv( wchar_t* ), called from wxEpandEnvVars(),
271 static std::mutex getenv_mutex;
272
273 std::lock_guard<std::mutex> lock( getenv_mutex );
274
275 // We reserve the right to do this another way, by providing our own member function.
276 return KIwxExpandEnvVars( aString, aProject );
277}
278
279
280const wxString ResolveUriByEnvVars( const wxString& aUri, PROJECT* aProject )
281{
282 wxString uri = ExpandTextVars( aUri, aProject );
283
284 // URL-like URI: return as is.
285 wxURL url( uri );
286
287 if( url.GetError() == wxURL_NOERR )
288 return uri;
289
290 // Otherwise, the path points to a local file. Resolve environment variables if any.
291 return ExpandEnvVarSubstitutions( aUri, aProject );
292}
293
294
295bool EnsureFileDirectoryExists( wxFileName* aTargetFullFileName,
296 const wxString& aBaseFilename,
297 REPORTER* aReporter )
298{
299 wxString msg;
300 wxString baseFilePath = wxFileName( aBaseFilename ).GetPath();
301
302 // make aTargetFullFileName path, which is relative to aBaseFilename path (if it is not
303 // already an absolute path) absolute:
304 if( !aTargetFullFileName->MakeAbsolute( baseFilePath ) )
305 {
306 if( aReporter )
307 {
308 msg.Printf( _( "Cannot make path '%s' absolute with respect to '%s'." ),
309 aTargetFullFileName->GetPath(),
310 baseFilePath );
311 aReporter->Report( msg, RPT_SEVERITY_ERROR );
312 }
313
314 return false;
315 }
316
317 // Ensure the path of aTargetFullFileName exists, and create it if needed:
318 wxString outputPath( aTargetFullFileName->GetPath() );
319
320 if( !wxFileName::DirExists( outputPath ) )
321 {
322 // Make every directory provided when the provided path doesn't exist
323 if( wxFileName::Mkdir( outputPath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
324 {
325 if( aReporter )
326 {
327 msg.Printf( _( "Output directory '%s' created." ), outputPath );
328 aReporter->Report( msg, RPT_SEVERITY_INFO );
329 return true;
330 }
331 }
332 else
333 {
334 if( aReporter )
335 {
336 msg.Printf( _( "Cannot create output directory '%s'." ), outputPath );
337 aReporter->Report( msg, RPT_SEVERITY_ERROR );
338 }
339
340 return false;
341 }
342 }
343
344 return true;
345}
346
347
362bool matchWild( const char* pat, const char* text, bool dot_special )
363{
364 if( !*text )
365 {
366 /* Match if both are empty. */
367 return !*pat;
368 }
369
370 const char *m = pat,
371 *n = text,
372 *ma = nullptr,
373 *na = nullptr;
374 int just = 0,
375 acount = 0,
376 count = 0;
377
378 if( dot_special && (*n == '.') )
379 {
380 /* Never match so that hidden Unix files
381 * are never found. */
382 return false;
383 }
384
385 for(;;)
386 {
387 if( *m == '*' )
388 {
389 ma = ++m;
390 na = n;
391 just = 1;
392 acount = count;
393 }
394 else if( *m == '?' )
395 {
396 m++;
397
398 if( !*n++ )
399 return false;
400 }
401 else
402 {
403 if( *m == '\\' )
404 {
405 m++;
406
407 /* Quoting "nothing" is a bad thing */
408 if( !*m )
409 return false;
410 }
411
412 if( !*m )
413 {
414 /*
415 * If we are out of both strings or we just
416 * saw a wildcard, then we can say we have a
417 * match
418 */
419 if( !*n )
420 return true;
421
422 if( just )
423 return true;
424
425 just = 0;
426 goto not_matched;
427 }
428
429 /*
430 * We could check for *n == NULL at this point, but
431 * since it's more common to have a character there,
432 * check to see if they match first (m and n) and
433 * then if they don't match, THEN we can check for
434 * the NULL of n
435 */
436 just = 0;
437
438 if( *m == *n )
439 {
440 m++;
441 count++;
442 n++;
443 }
444 else
445 {
446 not_matched:
447
448 /*
449 * If there are no more characters in the
450 * string, but we still need to find another
451 * character (*m != NULL), then it will be
452 * impossible to match it
453 */
454 if( !*n )
455 return false;
456
457 if( ma )
458 {
459 m = ma;
460 n = ++na;
461 count = acount;
462 }
463 else
464 return false;
465 }
466 }
467 }
468}
469
470
475#if wxUSE_DATETIME && defined(__WIN32__) && !defined(__WXMICROWIN__)
476
477// Convert between wxDateTime and FILETIME which is a 64-bit value representing
478// the number of 100-nanosecond intervals since January 1, 1601 UTC.
479//
480// This is the offset between FILETIME epoch and the Unix/wxDateTime Epoch.
481static wxInt64 EPOCH_OFFSET_IN_MSEC = wxLL(11644473600000);
482
483
484static void ConvertFileTimeToWx( wxDateTime* dt, const FILETIME& ft )
485{
486 wxLongLong t( ft.dwHighDateTime, ft.dwLowDateTime );
487 t /= 10000; // Convert hundreds of nanoseconds to milliseconds.
488 t -= EPOCH_OFFSET_IN_MSEC;
489
490 *dt = wxDateTime( t );
491}
492
493#endif // wxUSE_DATETIME && __WIN32__
494
495
504long long TimestampDir( const wxString& aDirPath, const wxString& aFilespec )
505{
506 long long timestamp = 0;
507
508#if defined( __WIN32__ )
509 // Win32 version.
510 // Save time by not searching for each path twice: once in wxDir.GetNext() and once in
511 // wxFileName.GetModificationTime(). Also cuts out wxWidgets' string-matching and case
512 // conversion by staying on the MSW side of things.
513 std::wstring filespec( aDirPath.t_str() );
514 filespec += '\\';
515 filespec += aFilespec.t_str();
516
517 WIN32_FIND_DATA findData;
518 wxDateTime lastModDate;
519
520 HANDLE fileHandle = ::FindFirstFile( filespec.data(), &findData );
521
522 if( fileHandle != INVALID_HANDLE_VALUE )
523 {
524 do
525 {
526 ConvertFileTimeToWx( &lastModDate, findData.ftLastWriteTime );
527 timestamp += lastModDate.GetValue().GetValue();
528
529 // Get the file size (partial) as well to check for sneaky changes.
530 timestamp += findData.nFileSizeLow;
531 }
532 while ( FindNextFile( fileHandle, &findData ) != 0 );
533 }
534
535 FindClose( fileHandle );
536#else
537 // POSIX version.
538 // Save time by not converting between encodings -- do everything on the file-system side.
539 std::string filespec( aFilespec.fn_str() );
540 std::string dir_path( aDirPath.fn_str() );
541
542 DIR* dir = opendir( dir_path.c_str() );
543
544 if( dir )
545 {
546 for( dirent* dir_entry = readdir( dir ); dir_entry; dir_entry = readdir( dir ) )
547 {
548 if( !matchWild( filespec.c_str(), dir_entry->d_name, true ) )
549 continue;
550
551 std::string entry_path = dir_path + '/' + dir_entry->d_name;
552 struct stat entry_stat;
553
554 if( wxCRT_Lstat( entry_path.c_str(), &entry_stat ) == 0 )
555 {
556 // Timestamp the source file, not the symlink
557 if( S_ISLNK( entry_stat.st_mode ) ) // wxFILE_EXISTS_SYMLINK
558 {
559 char buffer[ PATH_MAX + 1 ];
560 ssize_t pathLen = readlink( entry_path.c_str(), buffer, PATH_MAX );
561
562 if( pathLen > 0 )
563 {
564 struct stat linked_stat;
565 buffer[ pathLen ] = '\0';
566 entry_path = dir_path + buffer;
567
568 if( wxCRT_Lstat( entry_path.c_str(), &linked_stat ) == 0 )
569 {
570 entry_stat = linked_stat;
571 }
572 else
573 {
574 // if we couldn't lstat the linked file we'll have to just use
575 // the symbolic link info
576 }
577 }
578 }
579
580 if( S_ISREG( entry_stat.st_mode ) ) // wxFileExists()
581 {
582 timestamp += entry_stat.st_mtime * 1000;
583
584 // Get the file size as well to check for sneaky changes.
585 timestamp += entry_stat.st_size;
586 }
587 }
588 else
589 {
590 // if we couldn't lstat the file itself all we can do is use the name
591 timestamp += (signed) std::hash<std::string>{}( std::string( dir_entry->d_name ) );
592 }
593 }
594
595 closedir( dir );
596 }
597#endif
598
599 return timestamp;
600}
601
602
604{
606 return false;
607
608 wxMessageDialog dialog( nullptr, _( "This operating system is not supported "
609 "by KiCad and its dependencies." ),
610 _( "Unsupported Operating System" ),
611 wxOK | wxICON_EXCLAMATION );
612
613 dialog.SetExtendedMessage( _( "Any issues with KiCad on this system cannot "
614 "be reported to the official bugtracker." ) );
615 dialog.ShowModal();
616
617 return true;
618}
Container for project specific data.
Definition: project.h:64
virtual bool TextVarResolver(wxString *aToken) const
Definition: project.cpp:70
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)=0
Report a string with a given severity.
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
wxString KIwxExpandEnvVars(const wxString &str, const PROJECT *aProject)
Definition: common.cpp:121
bool WarnUserIfOperatingSystemUnsupported()
Checks if the operating system is explicitly unsupported and displays a disclaimer message box.
Definition: common.cpp:603
bool matchWild(const char *pat, const char *text, bool dot_special)
Performance enhancements to file and directory operations.
Definition: common.cpp:362
bool EnsureFileDirectoryExists(wxFileName *aTargetFullFileName, const wxString &aBaseFilename, REPORTER *aReporter)
Make aTargetFullFileName absolute and create the path of this file if it doesn't yet exist.
Definition: common.cpp:295
Bracket
Definition: common.cpp:46
@ Bracket_Max
Definition: common.cpp:53
@ Bracket_None
Definition: common.cpp:47
@ Bracket_Normal
Definition: common.cpp:48
@ Bracket_Curly
Definition: common.cpp:49
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject)
Definition: common.cpp:57
const wxString ResolveUriByEnvVars(const wxString &aUri, PROJECT *aProject)
Replace any environment and/or text variables in file-path uris (leaving network-path URIs alone).
Definition: common.cpp:280
long long TimestampDir(const wxString &aDirPath, const wxString &aFilespec)
A copy of ConvertFileTimeToWx() because wxWidgets left it as a static function private to src/common/...
Definition: common.cpp:504
The common library.
#define _(s)
Base window classes and related definitions.
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition: macros.h:83
bool IsOperatingSystemUnsupported()
Checks if the Operating System is explicitly unsupported and we want to prevent users from sending bu...
Definition: gtk/app.cpp:51
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO