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-2023 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 <env_vars.h>
31#include <reporter.h>
32#include <macros.h>
33#include <mutex>
34#include <wx/config.h>
35#include <wx/log.h>
36#include <wx/msgdlg.h>
37#include <wx/stdpaths.h>
38#include <wx/url.h>
39#include <wx/utils.h>
40
41#ifdef _WIN32
42#include <Windows.h>
43#endif
44
45
47{
51#ifdef __WINDOWS__
52 Bracket_Windows = '%', // yeah, Windows people are a bit strange ;-)
53#endif
55};
56
57
58wxString ExpandTextVars( const wxString& aSource, const PROJECT* aProject )
59{
60 std::function<bool( wxString* )> projectResolver =
61 [&]( wxString* token ) -> bool
62 {
63 return aProject->TextVarResolver( token );
64 };
65
66 return ExpandTextVars( aSource, &projectResolver );
67}
68
69
70wxString ExpandTextVars( const wxString& aSource,
71 const std::function<bool( wxString* )>* aResolver )
72{
73 wxString newbuf;
74 size_t sourceLen = aSource.length();
75
76 newbuf.Alloc( sourceLen ); // best guess (improves performance)
77
78 for( size_t i = 0; i < sourceLen; ++i )
79 {
80 if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
81 {
82 wxString token;
83
84 for( i = i + 2; i < sourceLen; ++i )
85 {
86 if( aSource[i] == '}' )
87 break;
88 else
89 token.append( aSource[i] );
90 }
91
92 if( token.IsEmpty() )
93 continue;
94
95 if( aResolver && (*aResolver)( &token ) )
96 {
97 newbuf.append( token );
98 }
99 else
100 {
101 // Token not resolved: leave the reference unchanged
102 newbuf.append( "${" + token + "}" );
103 }
104 }
105 else
106 {
107 newbuf.append( aSource[i] );
108 }
109 }
110
111 return newbuf;
112}
113
114
115//
116// Stolen from wxExpandEnvVars and then heavily optimized
117//
118wxString KIwxExpandEnvVars( const wxString& str, const PROJECT* aProject, std::set<wxString>* aSet = nullptr )
119{
120 // If the same string is inserted twice, we have a loop
121 if( aSet )
122 {
123 if( auto [ _, result ] = aSet->insert( str ); !result )
124 return str;
125 }
126
127 size_t strlen = str.length();
128
129 wxString strResult;
130 strResult.Alloc( strlen ); // best guess (improves performance)
131
132 for( size_t n = 0; n < strlen; n++ )
133 {
134 wxUniChar str_n = str[n];
135
136 switch( str_n.GetValue() )
137 {
138#ifdef __WINDOWS__
139 case wxT( '%' ):
140#endif // __WINDOWS__
141 case wxT( '$' ):
142 {
143 Bracket bracket;
144#ifdef __WINDOWS__
145 if( str_n == wxT( '%' ) )
146 bracket = Bracket_Windows;
147 else
148#endif // __WINDOWS__
149 if( n == strlen - 1 )
150 {
151 bracket = Bracket_None;
152 }
153 else
154 {
155 switch( str[n + 1].GetValue() )
156 {
157 case wxT( '(' ):
158 bracket = Bracket_Normal;
159 str_n = str[++n]; // skip the bracket
160 break;
161
162 case wxT( '{' ):
163 bracket = Bracket_Curly;
164 str_n = str[++n]; // skip the bracket
165 break;
166
167 default:
168 bracket = Bracket_None;
169 }
170 }
171
172 size_t m = n + 1;
173
174 if( m >= strlen )
175 break;
176
177 wxUniChar str_m = str[m];
178
179 while( wxIsalnum( str_m ) || str_m == wxT( '_' ) || str_m == wxT( ':' ) )
180 {
181 if( ++m == strlen )
182 {
183 str_m = 0;
184 break;
185 }
186
187 str_m = str[m];
188 }
189
190 wxString strVarName( str.c_str() + n + 1, m - n - 1 );
191
192 // NB: use wxGetEnv instead of wxGetenv as otherwise variables
193 // set through wxSetEnv may not be read correctly!
194 bool expanded = false;
195 wxString tmp = strVarName;
196
197 if( aProject && aProject->TextVarResolver( &tmp ) )
198 {
199 strResult += tmp;
200 expanded = true;
201 }
202 else if( wxGetEnv( strVarName, &tmp ) )
203 {
204 strResult += tmp;
205 expanded = true;
206 }
207 // Replace unmatched older variables with current 3d model locations
208 // If the user has the older model location defined, that will be matched
209 // first above. But if they do not, this will ensure that their board still
210 // displays correctly
211 else if( strVarName.Contains( "KISYS3DMOD") || strVarName.Matches( "KICAD*_3DMODEL_DIR" ) )
212 {
213 for( auto& var : ENV_VAR::GetPredefinedEnvVars() )
214 {
215 if( var.Matches( "KICAD*_3DMODEL_DIR" ) )
216 {
217 const auto value = ENV_VAR::GetEnvVar<wxString>( var );
218
219 if( !value )
220 continue;
221
222 strResult += *value;
223 expanded = true;
224 break;
225 }
226 }
227 }
228 else
229 {
230 // variable doesn't exist => don't change anything
231#ifdef __WINDOWS__
232 if ( bracket != Bracket_Windows )
233#endif
234 if ( bracket != Bracket_None )
235 strResult << str[n - 1];
236
237 strResult << str_n << strVarName;
238 }
239
240 // check the closing bracket
241 if( bracket != Bracket_None )
242 {
243 if( m == strlen || str_m != (wxChar)bracket )
244 {
245 // under MSW it's common to have '%' characters in the registry
246 // and it's annoying to have warnings about them each time, so
247 // ignore them silently if they are not used for env vars
248 //
249 // under Unix, OTOH, this warning could be useful for the user to
250 // understand why isn't the variable expanded as intended
251#ifndef __WINDOWS__
252 wxLogWarning( _( "Environment variables expansion failed: missing '%c' "
253 "at position %u in '%s'." ),
254 (char)bracket, (unsigned int) (m + 1), str.c_str() );
255#endif // __WINDOWS__
256 }
257 else
258 {
259 // skip closing bracket unless the variables wasn't expanded
260 if( !expanded )
261 strResult << (wxChar)bracket;
262
263 m++;
264 }
265 }
266
267 n = m - 1; // skip variable name
268 str_n = str[n];
269 }
270 break;
271
272 case wxT( '\\' ):
273 // backslash can be used to suppress special meaning of % and $
274 if( n < strlen - 1 && (str[n + 1] == wxT( '%' ) || str[n + 1] == wxT( '$' )) )
275 {
276 str_n = str[++n];
277 strResult += str_n;
278
279 break;
280 }
282
283 default:
284 strResult += str_n;
285 }
286 }
287
288 std::set<wxString> loop_check;
289 auto first_pos = strResult.find_first_of( wxS( "{(%" ) );
290 auto last_pos = strResult.find_last_of( wxS( "})%" ) );
291
292 if( first_pos != strResult.npos && last_pos != strResult.npos && first_pos != last_pos )
293 strResult = KIwxExpandEnvVars( strResult, aProject, aSet ? aSet : &loop_check );
294
295 return strResult;
296}
297
298
299const wxString ExpandEnvVarSubstitutions( const wxString& aString, const PROJECT* aProject )
300{
301 // wxGetenv( wchar_t* ) is not re-entrant on linux.
302 // Put a lock on multithreaded use of wxGetenv( wchar_t* ), called from wxEpandEnvVars(),
303 static std::mutex getenv_mutex;
304
305 std::lock_guard<std::mutex> lock( getenv_mutex );
306
307 // We reserve the right to do this another way, by providing our own member function.
308 return KIwxExpandEnvVars( aString, aProject );
309}
310
311
312const wxString ResolveUriByEnvVars( const wxString& aUri, PROJECT* aProject )
313{
314 wxString uri = ExpandTextVars( aUri, aProject );
315
316 // URL-like URI: return as is.
317 wxURL url( uri );
318
319 if( url.GetError() == wxURL_NOERR )
320 return uri;
321
322 // Otherwise, the path points to a local file. Resolve environment variables if any.
323 return ExpandEnvVarSubstitutions( aUri, aProject );
324}
325
326
327bool EnsureFileDirectoryExists( wxFileName* aTargetFullFileName,
328 const wxString& aBaseFilename,
329 REPORTER* aReporter )
330{
331 wxString msg;
332 wxString baseFilePath = wxFileName( aBaseFilename ).GetPath();
333
334 // make aTargetFullFileName path, which is relative to aBaseFilename path (if it is not
335 // already an absolute path) absolute:
336 if( !aTargetFullFileName->MakeAbsolute( baseFilePath ) )
337 {
338 if( aReporter )
339 {
340 msg.Printf( _( "Cannot make path '%s' absolute with respect to '%s'." ),
341 aTargetFullFileName->GetPath(),
342 baseFilePath );
343 aReporter->Report( msg, RPT_SEVERITY_ERROR );
344 }
345
346 return false;
347 }
348
349 // Ensure the path of aTargetFullFileName exists, and create it if needed:
350 wxString outputPath( aTargetFullFileName->GetPath() );
351
352 if( !wxFileName::DirExists( outputPath ) )
353 {
354 // Make every directory provided when the provided path doesn't exist
355 if( wxFileName::Mkdir( outputPath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
356 {
357 if( aReporter )
358 {
359 msg.Printf( _( "Output directory '%s' created." ), outputPath );
360 aReporter->Report( msg, RPT_SEVERITY_INFO );
361 return true;
362 }
363 }
364 else
365 {
366 if( aReporter )
367 {
368 msg.Printf( _( "Cannot create output directory '%s'." ), outputPath );
369 aReporter->Report( msg, RPT_SEVERITY_ERROR );
370 }
371
372 return false;
373 }
374 }
375
376 return true;
377}
378
379
394bool matchWild( const char* pat, const char* text, bool dot_special )
395{
396 if( !*text )
397 {
398 /* Match if both are empty. */
399 return !*pat;
400 }
401
402 const char *m = pat,
403 *n = text,
404 *ma = nullptr,
405 *na = nullptr;
406 int just = 0,
407 acount = 0,
408 count = 0;
409
410 if( dot_special && (*n == '.') )
411 {
412 /* Never match so that hidden Unix files
413 * are never found. */
414 return false;
415 }
416
417 for(;;)
418 {
419 if( *m == '*' )
420 {
421 ma = ++m;
422 na = n;
423 just = 1;
424 acount = count;
425 }
426 else if( *m == '?' )
427 {
428 m++;
429
430 if( !*n++ )
431 return false;
432 }
433 else
434 {
435 if( *m == '\\' )
436 {
437 m++;
438
439 /* Quoting "nothing" is a bad thing */
440 if( !*m )
441 return false;
442 }
443
444 if( !*m )
445 {
446 /*
447 * If we are out of both strings or we just
448 * saw a wildcard, then we can say we have a
449 * match
450 */
451 if( !*n )
452 return true;
453
454 if( just )
455 return true;
456
457 just = 0;
458 goto not_matched;
459 }
460
461 /*
462 * We could check for *n == NULL at this point, but
463 * since it's more common to have a character there,
464 * check to see if they match first (m and n) and
465 * then if they don't match, THEN we can check for
466 * the NULL of n
467 */
468 just = 0;
469
470 if( *m == *n )
471 {
472 m++;
473 count++;
474 n++;
475 }
476 else
477 {
478 not_matched:
479
480 /*
481 * If there are no more characters in the
482 * string, but we still need to find another
483 * character (*m != NULL), then it will be
484 * impossible to match it
485 */
486 if( !*n )
487 return false;
488
489 if( ma )
490 {
491 m = ma;
492 n = ++na;
493 count = acount;
494 }
495 else
496 return false;
497 }
498 }
499 }
500}
501
502
507#if wxUSE_DATETIME && defined(__WIN32__) && !defined(__WXMICROWIN__)
508
509// Convert between wxDateTime and FILETIME which is a 64-bit value representing
510// the number of 100-nanosecond intervals since January 1, 1601 UTC.
511//
512// This is the offset between FILETIME epoch and the Unix/wxDateTime Epoch.
513static wxInt64 EPOCH_OFFSET_IN_MSEC = wxLL(11644473600000);
514
515
516static void ConvertFileTimeToWx( wxDateTime* dt, const FILETIME& ft )
517{
518 wxLongLong t( ft.dwHighDateTime, ft.dwLowDateTime );
519 t /= 10000; // Convert hundreds of nanoseconds to milliseconds.
520 t -= EPOCH_OFFSET_IN_MSEC;
521
522 *dt = wxDateTime( t );
523}
524
525#endif // wxUSE_DATETIME && __WIN32__
526
527
536long long TimestampDir( const wxString& aDirPath, const wxString& aFilespec )
537{
538 long long timestamp = 0;
539
540#if defined( __WIN32__ )
541 // Win32 version.
542 // Save time by not searching for each path twice: once in wxDir.GetNext() and once in
543 // wxFileName.GetModificationTime(). Also cuts out wxWidgets' string-matching and case
544 // conversion by staying on the MSW side of things.
545 std::wstring filespec( aDirPath.t_str() );
546 filespec += '\\';
547 filespec += aFilespec.t_str();
548
549 WIN32_FIND_DATA findData;
550 wxDateTime lastModDate;
551
552 HANDLE fileHandle = ::FindFirstFile( filespec.data(), &findData );
553
554 if( fileHandle != INVALID_HANDLE_VALUE )
555 {
556 do
557 {
558 ConvertFileTimeToWx( &lastModDate, findData.ftLastWriteTime );
559 timestamp += lastModDate.GetValue().GetValue();
560
561 // Get the file size (partial) as well to check for sneaky changes.
562 timestamp += findData.nFileSizeLow;
563 }
564 while ( FindNextFile( fileHandle, &findData ) != 0 );
565 }
566
567 FindClose( fileHandle );
568#else
569 // POSIX version.
570 // Save time by not converting between encodings -- do everything on the file-system side.
571 std::string filespec( aFilespec.fn_str() );
572 std::string dir_path( aDirPath.fn_str() );
573
574 DIR* dir = opendir( dir_path.c_str() );
575
576 if( dir )
577 {
578 for( dirent* dir_entry = readdir( dir ); dir_entry; dir_entry = readdir( dir ) )
579 {
580 if( !matchWild( filespec.c_str(), dir_entry->d_name, true ) )
581 continue;
582
583 std::string entry_path = dir_path + '/' + dir_entry->d_name;
584 struct stat entry_stat;
585
586 if( wxCRT_Lstat( entry_path.c_str(), &entry_stat ) == 0 )
587 {
588 // Timestamp the source file, not the symlink
589 if( S_ISLNK( entry_stat.st_mode ) ) // wxFILE_EXISTS_SYMLINK
590 {
591 char buffer[ PATH_MAX + 1 ];
592 ssize_t pathLen = readlink( entry_path.c_str(), buffer, PATH_MAX );
593
594 if( pathLen > 0 )
595 {
596 struct stat linked_stat;
597 buffer[ pathLen ] = '\0';
598 entry_path = dir_path + buffer;
599
600 if( wxCRT_Lstat( entry_path.c_str(), &linked_stat ) == 0 )
601 {
602 entry_stat = linked_stat;
603 }
604 else
605 {
606 // if we couldn't lstat the linked file we'll have to just use
607 // the symbolic link info
608 }
609 }
610 }
611
612 if( S_ISREG( entry_stat.st_mode ) ) // wxFileExists()
613 {
614 timestamp += entry_stat.st_mtime * 1000;
615
616 // Get the file size as well to check for sneaky changes.
617 timestamp += entry_stat.st_size;
618 }
619 }
620 else
621 {
622 // if we couldn't lstat the file itself all we can do is use the name
623 timestamp += (signed) std::hash<std::string>{}( std::string( dir_entry->d_name ) );
624 }
625 }
626
627 closedir( dir );
628 }
629#endif
630
631 return timestamp;
632}
633
634
636{
638 return false;
639
640 wxMessageDialog dialog( nullptr, _( "This operating system is not supported "
641 "by KiCad and its dependencies." ),
642 _( "Unsupported Operating System" ),
643 wxOK | wxICON_EXCLAMATION );
644
645 dialog.SetExtendedMessage( _( "Any issues with KiCad on this system cannot "
646 "be reported to the official bugtracker." ) );
647 dialog.ShowModal();
648
649 return true;
650}
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:299
bool WarnUserIfOperatingSystemUnsupported()
Checks if the operating system is explicitly unsupported and displays a disclaimer message box.
Definition: common.cpp:635
bool matchWild(const char *pat, const char *text, bool dot_special)
Performance enhancements to file and directory operations.
Definition: common.cpp:394
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:327
wxString KIwxExpandEnvVars(const wxString &str, const PROJECT *aProject, std::set< wxString > *aSet=nullptr)
Definition: common.cpp:118
Bracket
Definition: common.cpp:47
@ Bracket_Max
Definition: common.cpp:54
@ Bracket_None
Definition: common.cpp:48
@ Bracket_Normal
Definition: common.cpp:49
@ Bracket_Curly
Definition: common.cpp:50
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject)
Definition: common.cpp:58
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:312
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:536
The common library.
#define _(s)
Base window classes and related definitions.
Functions related to environment variables, including help functions.
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
const ENV_VAR_LIST & GetPredefinedEnvVars()
Get the list of pre-defined environment variables.
Definition: env_vars.cpp:60
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