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.

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