KiCad PCB EDA Suite
Loading...
Searching...
No Matches
lib_parser.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 <chrono>
23#include <iostream>
24#include <set>
25#include <string>
26#include <vector>
27
28#include <fmt/format.h>
29#include <wx/cmdline.h>
30#include <wx/msgout.h>
31
32#include <common.h>
33#include <core/profile.h>
34#include <ki_exception.h>
36#include <lib_symbol.h>
37#include <paths.h>
38#include <project.h>
39#include <richio.h>
45#include <wx_filename.h>
46
47
48using PARSE_DURATION = std::chrono::microseconds;
49
50
56static void loadKiCadEnvVars()
57{
60
61 if( !cfg )
62 return;
63
64 // Trigger loading of the settings from disk
65 mgr.Load( cfg );
66
67 for( const auto& [name, item] : cfg->m_Env.vars )
68 {
69 // Don't overwrite variables that are already defined in the system environment
70 if( item.GetDefinedExternally() )
71 continue;
72
73 wxString existing;
74
75 if( !wxGetEnv( name, &existing ) )
76 wxSetEnv( name, item.GetValue() );
77 }
78}
79
80
86static int parseLibraryFile( const wxString& aPath, bool aVerbose )
87{
88 // The s-expression library parser requires an absolute path.
89 wxFileName fn( aPath );
90 fn.MakeAbsolute();
91 wxString absPath = fn.GetFullPath();
92
93 if( aVerbose )
94 {
95 std::cout << fmt::format( "Parsing library: {}", absPath.ToStdString() ) << std::endl;
96 }
97
98 PROF_TIMER timer;
100 std::vector<LIB_SYMBOL*> symbols;
101
102 try
103 {
104 io.EnumerateSymbolLib( symbols, absPath );
105 }
106 catch( const IO_ERROR& e )
107 {
108 if( aVerbose )
109 {
110 auto duration = timer.SinceStart<PARSE_DURATION>();
111 std::cerr << fmt::format( "Failed to parse library '{}' after {} us: {}", absPath.ToStdString(),
112 duration.count(), e.What().ToStdString() )
113 << std::endl;
114 }
115 else
116 {
117 std::cerr << fmt::format( "Failed to parse library '{}': {}", absPath.ToStdString(),
118 e.What().ToStdString() )
119 << std::endl;
120 }
121 return -1;
122 }
123
124 if( aVerbose )
125 {
126 auto duration = timer.SinceStart<PARSE_DURATION>();
127 std::cout << fmt::format( " {} symbols", symbols.size() ) << std::endl;
128 std::cout << fmt::format( " Took: {} us", duration.count() ) << std::endl;
129 }
130
131 return static_cast<int>( symbols.size() );
132}
133
134
140static bool parseStdin()
141{
142 // Read all of stdin into a string
143 std::string content( ( std::istreambuf_iterator<char>( std::cin ) ), std::istreambuf_iterator<char>() );
144
145 if( content.empty() )
146 return true; // empty input is not a parse error (important for fuzzing)
147
148 LIB_SYMBOL_MAP symbolMap;
149 bool ok = true;
150
151 try
152 {
153 STRING_LINE_READER reader( content, wxS( "<stdin>" ) );
154 SCH_IO_KICAD_SEXPR_PARSER parser( &reader );
155
156 parser.ParseLib( symbolMap );
157 }
158 catch( const IO_ERROR& )
159 {
160 // Any symbols parsed before the error are released below
161 ok = false;
162 }
163
164 // LIB_SYMBOL_MAP owns the LIB_SYMBOL* objects
165 for( auto& entry : symbolMap )
166 delete entry.second;
167
168 return ok;
169}
170
171
181static int parseLibTable( const wxString& aTablePath, bool aVerbose, std::set<wxString>& aVisited )
182{
183 // Resolve symlinks so that the visited-set key is canonical
184 wxFileName tableFn( aTablePath );
185 tableFn.MakeAbsolute();
187 wxString canonicalPath = tableFn.GetFullPath();
188
189 // Prevent infinite recursion through nested tables
190 if( aVisited.count( canonicalPath ) )
191 return 0;
192
193 aVisited.insert( canonicalPath );
194
196
197 if( !table.IsOk() )
198 {
199 std::cerr << fmt::format( "Failed to load library table '{}': {}", canonicalPath.ToStdString(),
200 table.ErrorDescription().ToStdString() )
201 << std::endl;
202 return -1;
203 }
204
205 int okCount = 0;
206 bool hadRows = false;
207
208 for( const auto& row : table.Rows() )
209 {
210 if( row.Disabled() || row.Hidden() )
211 continue;
212
213 hadRows = true;
214
215 wxString uri = row.URI();
216
217 // Expand environment variables in the URI
218 uri = ExpandEnvVarSubstitutions( uri, nullptr );
219
220 // Resolve URIs relative to the table file's directory
221 {
222 wxFileName uriFn( uri );
223 uriFn.MakeAbsolute( tableFn.GetPath() );
224 uri = uriFn.GetFullPath();
225 }
226
227 if( aVerbose )
228 {
229 std::cout << fmt::format( "Parsing library '{}': {}", row.Nickname().ToStdString(), uri.ToStdString() )
230 << std::endl;
231 }
232
233 // A row with type "Table" points to a nested sym-lib-table
234 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
235 {
236 int nestedCount = parseLibTable( uri, aVerbose, aVisited );
237
238 if( nestedCount >= 0 )
239 okCount += nestedCount;
240 }
241 else if( row.Type() == wxS( "KiCad" ) )
242 {
243 int count = parseLibraryFile( uri, aVerbose );
244
245 if( count >= 0 )
246 okCount++;
247 }
248 else
249 {
250 // Skip unsupported library types (Legacy, Database, HTTP, etc.)
251 if( aVerbose )
252 {
253 std::cerr << fmt::format( "Skipping unsupported library type '{}' for '{}'", row.Type().ToStdString(),
254 row.Nickname().ToStdString() )
255 << std::endl;
256 }
257 }
258 }
259
260 if( !hadRows )
261 return 0;
262
263 return okCount;
264}
265
266
267static const wxCmdLineEntryDesc g_cmdLineDesc[] = {
268 {
269 wxCMD_LINE_SWITCH,
270 "h",
271 "help",
272 _( "displays help on the command line parameters" ).mb_str(),
273 wxCMD_LINE_VAL_NONE,
274 wxCMD_LINE_OPTION_HELP,
275 },
276 {
277 wxCMD_LINE_SWITCH,
278 "v",
279 "verbose",
280 _( "print parsing information" ).mb_str(),
281 },
282 {
283 wxCMD_LINE_OPTION,
284 nullptr,
285 "lib-table",
286 _( "path to a symbol library table file" ).mb_str(),
287 wxCMD_LINE_VAL_STRING,
288 },
289 {
290 wxCMD_LINE_OPTION,
291 "l",
292 "loop",
293 _( "number of times to loop when parsing from stdin (for AFL)" ).mb_str(),
294 wxCMD_LINE_VAL_NUMBER,
295 },
296 {
297 wxCMD_LINE_PARAM,
298 nullptr,
299 nullptr,
300 _( "library file" ).mb_str(),
301 wxCMD_LINE_VAL_STRING,
302 wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE,
303 },
304 {
305 wxCMD_LINE_NONE,
306 }
307};
308
309
314
315
316int lib_parser_main_func( int argc, char** argv )
317{
318#ifdef __AFL_COMPILER
319 __AFL_INIT();
320#endif
321
322 wxMessageOutput::Set( new wxMessageOutputStderr );
323 wxCmdLineParser cl_parser( argc, argv );
324 cl_parser.SetDesc( g_cmdLineDesc );
325 cl_parser.AddUsageText( "This program parses schematic symbol library files, either from the stdin "
326 "stream, from individual library files, or from libraries listed in a symbol "
327 "library table. This can be used for profiling, fuzz testing, etc.\n" );
328
329 cl_parser.AddUsageText( "If no library files or library table are specified, the program will read from stdin. "
330 "This is useful for fuzz testing, for example.\n" );
331
332 cl_parser.AddUsageText( "Fuzzing with AFL:\n" );
333
334 cl_parser.AddUsageText( " afl-fuzz -i <input_dir> -o <output_dir> -- qa_eeschema_tools lib_parser --loop 1000\n" );
335
336 cl_parser.AddUsageText( "Some fuzzing seeds for the -i option can be found in the KiCad source tree under"
337 " qa/data/fuzzing/kicad_sym\n" );
338
339 cl_parser.AddUsageText( "See the fuzzing documentation in dev-docs for more information." );
340
341 int cmd_parsed_ok = cl_parser.Parse();
342
343 if( cmd_parsed_ok != 0 )
344 {
345 // Help and invalid input both stop here
346 return ( cmd_parsed_ok == -1 ) ? KI_TEST::RET_CODES::OK : KI_TEST::RET_CODES::BAD_CMDLINE;
347 }
348
349 // Load KiCad-configured environment variables so that library table URI
350 // references like ${KICAD10_SYMBOL_DIR} can be resolved.
352
353 const bool verbose = cl_parser.Found( "verbose" );
354 bool ok = true;
355
356 // Collect library paths from positional arguments
357 std::vector<wxString> libPaths;
358
359 for( size_t i = 0; i < cl_parser.GetParamCount(); i++ )
360 libPaths.push_back( cl_parser.GetParam( i ) );
361
362 // Handle --lib-table
363 wxString tablePath;
364
365 if( cl_parser.Found( "lib-table", &tablePath ) )
366 {
367 // Infer the KIPRJMOD environment variable from the table's directory
368 wxFileName tableFn( tablePath );
369 tableFn.MakeAbsolute();
370 wxSetEnv( PROJECT_VAR_NAME, tableFn.GetPath() );
371
372 std::set<wxString> visited;
373 int count = parseLibTable( tablePath, verbose, visited );
374
375 if( count < 0 )
377
378 if( count == 0 && !verbose )
379 {
380 // Only print this when not in verbose mode, since in verbose mode
381 // each failed library already printed its own error.
382 std::cerr << fmt::format( "No libraries successfully parsed from table '{}'", tablePath.ToStdString() )
383 << std::endl;
384 }
385 }
386
387 long aflLoopCount = 1;
388 cl_parser.Found( "loop", &aflLoopCount );
389
390 if( libPaths.empty() )
391 {
392 // If --lib-table was specified, we're done (no stdin fallback).
393 // If nothing was specified, parse from stdin (for fuzzing, probably).
394 if( cl_parser.Found( "lib-table" ) )
396
397#ifdef __AFL_COMPILER
398 while( __AFL_LOOP( aflLoopCount ) )
399#endif
400 {
401 ok = parseStdin();
402 }
403 }
404 else
405 {
406 // Parse each library file given on the command line
407 for( const auto& path : libPaths )
408 {
409 int count = parseLibraryFile( path, verbose );
410
411 if( count < 0 )
412 ok = false;
413 }
414 }
415
416 if( !ok )
418
420}
421
422
424 "lib_parser",
425 "Parse schematic symbol library files",
427} );
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()
static const wxString TABLE_TYPE_NAME
A small class to help profiling.
Definition profile.h:46
DURATION SinceStart(bool aSinceLast=false)
Definition profile.h:134
Object to parser s-expression symbol library and schematic file formats.
void ParseLib(LIB_SYMBOL_MAP &aSymbolLibMap)
A SCH_IO derivation for loading schematic files using the new s-expression file format.
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
COMMON_SETTINGS * GetCommonSettings() const
Retrieve the common settings shared by all applications.
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:225
static bool Register(const KI_TEST::UTILITY_PROGRAM &aProgInfo)
Register a utility program factory function against an ID string.
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:776
static bool registered
static const wxCmdLineEntryDesc g_cmdLineDesc[]
#define _(s)
static int parseLibraryFile(const wxString &aPath, bool aVerbose)
Parse a single library file path and return the number of symbols loaded.
static bool parseStdin()
Parse symbol library content from stdin (for fuzzing).
std::chrono::microseconds PARSE_DURATION
static int parseLibTable(const wxString &aTablePath, bool aVerbose, std::set< wxString > &aVisited)
Load libraries from a symbol library table file.
static void loadKiCadEnvVars()
Load the KiCad common settings and set any configured environment variables (e.g.
int lib_parser_main_func(int argc, char **argv)
LIB_PARSER_RET_CODES
@ PARSE_FAILED
@ OK
Tool exited OK.
@ TOOL_SPECIFIC
Tools can define their own statuses from here onwards.
@ BAD_CMDLINE
The command line was not correct for the tool.
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition project.h:38
@ PARSE_FAILED
std::map< wxString, LIB_SYMBOL *, LibSymbolMapSort > LIB_SYMBOL_MAP
Symbol library map sorted by the symbol name.
std::string path