KiCad PCB EDA Suite
Loading...
Searching...
No Matches
symbol_import_reconciler.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 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <map>
23#include <set>
24
25#include <wx/filefn.h>
26#include <wx/filename.h>
27
29#include <lib_id.h>
30#include <lib_symbol.h>
31#include <project.h>
32#include <project_sch.h>
33#include <reporter.h>
34#include <schematic.h>
35#include <sch_pin.h>
36#include <sch_screen.h>
37#include <sch_symbol.h>
39#include <io/io_mgr.h>
40#include <sch_io/sch_io.h>
41#include <sch_io/sch_io_mgr.h>
45
46
48 const wxString& aProjectPath,
49 REPORTER& aReporter ) :
50 m_adapter( aAdapter ),
51 m_projectPath( aProjectPath ),
52 m_reporter( aReporter )
53{
54}
55
56
57namespace
58{
59// every screen once, so a sheet instantiated many times does not re-walk its symbols
60std::vector<SCH_SYMBOL*> placedSymbols( SCHEMATIC* aSchematic )
61{
62 std::vector<SCH_SYMBOL*> symbols;
63 SCH_SCREENS screens( aSchematic->Root() );
64
65 for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
66 {
67 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
68 symbols.push_back( static_cast<SCH_SYMBOL*>( item ) );
69 }
70
71 return symbols;
72}
73
74
75// structural signature, flags same-name placed instances that differ
76wxString placedSignature( const SCH_SYMBOL* aSymbol )
77{
78 const std::unique_ptr<LIB_SYMBOL>& part = aSymbol->GetLibSymbolRef();
79
80 if( !part )
81 return wxEmptyString;
82
83 return wxString::Format( wxS( "%d:%d" ), part->GetPinCount(), part->GetUnitCount() );
84}
85
86
87// lookup that tolerates an unreadable or absent library
88LIB_SYMBOL* loadSymbol( SYMBOL_LIBRARY_ADAPTER& aAdapter, const wxString& aNickname,
89 const wxString& aName )
90{
91 try
92 {
93 return aAdapter.LoadSymbol( aNickname, aName );
94 }
95 catch( const IO_ERROR& )
96 {
97 return nullptr;
98 }
99}
100
101
102std::multiset<wxString> pinNumbers( const LIB_SYMBOL& aSymbol )
103{
104 std::multiset<wxString> numbers;
105
106 for( const SCH_PIN* pin : aSymbol.GetPins() )
107 numbers.insert( pin->GetNumber() );
108
109 return numbers;
110}
111
112
113// two tools never draw a part identically, so equivalence is the electrical interface
114bool sameInterface( const LIB_SYMBOL& aLhs, const LIB_SYMBOL& aRhs )
115{
116 return aLhs.GetUnitCount() == aRhs.GetUnitCount() && pinNumbers( aLhs ) == pinNumbers( aRhs );
117}
118
119
120// reuse existing row/file only if prior import-managed cache
121bool isManagedCache( const LIBRARY_TABLE_ROW* aRow )
122{
123 return aRow && aRow->GetOptionsMap().count( IMPORT_PROJ_PROPS::MANAGED_CACHE_KEY ) > 0;
124}
125}
126
127
130 std::vector<std::unique_ptr<LIB_SYMBOL>> aDefinitions,
131 const wxString& aCacheNickname,
132 const std::vector<wxString>& aSourceLibNicknames )
133{
135
136 if( !aSchematic )
137 return result;
138
139 std::map<wxString, LIB_SYMBOL*> defByName;
140
141 for( const std::unique_ptr<LIB_SYMBOL>& def : aDefinitions )
142 {
143 wxString name = def->GetLibId().GetUniStringLibItemName();
144
145 if( name.IsEmpty() )
146 name = def->GetName();
147
148 if( !name.IsEmpty() )
149 defByName.emplace( name, def.get() );
150 }
151
152 // preload source libs before membership queries
153 for( const wxString& nick : aSourceLibNicknames )
154 {
155 if( m_adapter.GetRow( nick ) )
156 m_adapter.LoadOne( nick );
157 }
158
159 std::set<wxString> provenance( aSourceLibNicknames.begin(), aSourceLibNicknames.end() );
160
161 // resolve one source lib, empty if none or ambiguous
162 auto resolveSource = [&]( const SCH_SYMBOL* aSymbol, const wxString& aName ) -> wxString
163 {
164 std::vector<wxString> candidates;
165 wxString ownNick = aSymbol->GetLibId().GetUniStringLibNickname();
166
167 if( !ownNick.IsEmpty() )
168 candidates.push_back( ownNick );
169
170 for( const wxString& nick : aSourceLibNicknames )
171 {
172 if( nick != ownNick )
173 candidates.push_back( nick );
174 }
175
176 std::vector<wxString> matches;
177
178 for( const wxString& nick : candidates )
179 {
180 if( !m_adapter.GetRow( nick ) )
181 continue;
182
183 LIB_SYMBOL* candidate = loadSymbol( m_adapter, nick, aName );
184
185 if( !candidate )
186 continue;
187
188 // A nickname the importer emitted is not provenance. An unrelated library that
189 // happens to carry the name must not swallow the imported definition, so it takes the
190 // link only when it holds the same part.
191 if( !provenance.count( nick ) )
192 {
193 auto def = defByName.find( aName );
194
195 if( def == defByName.end() || !sameInterface( *candidate, *def->second ) )
196 continue;
197 }
198
199 matches.push_back( nick );
200 }
201
202 return matches.size() == 1 ? matches.front() : wxString( wxEmptyString );
203 };
204
205 std::vector<SCH_SYMBOL*> symbols = placedSymbols( aSchematic );
206
207 // per-instance target keyed by nick+name, so same-name parts from different libs stay split
208 // empty target = cache-bound
209 std::map<wxString, wxString> targetByKey;
210 std::set<wxString> cacheNames;
211 std::map<wxString, std::vector<SCH_SYMBOL*>> instancesByName;
212
213 auto keyOf = []( const wxString& aNick, const wxString& aName )
214 {
215 return aNick + wxS( "\x1f" ) + aName;
216 };
217
218 for( SCH_SYMBOL* symbol : symbols )
219 {
220 wxString name = symbol->GetLibId().GetUniStringLibItemName();
221
222 if( name.IsEmpty() )
223 continue;
224
225 instancesByName[name].push_back( symbol );
226
227 wxString key = keyOf( symbol->GetLibId().GetUniStringLibNickname(), name );
228
229 if( targetByKey.count( key ) )
230 continue;
231
232 wxString sourceNick = resolveSource( symbol, name );
233 targetByKey[key] = sourceNick;
234
235 if( sourceNick.IsEmpty() )
236 cacheNames.insert( name );
237 }
238
239 // canonical def per cache name, fall back to the placed instance cache if importer gave none
240 std::map<wxString, LIB_SYMBOL*> cacheDefs;
241 std::vector<std::unique_ptr<LIB_SYMBOL>> placedDefs;
242
243 for( const wxString& name : cacheNames )
244 {
245 if( auto it = defByName.find( name ); it != defByName.end() )
246 {
247 cacheDefs[name] = it->second;
248 continue;
249 }
250
251 const std::vector<SCH_SYMBOL*>& instances = instancesByName[name];
252
253 if( instances.empty() || !instances.front()->GetLibSymbolRef() )
254 continue;
255
256 wxString firstSig = placedSignature( instances.front() );
257
258 for( auto it = instances.begin() + 1; it != instances.end(); ++it )
259 {
260 if( placedSignature( *it ) != firstSig )
261 {
262 m_reporter.Report( wxString::Format( _( "Imported symbol '%s' has conflicting "
263 "placed definitions; keeping the first." ),
264 name ),
266 break;
267 }
268 }
269
270 placedDefs.push_back(
271 std::make_unique<LIB_SYMBOL>( *instances.front()->GetLibSymbolRef() ) );
272 cacheDefs[name] = placedDefs.back().get();
273 }
274
275 if( !cacheDefs.empty() )
276 writeAndRegisterCache( aCacheNickname, cacheDefs, result );
277
278 // re-point nicks to the resolved lib, keep the item name
279 for( SCH_SYMBOL* symbol : symbols )
280 {
281 LIB_ID libId = symbol->GetLibId();
282 wxString name = libId.GetUniStringLibItemName();
283
284 if( name.IsEmpty() )
285 continue;
286
287 auto it = targetByKey.find( keyOf( libId.GetUniStringLibNickname(), name ) );
288
289 if( it == targetByKey.end() )
290 {
291 result.m_unresolved++;
292 continue;
293 }
294
295 // empty resolution = cache-bound, resolves only once the cache is published
296 if( it->second.IsEmpty() )
297 {
298 if( result.m_cacheNickname.IsEmpty() )
299 {
300 result.m_unresolved++;
301 continue;
302 }
303
304 libId.SetLibNickname( aCacheNickname );
305 symbol->SetLibId( libId );
306 result.m_linkedToCache++;
307 }
308 else
309 {
310 libId.SetLibNickname( it->second );
311 symbol->SetLibId( libId );
312 result.m_linkedToSource++;
313 }
314 }
315
316 return result;
317}
318
319
321 const wxString& aCacheNickname, const std::map<wxString, LIB_SYMBOL*>& aCacheDefs,
323{
324 wxFileName finalFn( m_projectPath, aCacheNickname, FILEEXT::KiCadSymbolLibFileExtension );
325 wxString finalPath = finalFn.GetFullPath();
326 wxString tempPath = finalPath + wxS( ".tmp" );
327
328 // a nickname the user already owns is never repurposed, whatever its row points at
329 LIBRARY_TABLE_ROW* existingRow = m_adapter.GetRow( aCacheNickname ).value_or( nullptr );
330
331 if( existingRow && !isManagedCache( existingRow ) )
332 {
333 m_reporter.Report( wxString::Format( _( "A symbol library named '%s' is already "
334 "registered; leaving imported symbols "
335 "unresolved." ), aCacheNickname ),
337 return;
338 }
339
340 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
341
342 if( !pi )
343 {
344 m_reporter.Report( _( "Cannot reconcile imported symbols: no KiCad symbol writer." ),
346 return;
347 }
348
349 // best-effort cleanup, must not throw
350 auto safeDelete = [&pi]( const wxString& aPath )
351 {
352 try
353 {
354 pi->DeleteLibrary( aPath );
355 }
356 catch( const IO_ERROR& )
357 {
358 }
359 };
360
361 bool wrote = false;
362
363 try
364 {
365 if( wxFileExists( tempPath ) )
366 pi->DeleteLibrary( tempPath );
367
368 pi->CreateLibrary( tempPath );
369
370 // buffer the writes, the library is flushed once by SaveLibrary
371 std::map<std::string, UTF8> properties;
372 properties.emplace( SCH_IO_KICAD_SEXPR::PropBuffering, wxEmptyString );
373
374 for( const auto& [name, def] : aCacheDefs )
375 {
376 std::unique_ptr<LIB_SYMBOL> copy = std::make_unique<LIB_SYMBOL>( *def );
377 LIB_ID id = copy->GetLibId();
378
379 id.SetLibNickname( aCacheNickname );
380 copy->SetLibId( id );
381 pi->SaveSymbol( tempPath, copy.release(), &properties );
382 }
383
384 pi->SaveLibrary( tempPath );
385 wrote = true;
386 }
387 catch( const IO_ERROR& ioe )
388 {
389 m_reporter.Report( wxString::Format( _( "Error writing imported symbol cache '%s': %s" ),
390 aCacheNickname, ioe.What() ),
392 }
393
394 if( !wrote )
395 {
396 if( wxFileExists( tempPath ) )
397 safeDelete( tempPath );
398
399 return;
400 }
401
402 // publish temp->final, replace only a managed cache, never a user lib
403 if( wxFileExists( finalPath ) )
404 {
405 if( isManagedCache( existingRow ) )
406 {
407 safeDelete( finalPath );
408 }
409 else
410 {
411 m_reporter.Report( wxString::Format( _( "A library already exists at '%s'; leaving "
412 "imported symbols unresolved." ), finalPath ),
414 safeDelete( tempPath );
415 return;
416 }
417 }
418
419 if( !wxRenameFile( tempPath, finalPath, false ) )
420 {
421 m_reporter.Report( wxString::Format( _( "Could not publish imported symbol cache to "
422 "'%s'." ), finalPath ),
424 safeDelete( tempPath );
425 return;
426 }
427
428 // only claim the cache when its table row is registered, else LIB_IDs re-point to a dead nickname
429 if( !registerCacheRow( aCacheNickname ) )
430 return;
431
432 aResult.m_cacheNickname = aCacheNickname;
433 aResult.m_savedToCache = static_cast<int>( aCacheDefs.size() );
434}
435
436
437bool SYMBOL_IMPORT_RECONCILER::registerCacheRow( const wxString& aCacheNickname )
438{
439 std::optional<LIBRARY_TABLE*> tableOpt = m_adapter.ProjectTable();
440
441 if( !tableOpt || !*tableOpt )
442 {
443 m_reporter.Report( _( "Cannot register imported symbol cache: no project library "
444 "table." ), RPT_SEVERITY_ERROR );
445 return false;
446 }
447
448 LIBRARY_TABLE* table = *tableOpt;
449 wxString cacheFile = aCacheNickname + wxS( "." )
451 wxString uri = wxS( "${KIPRJMOD}/" ) + cacheFile;
452 LIBRARY_TABLE_ROW* row = table->HasRow( aCacheNickname )
453 ? table->Row( aCacheNickname ).value_or( nullptr )
454 : &table->InsertRow();
455
456 if( !row )
457 return false;
458
459 row->SetNickname( aCacheNickname );
460 row->SetURI( uri );
461 row->SetType( wxS( "KiCad" ) );
464
465 // an unsaved row is gone on restart, so the cache cannot be claimed
466 if( !table->Save() )
467 {
468 m_reporter.Report( _( "Error saving project symbol library table; imported symbols left "
469 "unresolved." ), RPT_SEVERITY_ERROR );
470 return false;
471 }
472
473 // load the cache so membership and later lookups resolve it
474 m_adapter.LoadOne( aCacheNickname );
475 return true;
476}
477
478
480ReconcileImportedSymbols( SCH_IO& aPlugin, SCHEMATIC& aSchematic, PROJECT& aProject,
481 const wxString& aSchematicPath,
482 const std::map<std::string, UTF8>* aProperties, REPORTER& aReporter )
483{
485 std::vector<std::unique_ptr<LIB_SYMBOL>> definitions;
486
487 try
488 {
489 for( LIB_SYMBOL* symbol : aPlugin.GetImportedCachedLibrarySymbols() )
490 definitions.emplace_back( symbol );
491 }
492 catch( const IO_ERROR& )
493 {
494 return result;
495 }
496
497 // Importers still writing their own project library during load (Eagle) must be left alone:
498 // with no definitions to interface-match, resolveSource would reject the library they just
499 // wrote and duplicate every symbol into the cache. Drop this once they all publish here.
500 if( definitions.empty() )
501 return result;
502
504
505 if( !adapter )
506 return result;
507
508 // manager pre-commits the cache nickname + source libs; standalone import derives from filename
509 wxString cacheNick;
510 std::vector<wxString> sourceLibs;
511 IMPORT_PROJ_PROPS::ReadSymbolProps( aProperties, cacheNick, sourceLibs );
512
513 if( cacheNick.IsEmpty() )
514 {
516 wxFileName( aSchematicPath ).GetName() );
517 }
518
519 SYMBOL_IMPORT_RECONCILER reconciler( *adapter, aProject.GetProjectPath(), aReporter );
520
521 // reconciliation failure must not abort the import
522 try
523 {
524 result = reconciler.Reconcile( &aSchematic, std::move( definitions ), cacheNick,
525 sourceLibs );
526 }
527 catch( const IO_ERROR& ioe )
528 {
529 aReporter.Report( wxString::Format( _( "Could not reconcile imported symbol libraries: "
530 "%s" ), ioe.What() ), RPT_SEVERITY_ERROR );
531 }
532
533 return result;
534}
const char * name
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()
void SetOptions(const wxString &aOptions)
void SetNickname(const wxString &aNickname)
void SetType(const wxString &aType)
std::map< std::string, UTF8 > GetOptionsMap() const
void SetURI(const wxString &aUri)
void SetScope(LIBRARY_TABLE_SCOPE aScope)
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int SetLibNickname(const UTF8 &aLibNickname)
Override the logical library name portion of the LIB_ID to aLibNickname.
Definition lib_id.cpp:113
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:108
const wxString GetUniStringLibNickname() const
Definition lib_id.h:84
Define a library symbol object.
Definition lib_symbol.h:114
std::vector< SCH_PIN * > GetPins() const override
int GetUnitCount() const override
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
Container for project specific data.
Definition project.h:63
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:183
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:72
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:101
Holds all the data relating to one schematic.
Definition schematic.h:90
SCH_SHEET & Root() const
Definition schematic.h:134
static const char * PropBuffering
The property used internally by the plugin to enable cache buffering which prevents the library file ...
Base class that schematic file and library loading and saving plugins should derive from.
Definition sch_io.h:59
virtual std::vector< LIB_SYMBOL * > GetImportedCachedLibrarySymbols()
Return the canonical symbol definitions produced by the last LoadSchematicFile().
Definition sch_io.cpp:74
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:162
Container class that holds multiple SCH_SCREEN objects in a hierarchy.
Definition sch_screen.h:746
Schematic symbol object.
Definition sch_symbol.h:69
const LIB_ID & GetLibId() const override
Definition sch_symbol.h:158
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:177
Frame-independent, non-interactive service that reconciles the symbol-library references of a freshly...
bool registerCacheRow(const wxString &aCacheNickname)
Insert or refresh the project symbol-library-table row for the generated cache.
SYMBOL_IMPORT_RECONCILER(SYMBOL_LIBRARY_ADAPTER &aAdapter, const wxString &aProjectPath, REPORTER &aReporter=NULL_REPORTER::GetInstance())
SYMBOL_IMPORT_RECONCILE_RESULT Reconcile(SCHEMATIC *aSchematic, std::vector< std::unique_ptr< LIB_SYMBOL > > aDefinitions, const wxString &aCacheNickname, const std::vector< wxString > &aSourceLibNicknames)
Reconcile aSchematic against the importer definitions and the provenance source libraries.
SYMBOL_LIBRARY_ADAPTER & m_adapter
void writeAndRegisterCache(const wxString &aCacheNickname, const std::map< wxString, LIB_SYMBOL * > &aCacheDefs, SYMBOL_IMPORT_RECONCILE_RESULT &aResult)
Write the residual definitions into an atomically-published .kicad_sym and register its row.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
LIB_SYMBOL * LoadSymbol(const wxString &aNickname, const wxString &aName)
Load a LIB_SYMBOL having aName from the library given by aNickname.
#define _(s)
static const std::string KiCadSymbolLibFileExtension
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
void ReadSymbolProps(const std::map< std::string, UTF8 > *aProps, wxString &aCacheNickname, std::vector< wxString > &aSourceSymLibs)
Read the symbol-import coordination properties out of a properties map.
wxString ManagedCacheOption()
Options string identifying a library-table row as a generated import cache.
constexpr char MANAGED_CACHE_KEY[]
Library-table row option key marking a row as a generated import cache.
wxString MakeSymbolCacheNickname(const wxString &aStem)
Derive the generated symbol-cache nickname from a project or file stem.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
LIB_SYMBOL * loadSymbol(const wxString &aLibraryPath, nlohmann::json aFileData, const wxString &aAliasName, const std::map< std::string, UTF8 > *aProperties)
Outcome of a post-import symbol-library reconciliation pass.
wxString m_cacheNickname
nickname of the generated cache, empty if none written
int m_savedToCache
distinct definitions written into the cache library
SYMBOL_IMPORT_RECONCILE_RESULT ReconcileImportedSymbols(SCH_IO &aPlugin, SCHEMATIC &aSchematic, PROJECT &aProject, const wxString &aSchematicPath, const std::map< std::string, UTF8 > *aProperties, REPORTER &aReporter)
Reconcile aSchematic against the definitions aPlugin retained while loading it.
KIBIS_PIN * pin
wxString result
Test unit parsing edge cases and error handling.
@ SCH_SYMBOL_T
Definition typeinfo.h:169
Definition of file extensions used in Kicad.