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 
45 enum Bracket
46 {
50 #ifdef __WINDOWS__
51  Bracket_Windows = '%', // yeah, Windows people are a bit strange ;-)
52 #endif
54 };
55 
56 
57 wxString ExpandTextVars( const wxString& aSource, const PROJECT* aProject )
58 {
59  return ExpandTextVars( aSource, nullptr, nullptr, aProject );
60 }
61 
62 
63 wxString 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( wxT( "${" ) + token + wxT( "}" ) );
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 //
121 wxString 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 
267 const wxString ExpandEnvVarSubstitutions( const wxString& aString, 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 
280 const 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 
295 bool 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 
362 bool 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.
481 static wxInt64 EPOCH_OFFSET_IN_MSEC = wxLL(11644473600000);
482 
483 
484 static 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 
504 long 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 }
bool IsOperatingSystemUnsupported()
Checks if the Operating System is explicitly unsupported and we want to prevent users from sending bu...
Definition: gtk/app.cpp:51
Container for project specific data.
Definition: project.h:62
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject)
Definition: common.cpp:57
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition: macros.h:83
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:70
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
This file contains miscellaneous commonly used macros and functions.
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:45
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
Base window classes and related definitions.
virtual bool TextVarResolver(wxString *aToken) const
Definition: project.cpp:66
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
#define _(s)
bool matchWild(const char *pat, const char *text, bool dot_special)
Performance enhancements to file and directory operations.
Definition: common.cpp:362
bool WarnUserIfOperatingSystemUnsupported()
Checks if the operating system is explicitly unsupported and displays a disclaimer message box.
Definition: common.cpp:603
The common library.
wxString KIwxExpandEnvVars(const wxString &str, const PROJECT *aProject)
Definition: common.cpp:121