KiCad PCB EDA Suite
Loading...
Searching...
No Matches
library_manager.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 * @author Jon Evans <[email protected]>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <chrono>
22#include <common.h>
23#include <env_vars.h>
24#include <list>
25#include <magic_enum.hpp>
26#include <thread_pool.h>
27#include <ranges>
28#include <set>
29#include <unordered_set>
30
31#include <paths.h>
32#include <pgm_base.h>
33#include <richio.h>
34#include <trace_helpers.h>
36
38
39using namespace std::chrono_literals;
43#include <wx/dir.h>
44#include <wx/log.h>
45
47{
48 std::vector<LIBRARY_TABLE> tables;
49};
50
51
55
56
58
59
60void LIBRARY_MANAGER::loadTables( const wxString& aTablePath, LIBRARY_TABLE_SCOPE aScope,
61 std::vector<LIBRARY_TABLE_TYPE> aTablesToLoad )
62{
63 {
64 std::lock_guard lock( m_rowCacheMutex );
65 m_rowCache.clear();
66 }
67
68 auto getTarget =
69 [&]() -> std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>&
70 {
71 switch( aScope )
72 {
74 return m_tables;
75
77 return m_projectTables;
78
79 default:
80 wxCHECK_MSG( false, m_tables, "Invalid scope passed to loadTables" );
81 }
82 };
83
84 std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>& aTarget = getTarget();
85
86 if( aTablesToLoad.size() == 0 )
88
89 for( LIBRARY_TABLE_TYPE type : aTablesToLoad )
90 {
91 aTarget.erase( type );
92
93 wxFileName fn( aTablePath, tableFileName( type ) );
94
95 if( fn.IsFileReadable() )
96 {
97 std::unique_ptr<LIBRARY_TABLE> table = std::make_unique<LIBRARY_TABLE>( fn, aScope );
98
99 if( table->Type() != type )
100 {
101 auto actualName = magic_enum::enum_name( table->Type() );
102 auto expectedName = magic_enum::enum_name( type );
103 wxLogWarning( wxS( "Library table '%s' has type %s but expected %s; skipping" ),
104 fn.GetFullPath(),
105 wxString( actualName.data(), actualName.size() ),
106 wxString( expectedName.data(), expectedName.size() ) );
107 continue;
108 }
109
110 aTarget[type] = std::move( table );
111 loadNestedTables( *aTarget[type] );
112 }
113 else
114 {
115 wxLogTrace( traceLibraries, "No library table found at %s", fn.GetFullPath() );
116 }
117 }
118}
119
120
122{
123 std::unordered_set<wxString> seenTables;
124
125 std::function<void( LIBRARY_TABLE& )> processOneTable =
126 [&]( LIBRARY_TABLE& aTable )
127 {
128 seenTables.insert( aTable.Path() );
129
130 if( !aTable.IsOk() )
131 return;
132
133 for( LIBRARY_TABLE_ROW& row : aTable.Rows() )
134 {
135 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
136 {
137 wxFileName file( ExpandURI( row.URI(), Pgm().GetSettingsManager().Prj() ) );
138
139 // URI may be relative to parent
140 file.MakeAbsolute( wxFileName( aTable.Path() ).GetPath() );
141
143 wxString src = file.GetFullPath();
144
145 if( seenTables.contains( src ) )
146 {
147 wxLogTrace( traceLibraries, "Library table %s has already been loaded!", src );
148 row.SetOk( false );
149 row.SetErrorDescription( _( "A reference to this library table already exists" ) );
150 continue;
151 }
152
153 auto child = std::make_unique<LIBRARY_TABLE>( file, aRootTable.Scope() );
154
155 processOneTable( *child );
156
157 if( !child->IsOk() )
158 {
159 row.SetOk( false );
160 row.SetErrorDescription( child->ErrorDescription() );
161 }
162
163 applyLibOverrides( *child );
164
165 m_childTables.insert_or_assign( row.URI(), std::move( child ) );
166 }
167 }
168 };
169
170 processOneTable( aRootTable );
171}
172
173
175{
176 if( !aTable.IsReadOnly() )
177 return;
178
180 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
181
182 if( !settings )
183 return;
184
185 wxString tablePath = aTable.Path();
186
187 auto it = settings->m_LibOverrides.find( tablePath );
188
189 if( it == settings->m_LibOverrides.end() )
190 return;
191
192 const std::map<wxString, LIB_OVERRIDE>& overrides = it->second;
193
194 for( LIBRARY_TABLE_ROW& row : aTable.Rows() )
195 {
196 auto overIt = overrides.find( row.Nickname() );
197
198 if( overIt != overrides.end() )
199 {
200 row.SetDisabled( overIt->second.disabled );
201 row.SetHidden( overIt->second.hidden );
202 }
203 }
204}
205
206
211
212
213void LIBRARY_MANAGER::SetLibOverride( const wxString& aTablePath, const wxString& aNickname,
214 bool aDisabled, bool aHidden )
215{
217 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
218
219 wxCHECK( settings, /* void */ );
220
221 if( !aDisabled && !aHidden )
222 {
223 ClearLibOverride( aTablePath, aNickname );
224 return;
225 }
226
227 settings->m_LibOverrides[aTablePath][aNickname] = { aDisabled, aHidden };
228}
229
230
231void LIBRARY_MANAGER::ClearLibOverride( const wxString& aTablePath, const wxString& aNickname )
232{
234 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
235
236 wxCHECK( settings, /* void */ );
237
238 auto tableIt = settings->m_LibOverrides.find( aTablePath );
239
240 if( tableIt == settings->m_LibOverrides.end() )
241 return;
242
243 tableIt->second.erase( aNickname );
244
245 if( tableIt->second.empty() )
246 settings->m_LibOverrides.erase( tableIt );
247}
248
249
251{
252 switch( aType )
253 {
257 default: wxCHECK( false, wxEmptyString );
258 }
259}
260
261
263{
264 if( aScope == LIBRARY_TABLE_SCOPE::GLOBAL )
265 {
266 wxCHECK( !m_tables.contains( aType ), /* void */ );
267 wxFileName fn( PATHS::GetUserSettingsPath(), tableFileName( aType ) );
268
269 m_tables[aType] = std::make_unique<LIBRARY_TABLE>( fn, LIBRARY_TABLE_SCOPE::GLOBAL );
270 m_tables[aType]->SetType( aType );
271 }
272 else if( aScope == LIBRARY_TABLE_SCOPE::PROJECT )
273 {
274 wxCHECK( !m_projectTables.contains( aType ), /* void */ );
275 wxFileName fn( Pgm().GetSettingsManager().Prj().GetProjectDirectory(), tableFileName( aType ) );
276
277 m_projectTables[aType] = std::make_unique<LIBRARY_TABLE>( fn, LIBRARY_TABLE_SCOPE::PROJECT );
278 m_projectTables[aType]->SetType( aType );
279 }
280}
281
282
283class PCM_LIB_TRAVERSER final : public wxDirTraverser
284{
285public:
286 explicit PCM_LIB_TRAVERSER( const wxString& aBasePath, LIBRARY_MANAGER& aManager,
287 const wxString& aPrefix ) :
288 m_manager( aManager ),
289 m_project( Pgm().GetSettingsManager().Prj() ),
290 m_path_prefix( aBasePath ),
291 m_lib_prefix( aPrefix )
292 {
293 wxFileName f( aBasePath, "" );
294 m_prefix_dir_count = f.GetDirCount();
295
299 .value_or( nullptr );
300 }
301
303 wxDirTraverseResult OnFile( const wxString& aFilePath ) override
304 {
305 wxFileName file = wxFileName::FileName( aFilePath );
306
307 // consider a file to be a lib if it's name ends with .kicad_sym and
308 // it is under $KICADn_3RD_PARTY/symbols/<pkgid>/ i.e. has nested level of at least +2
309 if( file.GetExt() == wxT( "kicad_sym" )
310 && file.GetDirCount() >= m_prefix_dir_count + 2
311 && file.GetDirs()[m_prefix_dir_count] == wxT( "symbols" ) )
312 {
314 }
315
316 return wxDIR_CONTINUE;
317 }
318
320 wxDirTraverseResult OnDir( const wxString& dirPath ) override
321 {
322 static wxString designBlockExt = wxString::Format( wxS( ".%s" ), FILEEXT::KiCadDesignBlockLibPathExtension );
323 wxFileName dir = wxFileName::DirName( dirPath );
324
325 // consider a directory to be a lib if it's name ends with .pretty and
326 // it is under $KICADn_3RD_PARTY/footprints/<pkgid>/ i.e. has nested level of at least +3
327 if( dirPath.EndsWith( wxS( ".pretty" ) )
328 && dir.GetDirCount() >= m_prefix_dir_count + 3
329 && dir.GetDirs()[m_prefix_dir_count] == wxT( "footprints" ) )
330 {
332 }
333 else if( dirPath.EndsWith( designBlockExt )
334 && dir.GetDirCount() >= m_prefix_dir_count + 3
335 && dir.GetDirs()[m_prefix_dir_count] == wxT( "design_blocks" ) )
336 {
337 addRowIfNecessary( m_designBlockTable, dir, ADD_MODE::AM_DIRECTORY, designBlockExt.Len() );
338 }
339
340 return wxDIR_CONTINUE;
341 }
342
343 std::set<LIBRARY_TABLE*> Modified() const { return m_modified; }
344
345private:
346 void ensureUnique( LIBRARY_TABLE* aTable, const wxString& aBaseName, wxString& aNickname ) const
347 {
348 if( aTable->HasRow( aNickname ) )
349 {
350 int increment = 1;
351
352 do
353 {
354 aNickname = wxString::Format( "%s%s_%d", m_lib_prefix, aBaseName, increment );
355 increment++;
356 } while( aTable->HasRow( aNickname ) );
357 }
358 }
359
360 enum class ADD_MODE
361 {
364 };
365
366 void addRowIfNecessary( LIBRARY_TABLE* aTable, const wxFileName& aSource, ADD_MODE aMode,
367 int aExtensionLength )
368 {
369 wxString versionedPath = wxString::Format( wxS( "${%s}" ),
370 ENV_VAR::GetVersionedEnvVarName( wxS( "3RD_PARTY" ) ) );
371
372 wxArrayString parts = aSource.GetDirs();
373 parts.RemoveAt( 0, m_prefix_dir_count );
374 parts.Insert( versionedPath, 0 );
375
376 if( aMode == ADD_MODE::AM_FILE )
377 parts.Add( aSource.GetFullName() );
378
379 wxString libPath = wxJoin( parts, '/' );
380
381 if( !aTable->HasRowWithURI( libPath, m_project ) )
382 {
383 wxString name = parts.Last().substr( 0, parts.Last().length() - aExtensionLength );
384 wxString nickname = wxString::Format( "%s%s", m_lib_prefix, name );
385
386 ensureUnique( aTable, name, nickname );
387
388 wxLogTrace( traceLibraries, "Manager: Adding PCM lib '%s' as '%s'", libPath, nickname );
389
390 LIBRARY_TABLE_ROW& row = aTable->InsertRow();
391
392 row.SetNickname( nickname );
393 row.SetURI( libPath );
394 row.SetType( wxT( "KiCad" ) );
395 row.SetDescription( _( "Added by Plugin and Content Manager" ) );
396 m_modified.insert( aTable );
397 }
398 else
399 {
400 wxLogTrace( traceLibraries, "Manager: Not adding existing PCM lib '%s'", libPath );
401 }
402 }
403
404private:
408 wxString m_lib_prefix;
410 std::set<LIBRARY_TABLE*> m_modified;
411
415};
416
417
419{
420 wxString basePath = PATHS::GetUserSettingsPath();
421
422 wxFileName fn( basePath, tableFileName( aType ) );
423 fn.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
424
425 return fn.GetFullPath();
426}
427
428
430{
431 wxString basePath = PATHS::GetStockTemplatesPath();
432
433 wxFileName fn( basePath, tableFileName( aType ) );
434 fn.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
435
436 return fn.GetFullPath();
437}
438
439
441{
442 wxString templateDirVar = ENV_VAR::GetVersionedEnvVarName( wxS( "TEMPLATE_DIR" ) );
443
444 return wxString::Format( wxS( "${%s}/%s" ), templateDirVar, tableFileName( aType ) );
445}
446
447
449{
450 const wxString templateDirVar = ENV_VAR::GetVersionedEnvVarName( wxS( "TEMPLATE_DIR" ) );
451
452 // Only relocatable installs (AppImage, Nix) export the template-dir variable so the stock
453 // path follows the remounted prefix. There we store the unresolved token, which re-resolves
454 // each launch instead of dangling. A standard install leaves the variable at its built-in
455 // default, so we keep the historical resolved absolute path.
456 if( COMMON_SETTINGS* common = Pgm().GetCommonSettings() )
457 {
458 auto it = common->m_Env.vars.find( templateDirVar );
459
460 if( it != common->m_Env.vars.end() && it->second.GetDefinedExternally() )
461 return StockTableTokenizedURI( aType );
462 }
463
464 return StockTablePath( aType );
465}
466
467
468bool LIBRARY_MANAGER::IsTableValid( const wxString& aPath )
469{
470 if( wxFileName fn( aPath ); fn.IsFileReadable() )
471 {
473
474 if( temp.IsOk() )
475 return true;
476 }
477
478 return false;
479}
480
481
483{
484 return InvalidGlobalTables().empty();
485}
486
487
488std::vector<LIBRARY_TABLE_TYPE> LIBRARY_MANAGER::InvalidGlobalTables()
489{
490 std::vector<LIBRARY_TABLE_TYPE> invalidTables;
491 wxString basePath = PATHS::GetUserSettingsPath();
492
496 {
497 wxFileName fn( basePath, tableFileName( tableType ) );
498
499 if( !IsTableValid( fn.GetFullPath() ) )
500 invalidTables.emplace_back( tableType );
501 }
502
503 return invalidTables;
504}
505
506
507bool LIBRARY_MANAGER::CreateGlobalTable( LIBRARY_TABLE_TYPE aType, bool aPopulateDefaultLibraries )
508{
509 wxFileName fn( DefaultGlobalTablePath( aType ) );
510
512 table.SetType( aType );
513 table.Rows().clear();
514
515 wxFileName defaultLib( StockTablePath( aType ) );
516
517 if( aPopulateDefaultLibraries && defaultLib.IsFileReadable() )
518 {
519 LIBRARY_TABLE_ROW& chained = table.InsertRow();
521 chained.SetNickname( wxT( "KiCad" ) );
522 chained.SetDescription( _( "KiCad Default Libraries" ) );
523 chained.SetURI( StockTableReferenceURI( aType ) );
524 }
525 else if( aPopulateDefaultLibraries )
526 {
527 auto typeName = magic_enum::enum_name( aType );
528 wxLogTrace( traceLibraries, "No stock library table for %s at '%s'; creating empty global table",
529 wxString( typeName.data(), typeName.size() ), defaultLib.GetFullPath() );
530 }
531
532 try
533 {
534 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( fn.GetFullPath(), KICAD_FORMAT::FORMAT_MODE::LIBRARY_TABLE );
535 table.Format( &formatter );
536 formatter.Finish();
537 }
538 catch( IO_ERROR& e )
539 {
540 wxLogTrace( traceLibraries, "Exception while saving: %s", e.What() );
541 return false;
542 }
543
544 return true;
545}
546
547
548void LIBRARY_MANAGER::LoadGlobalTables( std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
549{
550 // Cancel any in-progress load
551 {
552 std::scoped_lock lock( m_adaptersMutex );
553
554 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
555 adapter->GlobalTablesChanged( aTablesToLoad );
556 }
557
559
561 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
562
563 wxCHECK( settings, /* void */ );
564
565 const ENV_VAR_MAP& vars = Pgm().GetLocalEnvVariables();
566 std::optional<wxString> packagesPath = ENV_VAR::GetVersionedEnvVarValue( vars, wxT( "3RD_PARTY" ) );
567
568 if( packagesPath && settings->m_PcmLibAutoAdd )
569 {
570 // Scan for libraries in PCM packages directory
571 wxFileName d( *packagesPath, "" );
572
573 if( d.DirExists() )
574 {
575 PCM_LIB_TRAVERSER traverser( *packagesPath, *this, settings->m_PcmLibPrefix );
576 wxDir dir( d.GetPath() );
577
578 dir.Traverse( traverser );
579
580 for( LIBRARY_TABLE* table : traverser.Modified() )
581 {
582 table->Save().map_error(
583 []( const LIBRARY_ERROR& aError )
584 {
585 wxLogTrace( traceLibraries, wxT( "Warning: save failed after PCM auto-add: %s" ),
586 aError.message );
587 } );
588 }
589 }
590 }
591
592 auto cleanupRemovedPCMLibraries =
593 [&]( LIBRARY_TABLE_TYPE aType )
594 {
595 LIBRARY_TABLE* table = Table( aType, LIBRARY_TABLE_SCOPE::GLOBAL ).value_or( nullptr );
596 // No global table file yet (first run): nothing to scan for PCM removals.
597 if( !table )
598 return;
599
600 auto toErase = std::ranges::remove_if( table->Rows(),
601 [&]( const LIBRARY_TABLE_ROW& aRow )
602 {
603 if( !IsPcmManagedRow( aRow ) )
604 return false;
605
606 wxString path = GetFullURI( &aRow, true );
607 return !wxFileName::Exists( path );
608 } );
609
610 bool hadRemovals = !toErase.empty();
611 table->Rows().erase( toErase.begin(), toErase.end() );
612
613 if( hadRemovals )
614 {
615 table->Save().map_error(
616 []( const LIBRARY_ERROR& aError )
617 {
618 wxLogTrace( traceLibraries, wxT( "Warning: save failed after PCM auto-remove: %s" ),
619 aError.message );
620 } );
621 }
622 };
623
624 if( packagesPath && settings->m_PcmLibAutoRemove )
625 {
626 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::SYMBOL );
627 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::FOOTPRINT );
628 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::DESIGN_BLOCK );
629 }
630}
631
632
633void LIBRARY_MANAGER::LoadProjectTables( std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
634{
635 LoadProjectTables( Pgm().GetSettingsManager().Prj().GetProjectDirectory(), aTablesToLoad );
636}
637
638
640{
641 // Abort any running async library loads before reloading project tables.
642 // Background workers hold raw LIBRARY_TABLE_ROW pointers that become dangling
643 // when loadTables() destroys and replaces the table objects.
645
646 LoadProjectTables( Pgm().GetSettingsManager().Prj().GetProjectDirectory() );
647
648 std::scoped_lock lock( m_adaptersMutex );
649
650 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
651 adapter->ProjectChanged();
652}
653
654
656{
657 std::scoped_lock lock( m_adaptersMutex );
658
659 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
660 adapter->AbortAsyncLoad();
661}
662
663
665 std::unique_ptr<LIBRARY_MANAGER_ADAPTER>&& aAdapter )
666{
667 std::scoped_lock lock( m_adaptersMutex );
668
669 wxCHECK_MSG( !m_adapters.contains( aType ), /**/, "You should only register an adapter once!" );
670
671 m_adapters[aType] = std::move( aAdapter );
672}
673
674
676{
677 std::scoped_lock lock( m_adaptersMutex );
678 if( !m_adapters.contains( aType ) )
679 return false;
680
681 if( m_adapters[aType].get() != aAdapter )
682 return false;
683
684 m_adapters.erase( aType );
685 return true;
686}
687
688
689std::optional<LIBRARY_MANAGER_ADAPTER*> LIBRARY_MANAGER::Adapter( LIBRARY_TABLE_TYPE aType ) const
690{
691 std::scoped_lock lock( m_adaptersMutex );
692
693 if( m_adapters.contains( aType ) )
694 return m_adapters.at( aType ).get();
695
696 return std::nullopt;
697}
698
699
700std::optional<LIBRARY_TABLE*> LIBRARY_MANAGER::Table( LIBRARY_TABLE_TYPE aType,
701 LIBRARY_TABLE_SCOPE aScope )
702{
703 switch( aScope )
704 {
707 wxCHECK_MSG( false, std::nullopt, "Table() requires a single scope" );
708
710 {
711 if( !m_tables.contains( aType ) )
712 {
713 wxLogTrace( traceLibraries, "WARNING: missing global table (%s)",
714 magic_enum::enum_name( aType ) );
715 return std::nullopt;
716 }
717
718 return m_tables.at( aType ).get();
719 }
720
722 {
723 // TODO: handle multiple projects
724 if( !m_projectTables.contains( aType ) )
725 {
726 if( !Pgm().GetSettingsManager().Prj().IsNullProject() )
728 else
729 return std::nullopt;
730 }
731
732 return m_projectTables.at( aType ).get();
733 }
734 }
735
736 return std::nullopt;
737}
738
739
740std::vector<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::Rows( LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope,
741 bool aIncludeInvalid ) const
742{
743 std::map<wxString, LIBRARY_TABLE_ROW*> rows;
744 std::vector<wxString> rowOrder;
745
746 std::list<std::ranges::ref_view<const std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>>> tables;
747
748 switch( aScope )
749 {
751 tables = { std::views::all( m_tables ) };
752 break;
753
755 tables = { std::views::all( m_projectTables ) };
756 break;
757
759 tables = { std::views::all( m_tables ), std::views::all( m_projectTables ) };
760 break;
761
763 wxFAIL;
764 }
765
766 std::function<void(const std::unique_ptr<LIBRARY_TABLE>&, bool parentHidden)> processTable =
767 [&]( const std::unique_ptr<LIBRARY_TABLE>& aTable, const bool parentHidden )
768 {
769 if( aTable->Type() != aType )
770 return;
771
772 if( aTable->IsOk() || aIncludeInvalid )
773 {
774 for( LIBRARY_TABLE_ROW& row : aTable->Rows() )
775 {
776 if( row.IsOk() || aIncludeInvalid )
777 {
778 // Hide child row if parent is hidden
779 if( parentHidden )
780 row.SetHidden( true );
781
782 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
783 {
784 if( !m_childTables.contains( row.URI() ) )
785 continue;
786
787 // Don't include libraries from disabled nested tables
788 if( row.Disabled() )
789 continue;
790
791 processTable( m_childTables.at( row.URI() ), row.Hidden() );
792 }
793 else
794 {
795 if( !rows.contains( row.Nickname() ) )
796 rowOrder.emplace_back( row.Nickname() );
797
798 rows[ row.Nickname() ] = &row;
799 }
800 }
801 }
802 }
803 };
804
805 for( const std::unique_ptr<LIBRARY_TABLE>& table :
806 std::views::join( tables ) | std::views::values )
807 {
808 processTable( table, false );
809 }
810
811 std::vector<LIBRARY_TABLE_ROW*> ret;
812
813 for( const wxString& row : rowOrder )
814 ret.emplace_back( rows[row] );
815
816 return ret;
817}
818
819
820std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::GetRow( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
821 LIBRARY_TABLE_SCOPE aScope )
822{
823 {
824 std::lock_guard lock( m_rowCacheMutex );
825 auto key = std::make_tuple( aType, aScope, aNickname );
826
827 if( auto it = m_rowCache.find( key ); it != m_rowCache.end() )
828 return it->second;
829 }
830
831 for( LIBRARY_TABLE_ROW* row : Rows( aType, aScope, true ) )
832 {
833 if( row->Nickname() == aNickname )
834 {
835 std::lock_guard lock( m_rowCacheMutex );
836 m_rowCache[std::make_tuple( aType, aScope, aNickname )] = row;
837 return row;
838 }
839 }
840
841 return std::nullopt;
842}
843
844
845std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::FindRowByURI( LIBRARY_TABLE_TYPE aType,
846 const wxString& aUri,
847 LIBRARY_TABLE_SCOPE aScope ) const
848{
849 for( LIBRARY_TABLE_ROW* row : Rows( aType, aScope, true ) )
850 {
851 if( UrisAreEquivalent( GetFullURI( row, true ), aUri ) )
852 return row;
853 }
854
855 return std::nullopt;
856}
857
858
859void LIBRARY_MANAGER::ReloadLibraryEntry( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
860 LIBRARY_TABLE_SCOPE aScope )
861{
862 if( std::optional<LIBRARY_MANAGER_ADAPTER*> adapter = Adapter( aType ); adapter )
863 ( *adapter )->ReloadLibraryEntry( aNickname, aScope );
864}
865
866
868 const wxString& aNickname )
869{
870 if( std::optional<LIBRARY_MANAGER_ADAPTER*> adapter = Adapter( aType ); adapter )
871 return ( *adapter )->LoadLibraryEntry( aNickname );
872
873 return std::nullopt;
874}
875
876
877void LIBRARY_MANAGER::LoadProjectTables( const wxString& aProjectPath,
878 std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
879{
880 // Cancel any in-progress loads and clear adapter caches before destroying project
881 // tables. Cached LIB_DATA entries hold raw LIBRARY_TABLE_ROW pointers into the old
882 // tables, which would dangle once loadTables() replaces them. Mirrors the safety
883 // ordering in LoadGlobalTables().
884 {
885 std::scoped_lock lock( m_adaptersMutex );
886
887 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
888 adapter->ProjectTablesChanged( aTablesToLoad );
889 }
890
891 if( wxFileName::IsDirReadable( aProjectPath ) )
892 {
893 loadTables( aProjectPath, LIBRARY_TABLE_SCOPE::PROJECT, aTablesToLoad );
894 }
895 else
896 {
897 // loadTables() would have cleared m_rowCache before rebuilding the new
898 // table; do the same here so cached entries don't point into the
899 // project tables we are about to destroy.
900 {
901 std::lock_guard lock( m_rowCacheMutex );
902 m_rowCache.clear();
903 }
904
905 m_projectTables.clear();
906 wxLogTrace( traceLibraries, "New project path %s is not readable, not loading project tables", aProjectPath );
907 }
908
909 // Phase 2: let adapters reconcile their cache against the rebuilt project
910 // table. This erases sentinels installed by ProjectTablesChanged() for any
911 // nickname that no longer has a project row, so a library removed from the
912 // project table stops masking a same-named global library.
913 {
914 std::scoped_lock lock( m_adaptersMutex );
915
916 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
917 adapter->ProjectTablesReloaded( aTablesToLoad );
918 }
919}
920
921
923 std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
924{
925 if( aScope == LIBRARY_TABLE_SCOPE::PROJECT )
926 {
928 LoadProjectTables( aTablesToLoad );
929 }
930 else
931 {
932 LoadGlobalTables( aTablesToLoad );
933 }
934}
935
936
937std::optional<wxString> LIBRARY_MANAGER::GetFullURI( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
938 bool aSubstituted )
939{
940 if( std::optional<const LIBRARY_TABLE_ROW*> result = GetRow( aType, aNickname ) )
941 return GetFullURI( *result, aSubstituted );
942
943 return std::nullopt;
944}
945
946
947wxString LIBRARY_MANAGER::GetFullURI( const LIBRARY_TABLE_ROW* aRow, bool aSubstituted )
948{
949 if( aSubstituted )
950 return ExpandEnvVarSubstitutions( aRow->URI(), &Pgm().GetSettingsManager().Prj() );
951
952 return aRow->URI();
953}
954
955
956wxString LIBRARY_MANAGER::ExpandURI( const wxString& aShortURI, const PROJECT& aProject )
957{
958 wxLogNull doNotLog; // We do our own error reporting; we don't want to hear about missing envvars
959
960 wxFileName path( ExpandEnvVarSubstitutions( aShortURI, &aProject ) );
961 path.MakeAbsolute();
962 return path.GetFullPath();
963}
964
965
967{
968 // PCM_LIB_TRAVERSER always stores URIs that begin with the versioned
969 // ${KICADn_3RD_PARTY} env var token. Any row whose URI does not start with that
970 // token was not added by PCM and must not be auto-removed even if its expanded
971 // absolute path happens to live inside the 3RD_PARTY directory via a different
972 // env var.
973 const wxString& uri = aRow.URI();
974
975 if( !uri.StartsWith( wxS( "${" ) ) )
976 return false;
977
978 size_t end = uri.find( wxS( '}' ) );
979
980 if( end == wxString::npos || end <= 2 )
981 return false;
982
983 wxString varName = uri.SubString( 2, end - 1 );
984
985 return varName.Matches( wxS( "KICAD*_3RD_PARTY" ) );
986}
987
988
989bool LIBRARY_MANAGER::UrisAreEquivalent( const wxString& aURI1, const wxString& aURI2 )
990{
991 // Avoid comparing filenames as wxURIs
992 if( aURI1.Find( "://" ) != wxNOT_FOUND )
993 {
994 // found as full path
995 return aURI1 == aURI2;
996 }
997 else
998 {
999 const wxFileName fn1( aURI1 );
1000 const wxFileName fn2( aURI2 );
1001
1002 // This will also test if the file is a symlink so if we are comparing
1003 // a symlink to the same real file, the comparison will be true. See
1004 // wxFileName::SameAs() in the wxWidgets source.
1005
1006 // found as full path and file name
1007 return fn1 == fn2;
1008 }
1009}
1010
1011
1015
1016
1021
1022
1026
1027
1032
1033
1038
1039
1041{
1042 abortLoad();
1043
1044 std::unique_lock lock( m_librariesMutex );
1045
1046 // Reset entries in place rather than erasing them. Erasing would let
1047 // fetchIfLoaded() fall through to globalLibs() for any nickname that is
1048 // shadowed by a project library, defeating the project-over-global
1049 // precedence enforced by LIBRARY_MANAGER::Rows(). ProjectTablesReloaded()
1050 // later prunes any sentinels whose nicknames are no longer in the rebuilt
1051 // project table, so stale shadowing cannot persist.
1052 for( auto& entry : m_libraries )
1053 entry.second = LIB_DATA{};
1054}
1055
1056
1061
1062
1063void LIBRARY_MANAGER_ADAPTER::GlobalTablesChanged( std::initializer_list<LIBRARY_TABLE_TYPE> aChangedTables )
1064{
1065 bool me = aChangedTables.size() == 0;
1066
1067 for( LIBRARY_TABLE_TYPE type : aChangedTables )
1068 {
1069 if( type == Type() )
1070 {
1071 me = true;
1072 break;
1073 }
1074 }
1075
1076 if( !me )
1077 return;
1078
1079 abortLoad();
1080
1081 {
1082 std::unique_lock lock( globalLibsMutex() );
1083 globalLibs().clear();
1084 }
1085}
1086
1087
1089{
1090 abortLoad();
1091
1092 std::unique_lock lock( globalLibsMutex() );
1093
1094 // Drop entries this adapter owns, identified by global_owner rather than the row pointer,
1095 // which another manager could reuse at the same address after this manager frees its tables.
1096 for( auto it = globalLibs().begin(); it != globalLibs().end(); )
1097 {
1098 if( it->second.global_owner == this )
1099 it = globalLibs().erase( it );
1100 else
1101 ++it;
1102 }
1103}
1104
1105
1106void LIBRARY_MANAGER_ADAPTER::ProjectTablesChanged( std::initializer_list<LIBRARY_TABLE_TYPE> aChangedTables )
1107{
1108 bool me = aChangedTables.size() == 0;
1109
1110 for( LIBRARY_TABLE_TYPE type : aChangedTables )
1111 {
1112 if( type == Type() )
1113 {
1114 me = true;
1115 break;
1116 }
1117 }
1118
1119 if( !me )
1120 return;
1121
1123}
1124
1125
1127 std::initializer_list<LIBRARY_TABLE_TYPE> aChangedTables )
1128{
1129 bool me = aChangedTables.size() == 0;
1130
1131 for( LIBRARY_TABLE_TYPE type : aChangedTables )
1132 {
1133 if( type == Type() )
1134 {
1135 me = true;
1136 break;
1137 }
1138 }
1139
1140 if( !me )
1141 return;
1142
1143 // Erase sentinels installed by resetProjectCache() for nicknames that no
1144 // longer appear in the rebuilt project table. Without this, a library
1145 // removed from the project would remain masked in m_libraries and hide a
1146 // same-named global library from HasLibrary() / fetchIfLoaded() / etc.
1147 // GetRow() is safe to call here: loadTables() reset m_rowCache before
1148 // building the new table, and async loads were aborted in phase 1.
1149 std::unique_lock lock( m_librariesMutex );
1150
1151 std::erase_if( m_libraries,
1152 [this]( const auto& aEntry )
1153 {
1154 return !m_manager.GetRow( Type(), aEntry.first,
1155 LIBRARY_TABLE_SCOPE::PROJECT ).has_value();
1156 } );
1157}
1158
1159
1161{
1162 // Testing is expensive; skip it if we already have a library with the same
1163 // nickname and URI as the row under test
1164 if( std::optional<LIB_DATA*> libData = fetchIfLoaded( aRow.Nickname() ) )
1165 {
1166 const LIBRARY_TABLE_ROW* loadedRow = ( *libData )->row;
1167
1168 if( loadedRow->URI() == aRow.URI() && loadedRow->Type() == aRow.Type() )
1169 {
1170 aRow.SetOk( loadedRow->IsOk() );
1171 return;
1172 }
1173 }
1174
1175 abortLoad();
1176
1178
1179 if( plugin.has_value() )
1180 {
1181 LIB_DATA lib;
1182 lib.row = &aRow;
1183 lib.plugin.reset( *plugin );
1184
1185 std::optional<LIB_STATUS> status = LoadOne( &lib );
1186
1187 if( status.has_value() )
1188 {
1189 aRow.SetOk( status.value().load_status == LOAD_STATUS::LOADED );
1190
1191 if( status.value().error.has_value() )
1192 aRow.SetErrorDescription( status.value().error.value().message );
1193 }
1194 }
1195 else if( plugin.error().message == LIBRARY_TABLE_OK().message )
1196 {
1197 aRow.SetOk( true );
1198 aRow.SetErrorDescription( wxEmptyString );
1199 }
1200 else
1201 {
1202 aRow.SetOk( false );
1203 aRow.SetErrorDescription( plugin.error().message );
1204 }
1205}
1206
1207
1208
1210{
1211 wxCHECK( m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::GLOBAL ), nullptr );
1212 return *m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::GLOBAL );
1213}
1214
1215
1216std::optional<LIBRARY_TABLE*> LIBRARY_MANAGER_ADAPTER::ProjectTable() const
1217{
1218 return m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::PROJECT );
1219}
1220
1221
1222std::optional<wxString> LIBRARY_MANAGER_ADAPTER::FindLibraryByURI( const wxString& aURI ) const
1223{
1224 for( const LIBRARY_TABLE_ROW* row : m_manager.Rows( Type() ) )
1225 {
1226 if( LIBRARY_MANAGER::UrisAreEquivalent( row->URI(), aURI ) )
1227 return row->Nickname();
1228 }
1229
1230 return std::nullopt;
1231}
1232
1233
1234std::vector<wxString> LIBRARY_MANAGER_ADAPTER::GetLibraryNames() const
1235{
1236 std::vector<wxString> ret;
1237 std::vector<LIBRARY_TABLE_ROW*> rows = m_manager.Rows( Type() );
1238
1239 wxLogTrace( traceLibraries, "GetLibraryNames: checking %zu rows from table", rows.size() );
1240
1241 for( const LIBRARY_TABLE_ROW* row : rows )
1242 {
1243 wxString nickname = row->Nickname();
1244 std::optional<const LIB_DATA*> loaded = fetchIfLoaded( nickname );
1245
1246 if( loaded )
1247 ret.emplace_back( nickname );
1248 }
1249
1250 wxLogTrace( traceLibraries, "GetLibraryNames: returning %zu of %zu libraries", ret.size(), rows.size() );
1251 return ret;
1252}
1253
1254
1255bool LIBRARY_MANAGER_ADAPTER::HasLibrary( const wxString& aNickname, bool aCheckEnabled ) const
1256{
1257 std::optional<const LIB_DATA*> r = fetchIfLoaded( aNickname );
1258
1259 if( r.has_value() )
1260 return !aCheckEnabled || !r.value()->row->Disabled();
1261
1262 return false;
1263}
1264
1265
1266bool LIBRARY_MANAGER_ADAPTER::DeleteLibrary( const wxString& aNickname )
1267{
1268 if( LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( aNickname ); result.has_value() )
1269 {
1270 LIB_DATA* data = *result;
1271 std::map<std::string, UTF8> options = data->row->GetOptionsMap();
1272
1273 try
1274 {
1275 return data->plugin->DeleteLibrary( getUri( data->row ), &options );
1276 }
1277 catch( ... )
1278 {
1279 return false;
1280 }
1281 }
1282
1283 return false;
1284}
1285
1286
1287std::optional<wxString> LIBRARY_MANAGER_ADAPTER::GetLibraryDescription( const wxString& aNickname ) const
1288{
1289 if( std::optional<const LIB_DATA*> optRow = fetchIfLoaded( aNickname ); optRow )
1290 return ( *optRow )->row->Description();
1291
1292 return std::nullopt;
1293}
1294
1295
1296std::vector<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::Rows( LIBRARY_TABLE_SCOPE aScope,
1297 bool aIncludeInvalid ) const
1298{
1299 return m_manager.Rows( Type(), aScope, aIncludeInvalid );
1300}
1301
1302
1303std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::GetRow( const wxString &aNickname,
1304 LIBRARY_TABLE_SCOPE aScope ) const
1305{
1306 return m_manager.GetRow( Type(), aNickname, aScope );
1307}
1308
1309
1310std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::FindRowByURI(
1311 const wxString& aUri,
1312 LIBRARY_TABLE_SCOPE aScope ) const
1313{
1314 return m_manager.FindRowByURI( Type(), aUri, aScope );
1315}
1316
1317
1319{
1320 {
1321 std::lock_guard lock( m_loadMutex );
1322
1323 if( m_futures.empty() )
1324 return;
1325
1326 wxLogTrace( traceLibraries, "Aborting library load..." );
1327 m_abort.store( true );
1328 }
1329
1331 wxLogTrace( traceLibraries, "Aborted" );
1332
1333 {
1334 std::lock_guard lock( m_loadMutex );
1335 m_abort.store( false );
1336 m_futures.clear();
1337 m_loadTotal.store( 0 );
1338 m_loadCount.store( 0 );
1339 }
1340}
1341
1342
1344{
1345 size_t total = m_loadTotal.load();
1346
1347 if( total == 0 )
1348 return std::nullopt;
1349
1350 size_t loaded = m_loadCount.load();
1351 return loaded / static_cast<float>( total );
1352}
1353
1354
1356{
1357 wxLogTrace( traceLibraries, "BlockUntilLoaded: entry, acquiring m_loadMutex" );
1358 std::unique_lock<std::mutex> asyncLock( m_loadMutex );
1359
1360 wxLogTrace( traceLibraries, "BlockUntilLoaded: waiting on %zu futures", m_futures.size() );
1361
1362 for( const std::future<void>& future : m_futures )
1363 future.wait();
1364
1365 wxLogTrace( traceLibraries, "BlockUntilLoaded: all futures complete, loadCount=%zu, loadTotal=%zu",
1366 m_loadCount.load(), m_loadTotal.load() );
1367}
1368
1369
1370bool LIBRARY_MANAGER_ADAPTER::IsLibraryLoaded( const wxString& aNickname )
1371{
1372 {
1373 std::shared_lock lock( m_librariesMutex );
1374
1375 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1376 return it->second.status.load_status == LOAD_STATUS::LOADED;
1377 }
1378
1379 {
1380 std::shared_lock lock( globalLibsMutex() );
1381
1382 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1383 return it->second.status.load_status == LOAD_STATUS::LOADED;
1384 }
1385
1386 return false;
1387}
1388
1389
1390std::optional<LIBRARY_ERROR> LIBRARY_MANAGER_ADAPTER::LibraryError( const wxString& aNickname ) const
1391{
1392 {
1393 std::shared_lock lock( m_librariesMutex );
1394
1395 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1396 return it->second.status.error;
1397 }
1398
1399 {
1400 std::shared_lock lock( globalLibsMutex() );
1401
1402 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1403 return it->second.status.error;
1404 }
1405
1406 return std::nullopt;
1407}
1408
1409
1410std::vector<std::pair<wxString, LIB_STATUS>> LIBRARY_MANAGER_ADAPTER::GetLibraryStatuses() const
1411{
1412 std::vector<std::pair<wxString, LIB_STATUS>> ret;
1413
1414 for( const LIBRARY_TABLE_ROW* row : m_manager.Rows( Type() ) )
1415 {
1416 if( row->Disabled() )
1417 continue;
1418
1419 if( std::optional<LIB_STATUS> result = GetLibraryStatus( row->Nickname() ) )
1420 {
1421 ret.emplace_back( std::make_pair( row->Nickname(), *result ) );
1422 }
1423 else
1424 {
1425 // This should probably never happen, but until that can be proved...
1426 ret.emplace_back( std::make_pair( row->Nickname(),
1427 LIB_STATUS( {
1428 .load_status = LOAD_STATUS::LOAD_ERROR,
1429 .error = LIBRARY_ERROR( _( "Library not found in library table" ) )
1430 } ) ) );
1431 }
1432 }
1433
1434 return ret;
1435}
1436
1437
1439{
1440 wxString errors;
1441
1442 for( const auto& [nickname, status] : GetLibraryStatuses() )
1443 {
1444 if( status.load_status == LOAD_STATUS::LOAD_ERROR && status.error )
1445 {
1446 if( !errors.IsEmpty() )
1447 errors += wxS( "\n" );
1448
1449 errors += wxString::Format( _( "Library '%s': %s" ),
1450 nickname, status.error->message );
1451 }
1452 }
1453
1454 return errors;
1455}
1456
1457
1458std::optional<LIB_STATUS> LIBRARY_MANAGER_ADAPTER::LoadLibraryEntry( const wxString& aNickname )
1459{
1461
1462 if( result.has_value() )
1463 return LoadOne( *result );
1464
1465 return std::nullopt;
1466}
1467
1468
1470{
1471 auto reloadScope =
1472 [&]( LIBRARY_TABLE_SCOPE aScopeToReload, std::map<wxString, LIB_DATA>& aTarget,
1473 std::shared_mutex& aMutex )
1474 {
1475 bool wasLoaded = false;
1476
1477 {
1478 std::unique_lock lock( aMutex );
1479 auto it = aTarget.find( aNickname );
1480
1481 if( it != aTarget.end() && it->second.plugin )
1482 {
1483 wasLoaded = true;
1484 aTarget.erase( it );
1485 }
1486 }
1487
1488 if( wasLoaded )
1489 {
1490 LIBRARY_RESULT<LIB_DATA*> result = loadFromScope( aNickname, aScopeToReload, aTarget, aMutex );
1491
1492 if( !result.has_value() )
1493 {
1494 wxLogTrace( traceLibraries, "ReloadLibraryEntry: failed to reload %s (%s): %s",
1495 aNickname, magic_enum::enum_name( aScopeToReload ),
1496 result.error().message );
1497 }
1498 }
1499 };
1500
1501 switch( aScope )
1502 {
1505 break;
1506
1509 break;
1510
1515 break;
1516 }
1517}
1518
1519
1520bool LIBRARY_MANAGER_ADAPTER::IsWritable( const wxString& aNickname ) const
1521{
1522 if( std::optional<const LIB_DATA*> result = fetchIfLoaded( aNickname ) )
1523 {
1524 const LIB_DATA* rowData = *result;
1525 return rowData->plugin->IsLibraryWritable( getUri( rowData->row ) );
1526 }
1527
1528 return false;
1529}
1530
1531
1532bool LIBRARY_MANAGER_ADAPTER::CreateLibrary( const wxString& aNickname )
1533{
1534 if( LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( aNickname ); result.has_value() )
1535 {
1536 LIB_DATA* data = *result;
1537 std::map<std::string, UTF8> options = data->row->GetOptionsMap();
1538
1539 try
1540 {
1541 data->plugin->CreateLibrary( getUri( data->row ), &options );
1542 return true;
1543 }
1544 catch( const IO_ERROR& ioe )
1545 {
1546 wxLogTrace( traceLibraries, "CreateLibrary: IO_ERROR for %s: %s",
1547 aNickname, ioe.What() );
1548 return false;
1549 }
1550 catch( const std::exception& e )
1551 {
1552 wxLogTrace( traceLibraries, "CreateLibrary: std::exception for %s: %s",
1553 aNickname, e.what() );
1554 return false;
1555 }
1556 }
1557
1558 wxLogTrace( traceLibraries, "CreateLibrary: library row '%s' not found", aNickname );
1559 return false;
1560}
1561
1562
1564{
1565 return LIBRARY_MANAGER::ExpandURI( aRow->URI(), Pgm().GetSettingsManager().Prj() );
1566}
1567
1568
1569std::optional<const LIB_DATA*> LIBRARY_MANAGER_ADAPTER::fetchIfLoaded( const wxString& aNickname ) const
1570{
1571 {
1572 std::shared_lock lock( m_librariesMutex );
1573
1574 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1575 {
1576 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1577 return &it->second;
1578
1579 return std::nullopt;
1580 }
1581 }
1582
1583 {
1584 std::shared_lock lock( globalLibsMutex() );
1585
1586 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1587 {
1588 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1589 return &it->second;
1590
1591 return std::nullopt;
1592 }
1593 }
1594
1595 return std::nullopt;
1596}
1597
1598
1599std::optional<LIB_DATA*> LIBRARY_MANAGER_ADAPTER::fetchIfLoaded( const wxString& aNickname )
1600{
1601 {
1602 std::shared_lock lock( m_librariesMutex );
1603
1604 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1605 {
1606 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1607 return &it->second;
1608
1609 return std::nullopt;
1610 }
1611 }
1612
1613 {
1614 std::shared_lock lock( globalLibsMutex() );
1615
1616 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1617 {
1618 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1619 return &it->second;
1620
1621 return std::nullopt;
1622 }
1623 }
1624
1625 return std::nullopt;
1626}
1627
1628
1630 LIBRARY_TABLE_SCOPE aScope,
1631 std::map<wxString, LIB_DATA>& aTarget,
1632 std::shared_mutex& aMutex )
1633{
1634 bool present = false;
1635
1636 {
1637 std::shared_lock lock( aMutex );
1638 present = aTarget.contains( aNickname ) && aTarget.at( aNickname ).plugin;
1639 }
1640
1641 if( !present )
1642 {
1643 if( auto result = m_manager.GetRow( Type(), aNickname, aScope ) )
1644 {
1645 const LIBRARY_TABLE_ROW* row = *result;
1646 wxLogTrace( traceLibraries, "Library %s (%s) not yet loaded, will attempt...",
1647 aNickname, magic_enum::enum_name( aScope ) );
1648
1649 if( LIBRARY_RESULT<IO_BASE*> plugin = createPlugin( row ); plugin.has_value() )
1650 {
1651 std::unique_lock lock( aMutex );
1652
1653 aTarget[ row->Nickname() ].status.load_status = LOAD_STATUS::LOADING;
1654 aTarget[ row->Nickname() ].row = row;
1655 aTarget[ row->Nickname() ].plugin.reset( *plugin );
1656
1657 if( aScope == LIBRARY_TABLE_SCOPE::GLOBAL )
1658 aTarget[ row->Nickname() ].global_owner = this;
1659
1660 return &aTarget.at( aNickname );
1661 }
1662 else
1663 {
1664 return tl::unexpected( plugin.error() );
1665 }
1666 }
1667
1668 return nullptr;
1669 }
1670
1671 std::shared_lock lock( aMutex );
1672 return &aTarget.at( aNickname );
1673}
1674
1675
1677{
1680
1681 if( !result.has_value() || *result )
1682 return result;
1683
1685
1686 if( !result.has_value() || *result )
1687 return result;
1688
1689 wxString msg = wxString::Format( _( "Library %s not found" ), aNickname );
1690 return tl::unexpected( LIBRARY_ERROR( msg ) );
1691}
1692
1693
1694std::optional<LIB_STATUS> LIBRARY_MANAGER_ADAPTER::GetLibraryStatus( const wxString& aNickname ) const
1695{
1696 {
1697 std::shared_lock lock( m_librariesMutex );
1698
1699 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1700 return it->second.status;
1701 }
1702
1703 {
1704 std::shared_lock lock( globalLibsMutex() );
1705
1706 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1707 return it->second.status;
1708 }
1709
1710 return std::nullopt;
1711}
1712
1713
1715{
1716 std::unique_lock<std::mutex> asyncLock( m_loadMutex, std::try_to_lock );
1717
1718 if( !asyncLock )
1719 return;
1720
1721 std::erase_if( m_futures,
1722 []( std::future<void>& aFuture )
1723 {
1724 return aFuture.valid()
1725 && aFuture.wait_for( 0s ) == std::future_status::ready;
1726 } );
1727
1728 if( !m_futures.empty() )
1729 {
1730 wxLogTrace( traceLibraries, "Cannot AsyncLoad, futures from a previous call remain!" );
1731 return;
1732 }
1733
1734 std::vector<LIBRARY_TABLE_ROW*> rows = m_manager.Rows( Type() );
1735
1736 m_loadTotal.store( rows.size() );
1737 m_loadCount.store( 0 );
1738
1739 if( rows.empty() )
1740 {
1741 wxLogTrace( traceLibraries, "AsyncLoad: no libraries left to load; exiting" );
1742 return;
1743 }
1744
1746
1747 auto check =
1748 [&]( const wxString& aLib, std::map<wxString, LIB_DATA>& aMap, std::shared_mutex& aMutex )
1749 {
1750 std::shared_lock lock( aMutex );
1751
1752 if( auto it = aMap.find( aLib ); it != aMap.end() )
1753 {
1754 LOAD_STATUS status = it->second.status.load_status;
1755
1756 if( status == LOAD_STATUS::LOADED || status == LOAD_STATUS::LOADING )
1757 return true;
1758 }
1759
1760 return false;
1761 };
1762
1763 // Collect work items with pre-resolved URIs. URI expansion accesses PROJECT data
1764 // (text variables, env vars) that is not thread-safe, so resolve on the calling thread.
1765 struct LOAD_WORK
1766 {
1767 wxString nickname;
1768 LIBRARY_TABLE_SCOPE scope;
1769 wxString uri;
1770 };
1771
1772 auto workQueue = std::make_shared<std::vector<LOAD_WORK>>();
1773 workQueue->reserve( rows.size() );
1774
1775 for( const LIBRARY_TABLE_ROW* row : rows )
1776 {
1777 wxString nickname = row->Nickname();
1778 LIBRARY_TABLE_SCOPE scope = row->Scope();
1779
1780 if( check( nickname, m_libraries, m_librariesMutex ) )
1781 {
1782 m_loadTotal.fetch_sub( 1 );
1783 continue;
1784 }
1785
1786 if( check( nickname, globalLibs(), globalLibsMutex() ) )
1787 {
1788 m_loadTotal.fetch_sub( 1 );
1789 continue;
1790 }
1791
1792 workQueue->emplace_back( LOAD_WORK{ nickname, scope, getUri( row ) } );
1793 }
1794
1795 if( workQueue->empty() )
1796 {
1797 wxLogTrace( traceLibraries, "AsyncLoad: all libraries already loaded; exiting" );
1798 return;
1799 }
1800
1801 // Cap loading threads to leave headroom for the GUI and other thread pool work.
1802 // Each worker pulls libraries from a shared queue, so we submit fewer tasks than
1803 // libraries and avoid flooding the pool.
1804 size_t poolSize = tp.get_thread_count();
1805 size_t maxLoadThreads = std::max<size_t>( 1, poolSize > 2 ? poolSize - 2 : 1 );
1806 size_t numWorkers = std::min( maxLoadThreads, workQueue->size() );
1807
1808 auto workIndex = std::make_shared<std::atomic<size_t>>( 0 );
1809
1810 wxLogTrace( traceLibraries, "AsyncLoad: %zu libraries to load, using %zu worker threads (pool has %zu)",
1811 workQueue->size(), numWorkers, poolSize );
1812
1813 for( size_t w = 0; w < numWorkers; ++w )
1814 {
1815 m_futures.emplace_back( tp.submit_task(
1816 [this, workQueue, workIndex]()
1817 {
1818 while( true )
1819 {
1820 if( m_abort.load() )
1821 return;
1822
1823 size_t idx = workIndex->fetch_add( 1 );
1824
1825 if( idx >= workQueue->size() )
1826 return;
1827
1828 const LOAD_WORK& work = ( *workQueue )[idx];
1829 LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( work.nickname );
1830
1831 if( result.has_value() )
1832 {
1833 LIB_DATA* lib = *result;
1834
1835 try
1836 {
1837 {
1838 std::unique_lock lock(
1839 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
1840 ? globalLibsMutex()
1841 : m_librariesMutex );
1842 lib->status.load_status = LOAD_STATUS::LOADING;
1843 }
1844
1845 enumerateLibrary( lib, work.uri );
1846
1847 {
1848 std::unique_lock lock(
1849 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
1850 ? globalLibsMutex()
1851 : m_librariesMutex );
1852 lib->status.load_status = LOAD_STATUS::LOADED;
1853 }
1854 }
1855 catch( IO_ERROR& e )
1856 {
1857 std::unique_lock lock(
1858 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
1859 ? globalLibsMutex()
1860 : m_librariesMutex );
1861 lib->status.load_status = LOAD_STATUS::LOAD_ERROR;
1862 lib->status.error = LIBRARY_ERROR( { e.What() } );
1863 wxLogTrace( traceLibraries, "%s: plugin threw exception: %s",
1864 work.nickname, e.What() );
1865 }
1866 }
1867 else
1868 {
1869 std::unique_lock lock(
1870 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
1871 ? globalLibsMutex()
1872 : m_librariesMutex );
1873
1874 std::map<wxString, LIB_DATA>& target =
1875 ( work.scope == LIBRARY_TABLE_SCOPE::GLOBAL ) ? globalLibs()
1876 : m_libraries;
1877
1878 target[work.nickname].status = LIB_STATUS( {
1879 .load_status = LOAD_STATUS::LOAD_ERROR,
1880 .error = result.error()
1881 } );
1882 }
1883
1884 ++m_loadCount;
1885 }
1886 }, BS::pr::lowest ) );
1887 }
1888
1889 wxLogTrace( traceLibraries, "Started async load of %zu libraries", workQueue->size() );
1890}
const char * name
virtual bool DeleteLibrary(const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr)
Delete an existing library and returns true, or if library does not exist returns false,...
Definition io_base.cpp:53
virtual bool IsLibraryWritable(const wxString &aLibraryPath)
Return true if the library at aLibraryPath is writable.
Definition io_base.cpp:60
virtual void CreateLibrary(const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr)
Create a new empty library at aLibraryPath empty.
Definition io_base.cpp:46
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
wxString m_PcmLibPrefix
std::map< wxString, std::map< wxString, LIB_OVERRIDE > > m_LibOverrides
Overrides for libraries in read-only nested tables.
The interface used by the classes that actually can load IO plugins for the different parts of KiCad ...
std::optional< float > AsyncLoadProgress() const
Returns async load progress between 0.0 and 1.0, or nullopt if load is not in progress.
virtual std::optional< LIB_STATUS > LoadOne(LIB_DATA *aLib)=0
void resetProjectCache()
Aborts pending loads and resets every project-scope cache entry in place (plugin and row cleared,...
virtual std::optional< LIBRARY_ERROR > LibraryError(const wxString &aNickname) const
void ReloadLibraryEntry(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH)
std::optional< LIB_STATUS > GetLibraryStatus(const wxString &aNickname) const
Returns the status of a loaded library, or nullopt if the library hasn't been loaded (yet)
std::optional< LIBRARY_TABLE * > ProjectTable() const
Retrieves the project library table for this adapter type, or nullopt if one doesn't exist.
void AbortAsyncLoad()
Aborts any async load in progress; blocks until fully done aborting.
void ProjectTablesChanged(std::initializer_list< LIBRARY_TABLE_TYPE > aChangedTables={})
Notify the adapter that the project library tables are about to be rebuilt.
LIBRARY_TABLE * GlobalTable() const
Retrieves the global library table for this adapter type.
virtual std::shared_mutex & globalLibsMutex()=0
void evictOwnedGlobalEntries()
Erases this adapter's own entries (LIB_DATA::global_owner == this) from the process-wide globalLibs()...
LIBRARY_MANAGER_ADAPTER(LIBRARY_MANAGER &aManager)
Constructs a type-specific adapter into the library manager.
void CheckTableRow(LIBRARY_TABLE_ROW &aRow)
bool IsLibraryLoaded(const wxString &aNickname)
std::vector< LIBRARY_TABLE_ROW * > Rows(LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH, bool aIncludeInvalid=false) const
Like LIBRARY_MANAGER::Rows but filtered to the LIBRARY_TABLE_TYPE of this adapter.
std::optional< LIBRARY_TABLE_ROW * > FindRowByURI(const wxString &aUri, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::FindRowByURI but filtered to the LIBRARY_TABLE_TYPE of this adapter.
virtual std::map< wxString, LIB_DATA > & globalLibs()=0
virtual LIBRARY_TABLE_TYPE Type() const =0
The type of library table this adapter works with.
bool DeleteLibrary(const wxString &aNickname)
Deletes the given library from disk if it exists; returns true if deleted.
LIBRARY_RESULT< LIB_DATA * > loadIfNeeded(const wxString &aNickname)
Fetches a loaded library, triggering a load of that library if it isn't loaded yet.
wxString GetLibraryLoadErrors() const
Returns all library load errors as newline-separated strings for display.
std::optional< wxString > FindLibraryByURI(const wxString &aURI) const
std::shared_mutex m_librariesMutex
LIBRARY_MANAGER & Manager() const
void abortLoad()
Aborts any async load in progress; blocks until fully done aborting.
std::optional< wxString > GetLibraryDescription(const wxString &aNickname) const
virtual LIBRARY_RESULT< IO_BASE * > createPlugin(const LIBRARY_TABLE_ROW *row)=0
Creates a concrete plugin for the given row.
void GlobalTablesChanged(std::initializer_list< LIBRARY_TABLE_TYPE > aChangedTables={})
Notify the adapter that the global library tables have changed.
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library tables.
std::optional< LIBRARY_TABLE_ROW * > GetRow(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::GetRow but filtered to the LIBRARY_TABLE_TYPE of this adapter.
std::map< wxString, LIB_DATA > m_libraries
virtual IO_BASE * plugin(const LIB_DATA *aRow)=0
std::vector< wxString > GetLibraryNames() const
Returns a list of library nicknames that are available (skips any that failed to load)
virtual void ProjectChanged()
Notify the adapter that the active project has changed.
LIBRARY_RESULT< LIB_DATA * > loadFromScope(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope, std::map< wxString, LIB_DATA > &aTarget, std::shared_mutex &aMutex)
std::vector< std::future< void > > m_futures
static wxString getUri(const LIBRARY_TABLE_ROW *aRow)
bool CreateLibrary(const wxString &aNickname)
Creates the library (i.e. saves to disk) for the given row if it exists.
void AsyncLoad()
Loads all available libraries for this adapter type in the background.
virtual bool IsWritable(const wxString &aNickname) const
Return true if the given nickname exists and is not a read-only library.
std::vector< std::pair< wxString, LIB_STATUS > > GetLibraryStatuses() const
Returns a list of all library nicknames and their status (even if they failed to load)
std::atomic< size_t > m_loadTotal
void ProjectTablesReloaded(std::initializer_list< LIBRARY_TABLE_TYPE > aChangedTables={})
Complements ProjectTablesChanged by erasing project-scope cache entries whose nicknames no longer app...
std::atomic< size_t > m_loadCount
LIBRARY_MANAGER & m_manager
std::optional< LIB_STATUS > LoadLibraryEntry(const wxString &aNickname)
Synchronously loads the named library to LOADED state.
std::optional< const LIB_DATA * > fetchIfLoaded(const wxString &aNickname) const
void ReloadTables(LIBRARY_TABLE_SCOPE aScope, std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
static wxString StockTableTokenizedURI(LIBRARY_TABLE_TYPE aType)
static wxString ExpandURI(const wxString &aShortURI, const PROJECT &aProject)
std::mutex m_rowCacheMutex
void ClearLibOverride(const wxString &aTablePath, const wxString &aNickname)
Removes any override for a library that no longer needs one.
std::optional< LIBRARY_MANAGER_ADAPTER * > Adapter(LIBRARY_TABLE_TYPE aType) const
void applyLibOverrides(LIBRARY_TABLE &aTable)
Applies user overrides (disabled/hidden) to rows of a read-only nested table.
std::map< wxString, std::unique_ptr< LIBRARY_TABLE > > m_childTables
Map of full URI to table object for tables that are referenced by global or project tables.
void loadNestedTables(LIBRARY_TABLE &aTable)
std::map< ROW_CACHE_KEY, LIBRARY_TABLE_ROW * > m_rowCache
bool RemoveAdapter(LIBRARY_TABLE_TYPE aType, LIBRARY_MANAGER_ADAPTER *aAdapter)
void ReloadLibraryEntry(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH)
static bool UrisAreEquivalent(const wxString &aURI1, const wxString &aURI2)
std::mutex m_adaptersMutex
std::optional< LIBRARY_TABLE * > Table(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope)
Retrieves a given table; creating a new empty project table if a valid project is loaded and the give...
void ApplyLibOverrides(LIBRARY_TABLE &aTable)
Applies stored user overrides (disabled/hidden) to rows of a read-only table.
void RegisterAdapter(LIBRARY_TABLE_TYPE aType, std::unique_ptr< LIBRARY_MANAGER_ADAPTER > &&aAdapter)
static wxString DefaultGlobalTablePath(LIBRARY_TABLE_TYPE aType)
std::optional< wxString > GetFullURI(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, bool aSubstituted=false)
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
static std::vector< LIBRARY_TABLE_TYPE > InvalidGlobalTables()
void createEmptyTable(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope)
void AbortAsyncLoads()
Abort any async library loading operations in progress.
std::optional< LIB_STATUS > LoadLibraryEntry(LIBRARY_TABLE_TYPE aType, const wxString &aNickname)
Synchronously loads the named library to LOADED state for the given type.
static bool GlobalTablesValid()
std::map< LIBRARY_TABLE_TYPE, std::unique_ptr< LIBRARY_TABLE > > m_projectTables
std::optional< LIBRARY_TABLE_ROW * > FindRowByURI(LIBRARY_TABLE_TYPE aType, const wxString &aUri, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
void SetLibOverride(const wxString &aTablePath, const wxString &aNickname, bool aDisabled, bool aHidden)
Set a user override for a library in a read-only nested table.
void LoadProjectTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the project library tables in the given list, or all tables if no list is given
static bool CreateGlobalTable(LIBRARY_TABLE_TYPE aType, bool aPopulateDefaultLibraries)
void loadTables(const wxString &aTablePath, LIBRARY_TABLE_SCOPE aScope, std::vector< LIBRARY_TABLE_TYPE > aTablesToLoad={})
static wxString tableFileName(LIBRARY_TABLE_TYPE aType)
static bool IsPcmManagedRow(const LIBRARY_TABLE_ROW &aRow)
Return true if a library table row was added by the Plugin and Content Manager.
std::vector< LIBRARY_TABLE_ROW * > Rows(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH, bool aIncludeInvalid=false) const
Returns a flattened list of libraries of the given type.
void LoadGlobalTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the global library tables in the given list, or all tables if no list is given
std::map< LIBRARY_TABLE_TYPE, std::unique_ptr< LIBRARY_TABLE > > m_tables
static wxString StockTableReferenceURI(LIBRARY_TABLE_TYPE aType)
static wxString StockTablePath(LIBRARY_TABLE_TYPE aType)
std::optional< LIBRARY_TABLE_ROW * > GetRow(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH)
void ProjectChanged()
Notify all adapters that the project has changed.
static bool IsTableValid(const wxString &aPath)
std::map< LIBRARY_TABLE_TYPE, std::unique_ptr< LIBRARY_MANAGER_ADAPTER > > m_adapters
void SetNickname(const wxString &aNickname)
void SetOk(bool aOk=true)
void SetType(const wxString &aType)
void SetErrorDescription(const wxString &aDescription)
std::map< std::string, UTF8 > GetOptionsMap() const
void SetDescription(const wxString &aDescription)
const wxString & Type() const
static const wxString TABLE_TYPE_NAME
void SetURI(const wxString &aUri)
bool IsOk() const
const wxString & URI() const
const wxString & Nickname() const
bool IsReadOnly() const
Returns true if the underlying file exists but is not writable.
LIBRARY_TABLE_ROW & InsertRow()
Builds a new row and inserts it at the end of the table; returning a reference to the row.
const wxString & Path() const
LIBRARY_TABLE_SCOPE Scope() const
bool HasRow(const wxString &aNickname) const
bool HasRowWithURI(const wxString &aUri, const PROJECT &aProject, bool aSubstituted=false) const
Returns true if the given (fully-expanded) URI exists as a library in this table.
const std::deque< LIBRARY_TABLE_ROW > & Rows() const
bool IsOk() const
static wxString GetStockTemplatesPath()
Gets the stock (install) templates path.
Definition paths.cpp:355
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition paths.cpp:634
LIBRARY_TABLE * m_designBlockTable
std::set< LIBRARY_TABLE * > m_modified
LIBRARY_MANAGER & m_manager
std::set< LIBRARY_TABLE * > Modified() const
const PROJECT & m_project
PCM_LIB_TRAVERSER(const wxString &aBasePath, LIBRARY_MANAGER &aManager, const wxString &aPrefix)
void ensureUnique(LIBRARY_TABLE *aTable, const wxString &aBaseName, wxString &aNickname) const
LIBRARY_TABLE * m_symbolTable
wxDirTraverseResult OnDir(const wxString &dirPath) override
Handles footprint library and design block library directories, minimum nest level 3.
void addRowIfNecessary(LIBRARY_TABLE *aTable, const wxFileName &aSource, ADD_MODE aMode, int aExtensionLength)
LIBRARY_TABLE * m_fpTable
wxDirTraverseResult OnFile(const wxString &aFilePath) override
Handles symbol library files, minimum nest level 2.
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:774
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:124
bool Finish() override
Runs prettification over the buffered bytes, writes them to the sibling temp file,...
Definition richio.cpp:696
Container for project specific data.
Definition project.h:62
T * GetAppSettings(const char *aFilename)
Return a handle to the a given settings by type.
static void ResolvePossibleSymlinks(wxFileName &aFilename)
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:704
The common library.
#define _(s)
Functions related to environment variables, including help functions.
static const std::string KiCadDesignBlockLibPathExtension
static const std::string SymbolLibraryTableFileName
static const std::string DesignBlockLibraryTableFileName
static const std::string FootprintLibraryTableFileName
const wxChar *const traceLibraries
Flag to enable library table and library manager tracing.
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
PROJECT & Prj()
Definition kicad.cpp:730
LOAD_STATUS
Status of a library load managed by a library adapter.
tl::expected< ResultType, LIBRARY_ERROR > LIBRARY_RESULT
LIBRARY_TABLE_TYPE
LIBRARY_TABLE_SCOPE
KICOMMON_API std::optional< wxString > GetVersionedEnvVarValue(const std::map< wxString, ENV_VAR_ITEM > &aMap, const wxString &aBaseName)
Attempt to retrieve the value of a versioned environment variable, such as KICAD8_TEMPLATE_DIR.
Definition env_vars.cpp:86
KICOMMON_API wxString GetVersionedEnvVarName(const wxString &aBaseName)
Construct a versioned environment variable based on this KiCad major version.
Definition env_vars.cpp:77
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
wxString message
std::vector< LIBRARY_TABLE > tables
Storage for an actual loaded library (including library content owned by the plugin)
std::unique_ptr< IO_BASE > plugin
const LIBRARY_TABLE_ROW * row
The overall status of a loaded or loading library.
std::string path
std::vector< std::vector< std::string > > table
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
wxLogTrace helper definitions.
Definition of file extensions used in Kicad.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:35