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.

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