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.

718 lines
21 KiB

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