You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

630 lines
18 KiB

  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 <stambaughw@gmail.com>
  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. #include <eda_base_frame.h>
  26. #include <kiplatform/app.h>
  27. #include <project.h>
  28. #include <common.h>
  29. #include <reporter.h>
  30. #include <macros.h>
  31. #include <mutex>
  32. #include <wx/config.h>
  33. #include <wx/log.h>
  34. #include <wx/msgdlg.h>
  35. #include <wx/process.h>
  36. #include <wx/stdpaths.h>
  37. #include <wx/url.h>
  38. #include <wx/utils.h>
  39. #ifdef _WIN32
  40. #include <Windows.h>
  41. #endif
  42. int ProcessExecute( const wxString& aCommandLine, int aFlags, wxProcess *callback )
  43. {
  44. return (int) wxExecute( aCommandLine, aFlags, callback );
  45. }
  46. enum Bracket
  47. {
  48. Bracket_None,
  49. Bracket_Normal = ')',
  50. Bracket_Curly = '}',
  51. #ifdef __WINDOWS__
  52. Bracket_Windows = '%', // yeah, Windows people are a bit strange ;-)
  53. #endif
  54. Bracket_Max
  55. };
  56. wxString ExpandTextVars( const wxString& aSource, const PROJECT* aProject )
  57. {
  58. return ExpandTextVars( aSource, nullptr, nullptr, aProject );
  59. }
  60. wxString ExpandTextVars( const wxString& aSource,
  61. const std::function<bool( wxString* )>* aLocalResolver,
  62. const std::function<bool( wxString* )>* aFallbackResolver,
  63. const PROJECT* aProject )
  64. {
  65. wxString newbuf;
  66. size_t sourceLen = aSource.length();
  67. newbuf.Alloc( sourceLen ); // best guess (improves performance)
  68. for( size_t i = 0; i < sourceLen; ++i )
  69. {
  70. if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
  71. {
  72. wxString token;
  73. for( i = i + 2; i < sourceLen; ++i )
  74. {
  75. if( aSource[i] == '}' )
  76. break;
  77. else
  78. token.append( aSource[i] );
  79. }
  80. if( token.IsEmpty() )
  81. continue;
  82. if( aLocalResolver && (*aLocalResolver)( &token ) )
  83. {
  84. newbuf.append( token );
  85. }
  86. else if( aProject && aProject->TextVarResolver( &token ) )
  87. {
  88. newbuf.append( token );
  89. }
  90. else if( aFallbackResolver && (*aFallbackResolver)( &token ) )
  91. {
  92. newbuf.append( token );
  93. }
  94. else
  95. {
  96. // Token not resolved: leave the reference unchanged
  97. newbuf.append( "${" + token + "}" );
  98. }
  99. }
  100. else
  101. {
  102. newbuf.append( aSource[i] );
  103. }
  104. }
  105. return newbuf;
  106. }
  107. //
  108. // Stolen from wxExpandEnvVars and then heavily optimized
  109. //
  110. wxString KIwxExpandEnvVars( const wxString& str, const PROJECT* aProject )
  111. {
  112. size_t strlen = str.length();
  113. wxString strResult;
  114. strResult.Alloc( strlen ); // best guess (improves performance)
  115. for( size_t n = 0; n < strlen; n++ )
  116. {
  117. wxUniChar str_n = str[n];
  118. switch( str_n.GetValue() )
  119. {
  120. #ifdef __WINDOWS__
  121. case wxT( '%' ):
  122. #endif // __WINDOWS__
  123. case wxT( '$' ):
  124. {
  125. Bracket bracket;
  126. #ifdef __WINDOWS__
  127. if( str_n == wxT( '%' ) )
  128. bracket = Bracket_Windows;
  129. else
  130. #endif // __WINDOWS__
  131. if( n == strlen - 1 )
  132. {
  133. bracket = Bracket_None;
  134. }
  135. else
  136. {
  137. switch( str[n + 1].GetValue() )
  138. {
  139. case wxT( '(' ):
  140. bracket = Bracket_Normal;
  141. str_n = str[++n]; // skip the bracket
  142. break;
  143. case wxT( '{' ):
  144. bracket = Bracket_Curly;
  145. str_n = str[++n]; // skip the bracket
  146. break;
  147. default:
  148. bracket = Bracket_None;
  149. }
  150. }
  151. size_t m = n + 1;
  152. if( m >= strlen )
  153. break;
  154. wxUniChar str_m = str[m];
  155. while( wxIsalnum( str_m ) || str_m == wxT( '_' ) || str_m == wxT( ':' ) )
  156. {
  157. if( ++m == strlen )
  158. {
  159. str_m = 0;
  160. break;
  161. }
  162. str_m = str[m];
  163. }
  164. wxString strVarName( str.c_str() + n + 1, m - n - 1 );
  165. // NB: use wxGetEnv instead of wxGetenv as otherwise variables
  166. // set through wxSetEnv may not be read correctly!
  167. bool expanded = false;
  168. wxString tmp = strVarName;
  169. if( aProject && aProject->TextVarResolver( &tmp ) )
  170. {
  171. strResult += tmp;
  172. expanded = true;
  173. }
  174. else if( wxGetEnv( strVarName, &tmp ) )
  175. {
  176. strResult += tmp;
  177. expanded = true;
  178. }
  179. else
  180. {
  181. // variable doesn't exist => don't change anything
  182. #ifdef __WINDOWS__
  183. if ( bracket != Bracket_Windows )
  184. #endif
  185. if ( bracket != Bracket_None )
  186. strResult << str[n - 1];
  187. strResult << str_n << strVarName;
  188. }
  189. // check the closing bracket
  190. if( bracket != Bracket_None )
  191. {
  192. if( m == strlen || str_m != (wxChar)bracket )
  193. {
  194. // under MSW it's common to have '%' characters in the registry
  195. // and it's annoying to have warnings about them each time, so
  196. // ignore them silently if they are not used for env vars
  197. //
  198. // under Unix, OTOH, this warning could be useful for the user to
  199. // understand why isn't the variable expanded as intended
  200. #ifndef __WINDOWS__
  201. wxLogWarning( _( "Environment variables expansion failed: missing '%c' "
  202. "at position %u in '%s'." ),
  203. (char)bracket, (unsigned int) (m + 1), str.c_str() );
  204. #endif // __WINDOWS__
  205. }
  206. else
  207. {
  208. // skip closing bracket unless the variables wasn't expanded
  209. if( !expanded )
  210. strResult << (wxChar)bracket;
  211. m++;
  212. }
  213. }
  214. n = m - 1; // skip variable name
  215. str_n = str[n];
  216. }
  217. break;
  218. case wxT( '\\' ):
  219. // backslash can be used to suppress special meaning of % and $
  220. if( n < strlen - 1 && (str[n + 1] == wxT( '%' ) || str[n + 1] == wxT( '$' )) )
  221. {
  222. str_n = str[++n];
  223. strResult += str_n;
  224. break;
  225. }
  226. KI_FALLTHROUGH;
  227. default:
  228. strResult += str_n;
  229. }
  230. }
  231. #ifndef __WINDOWS__
  232. if( strResult.StartsWith( "~" ) )
  233. strResult.Replace( "~", wxGetHomeDir(), false );
  234. #endif // __WINDOWS__
  235. return strResult;
  236. }
  237. const wxString ExpandEnvVarSubstitutions( const wxString& aString, PROJECT* aProject )
  238. {
  239. // wxGetenv( wchar_t* ) is not re-entrant on linux.
  240. // Put a lock on multithreaded use of wxGetenv( wchar_t* ), called from wxEpandEnvVars(),
  241. static std::mutex getenv_mutex;
  242. std::lock_guard<std::mutex> lock( getenv_mutex );
  243. // We reserve the right to do this another way, by providing our own member function.
  244. return KIwxExpandEnvVars( aString, aProject );
  245. }
  246. const wxString ResolveUriByEnvVars( const wxString& aUri, PROJECT* aProject )
  247. {
  248. wxString uri = ExpandTextVars( aUri, aProject );
  249. // URL-like URI: return as is.
  250. wxURL url( uri );
  251. if( url.GetError() == wxURL_NOERR )
  252. return uri;
  253. // Otherwise, the path points to a local file. Resolve environment variables if any.
  254. return ExpandEnvVarSubstitutions( aUri, aProject );
  255. }
  256. bool EnsureFileDirectoryExists( wxFileName* aTargetFullFileName,
  257. const wxString& aBaseFilename,
  258. REPORTER* aReporter )
  259. {
  260. wxString msg;
  261. wxString baseFilePath = wxFileName( aBaseFilename ).GetPath();
  262. // make aTargetFullFileName path, which is relative to aBaseFilename path (if it is not
  263. // already an absolute path) absolute:
  264. if( !aTargetFullFileName->MakeAbsolute( baseFilePath ) )
  265. {
  266. if( aReporter )
  267. {
  268. msg.Printf( _( "Cannot make path '%s' absolute with respect to '%s'." ),
  269. aTargetFullFileName->GetPath(),
  270. baseFilePath );
  271. aReporter->Report( msg, RPT_SEVERITY_ERROR );
  272. }
  273. return false;
  274. }
  275. // Ensure the path of aTargetFullFileName exists, and create it if needed:
  276. wxString outputPath( aTargetFullFileName->GetPath() );
  277. if( !wxFileName::DirExists( outputPath ) )
  278. {
  279. // Make every directory provided when the provided path doesn't exist
  280. if( wxFileName::Mkdir( outputPath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
  281. {
  282. if( aReporter )
  283. {
  284. msg.Printf( _( "Output directory '%s' created." ), outputPath );
  285. aReporter->Report( msg, RPT_SEVERITY_INFO );
  286. return true;
  287. }
  288. }
  289. else
  290. {
  291. if( aReporter )
  292. {
  293. msg.Printf( _( "Cannot create output directory '%s'." ), outputPath );
  294. aReporter->Report( msg, RPT_SEVERITY_ERROR );
  295. }
  296. return false;
  297. }
  298. }
  299. return true;
  300. }
  301. /**
  302. * Performance enhancements to file and directory operations.
  303. *
  304. * Note: while it's annoying to have to make copies of wxWidgets stuff and then
  305. * add platform-specific performance optimizations, the following routines offer
  306. * SIGNIFICANT performance benefits.
  307. */
  308. /**
  309. * A copy of wxMatchWild(), which wxWidgets attributes to Douglas A. Lewis
  310. * <dalewis@cs.Buffalo.EDU> and ircII's reg.c.
  311. *
  312. * This version is modified to skip any encoding conversions (for performance).
  313. */
  314. bool matchWild( const char* pat, const char* text, bool dot_special )
  315. {
  316. if( !*text )
  317. {
  318. /* Match if both are empty. */
  319. return !*pat;
  320. }
  321. const char *m = pat,
  322. *n = text,
  323. *ma = nullptr,
  324. *na = nullptr;
  325. int just = 0,
  326. acount = 0,
  327. count = 0;
  328. if( dot_special && (*n == '.') )
  329. {
  330. /* Never match so that hidden Unix files
  331. * are never found. */
  332. return false;
  333. }
  334. for(;;)
  335. {
  336. if( *m == '*' )
  337. {
  338. ma = ++m;
  339. na = n;
  340. just = 1;
  341. acount = count;
  342. }
  343. else if( *m == '?' )
  344. {
  345. m++;
  346. if( !*n++ )
  347. return false;
  348. }
  349. else
  350. {
  351. if( *m == '\\' )
  352. {
  353. m++;
  354. /* Quoting "nothing" is a bad thing */
  355. if( !*m )
  356. return false;
  357. }
  358. if( !*m )
  359. {
  360. /*
  361. * If we are out of both strings or we just
  362. * saw a wildcard, then we can say we have a
  363. * match
  364. */
  365. if( !*n )
  366. return true;
  367. if( just )
  368. return true;
  369. just = 0;
  370. goto not_matched;
  371. }
  372. /*
  373. * We could check for *n == NULL at this point, but
  374. * since it's more common to have a character there,
  375. * check to see if they match first (m and n) and
  376. * then if they don't match, THEN we can check for
  377. * the NULL of n
  378. */
  379. just = 0;
  380. if( *m == *n )
  381. {
  382. m++;
  383. count++;
  384. n++;
  385. }
  386. else
  387. {
  388. not_matched:
  389. /*
  390. * If there are no more characters in the
  391. * string, but we still need to find another
  392. * character (*m != NULL), then it will be
  393. * impossible to match it
  394. */
  395. if( !*n )
  396. return false;
  397. if( ma )
  398. {
  399. m = ma;
  400. n = ++na;
  401. count = acount;
  402. }
  403. else
  404. return false;
  405. }
  406. }
  407. }
  408. }
  409. /**
  410. * A copy of ConvertFileTimeToWx() because wxWidgets left it as a static function
  411. * private to src/common/filename.cpp.
  412. */
  413. #if wxUSE_DATETIME && defined(__WIN32__) && !defined(__WXMICROWIN__)
  414. // Convert between wxDateTime and FILETIME which is a 64-bit value representing
  415. // the number of 100-nanosecond intervals since January 1, 1601 UTC.
  416. //
  417. // This is the offset between FILETIME epoch and the Unix/wxDateTime Epoch.
  418. static wxInt64 EPOCH_OFFSET_IN_MSEC = wxLL(11644473600000);
  419. static void ConvertFileTimeToWx( wxDateTime* dt, const FILETIME& ft )
  420. {
  421. wxLongLong t( ft.dwHighDateTime, ft.dwLowDateTime );
  422. t /= 10000; // Convert hundreds of nanoseconds to milliseconds.
  423. t -= EPOCH_OFFSET_IN_MSEC;
  424. *dt = wxDateTime( t );
  425. }
  426. #endif // wxUSE_DATETIME && __WIN32__
  427. /**
  428. * This routine offers SIGNIFICANT performance benefits over using wxWidgets to gather
  429. * timestamps from matching files in a directory.
  430. *
  431. * @param aDirPath is the directory to search.
  432. * @param aFilespec is a (wildcarded) file spec to match against.
  433. * @return a hash of the last-mod-dates of all matching files in the directory.
  434. */
  435. long long TimestampDir( const wxString& aDirPath, const wxString& aFilespec )
  436. {
  437. long long timestamp = 0;
  438. #if defined( __WIN32__ )
  439. // Win32 version.
  440. // Save time by not searching for each path twice: once in wxDir.GetNext() and once in
  441. // wxFileName.GetModificationTime(). Also cuts out wxWidgets' string-matching and case
  442. // conversion by staying on the MSW side of things.
  443. std::wstring filespec( aDirPath.t_str() );
  444. filespec += '\\';
  445. filespec += aFilespec.t_str();
  446. WIN32_FIND_DATA findData;
  447. wxDateTime lastModDate;
  448. HANDLE fileHandle = ::FindFirstFile( filespec.data(), &findData );
  449. if( fileHandle != INVALID_HANDLE_VALUE )
  450. {
  451. do
  452. {
  453. ConvertFileTimeToWx( &lastModDate, findData.ftLastWriteTime );
  454. timestamp += lastModDate.GetValue().GetValue();
  455. // Get the file size (partial) as well to check for sneaky changes.
  456. timestamp += findData.nFileSizeLow;
  457. }
  458. while ( FindNextFile( fileHandle, &findData ) != 0 );
  459. }
  460. FindClose( fileHandle );
  461. #else
  462. // POSIX version.
  463. // Save time by not converting between encodings -- do everything on the file-system side.
  464. std::string filespec( aFilespec.fn_str() );
  465. std::string dir_path( aDirPath.fn_str() );
  466. DIR* dir = opendir( dir_path.c_str() );
  467. if( dir )
  468. {
  469. for( dirent* dir_entry = readdir( dir ); dir_entry; dir_entry = readdir( dir ) )
  470. {
  471. if( !matchWild( filespec.c_str(), dir_entry->d_name, true ) )
  472. continue;
  473. std::string entry_path = dir_path + '/' + dir_entry->d_name;
  474. struct stat entry_stat;
  475. if( wxCRT_Lstat( entry_path.c_str(), &entry_stat ) == 0 )
  476. {
  477. // Timestamp the source file, not the symlink
  478. if( S_ISLNK( entry_stat.st_mode ) ) // wxFILE_EXISTS_SYMLINK
  479. {
  480. char buffer[ PATH_MAX + 1 ];
  481. ssize_t pathLen = readlink( entry_path.c_str(), buffer, PATH_MAX );
  482. if( pathLen > 0 )
  483. {
  484. struct stat linked_stat;
  485. buffer[ pathLen ] = '\0';
  486. entry_path = dir_path + buffer;
  487. if( wxCRT_Lstat( entry_path.c_str(), &linked_stat ) == 0 )
  488. {
  489. entry_stat = linked_stat;
  490. }
  491. else
  492. {
  493. // if we couldn't lstat the linked file we'll have to just use
  494. // the symbolic link info
  495. }
  496. }
  497. }
  498. if( S_ISREG( entry_stat.st_mode ) ) // wxFileExists()
  499. {
  500. timestamp += entry_stat.st_mtime * 1000;
  501. // Get the file size as well to check for sneaky changes.
  502. timestamp += entry_stat.st_size;
  503. }
  504. }
  505. else
  506. {
  507. // if we couldn't lstat the file itself all we can do is use the name
  508. timestamp += (signed) std::hash<std::string>{}( std::string( dir_entry->d_name ) );
  509. }
  510. }
  511. closedir( dir );
  512. }
  513. #endif
  514. return timestamp;
  515. }
  516. bool WarnUserIfOperatingSystemUnsupported()
  517. {
  518. if( !KIPLATFORM::APP::IsOperatingSystemUnsupported() )
  519. return false;
  520. wxMessageDialog dialog( nullptr, _( "This operating system is not supported "
  521. "by KiCad and its dependencies." ),
  522. _( "Unsupported Operating System" ),
  523. wxOK | wxICON_EXCLAMATION );
  524. dialog.SetExtendedMessage( _( "Any issues with KiCad on this system cannot "
  525. "be reported to the official bugtracker." ) );
  526. dialog.ShowModal();
  527. return true;
  528. }