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.

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