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
179static int parseLibTable( const wxString& aTablePath, bool aVerbose, std::set<wxString>& aVisited )
180{
181 // Resolve symlinks so that the visited-set key is canonical
182 wxFileName tableFn( aTablePath );
183 tableFn.MakeAbsolute();
185 wxString canonicalPath = tableFn.GetFullPath();
186
187 // Prevent infinite recursion through nested tables
188 if( aVisited.count( canonicalPath ) )
189 return 0;
190
191 aVisited.insert( canonicalPath );
192
194
195 if( !table.IsOk() )
196 {
197 std::cerr << fmt::format( "Failed to load library table '{}': {}", canonicalPath.ToStdString(),
198 table.ErrorDescription().ToStdString() )
199 << std::endl;
200 return -1;
201 }
202
203 int okCount = 0;
204 bool hadRows = false;
205
206 for( const auto& row : table.Rows() )
207 {
208 if( row.Disabled() || row.Hidden() )
209 continue;
210
211 hadRows = true;
212
213 wxString uri = row.URI();
214
215 // Expand environment variables in the URI
216 uri = ExpandEnvVarSubstitutions( uri, nullptr );
217
218 // Resolve URIs relative to the table file's directory
219 {
220 wxFileName uriFn( uri );
221 uriFn.MakeAbsolute( tableFn.GetPath() );
222 uri = uriFn.GetFullPath();
223 }
224
225 if( aVerbose )
226 {
227 std::cout << fmt::format( "Parsing library '{}': {}", row.Nickname().ToStdString(), uri.ToStdString() )
228 << std::endl;
229 }
230
231 // A row with type "Table" points to a nested sym-lib-table
232 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
233 {
234 int nestedCount = parseLibTable( uri, aVerbose, aVisited );
235
236 if( nestedCount >= 0 )
237 okCount += nestedCount;
238 }
239 else if( row.Type() == wxS( "KiCad" ) )
240 {
241 int count = parseLibraryFile( uri, aVerbose );
242
243 if( count >= 0 )
244 okCount++;
245 }
246 else
247 {
248 // Skip unsupported library types (Legacy, Database, HTTP, etc.)
249 if( aVerbose )
250 {
251 std::cerr << fmt::format( "Skipping unsupported library type '{}' for '{}'", row.Type().ToStdString(),
252 row.Nickname().ToStdString() )
253 << std::endl;
254 }
255 }
256 }
257
258 if( !hadRows )
259 return 0;
260
261 return okCount;
262}
263
264
265static const wxCmdLineEntryDesc g_cmdLineDesc[] = {
266 {
267 wxCMD_LINE_SWITCH,
268 "h",
269 "help",
270 _( "displays help on the command line parameters" ).mb_str(),
271 wxCMD_LINE_VAL_NONE,
272 wxCMD_LINE_OPTION_HELP,
273 },
274 {
275 wxCMD_LINE_SWITCH,
276 "v",
277 "verbose",
278 _( "print parsing information" ).mb_str(),
279 },
280 {
281 wxCMD_LINE_OPTION,
282 nullptr,
283 "lib-table",
284 _( "path to a symbol library table file" ).mb_str(),
285 wxCMD_LINE_VAL_STRING,
286 },
287 {
288 wxCMD_LINE_OPTION,
289 "l",
290 "loop",
291 _( "number of times to loop when parsing from stdin (for AFL)" ).mb_str(),
292 wxCMD_LINE_VAL_NUMBER,
293 },
294 {
295 wxCMD_LINE_PARAM,
296 nullptr,
297 nullptr,
298 _( "library file" ).mb_str(),
299 wxCMD_LINE_VAL_STRING,
300 wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE,
301 },
302 {
303 wxCMD_LINE_NONE,
304 }
305};
306
307
312
313
314int lib_parser_main_func( int argc, char** argv )
315{
316#ifdef __AFL_COMPILER
317 __AFL_INIT();
318#endif
319
320 wxMessageOutput::Set( new wxMessageOutputStderr );
321 wxCmdLineParser cl_parser( argc, argv );
322 cl_parser.SetDesc( g_cmdLineDesc );
323 cl_parser.AddUsageText( "This program parses schematic symbol library files, either from the stdin "
324 "stream, from individual library files, or from libraries listed in a symbol "
325 "library table. This can be used for profiling, fuzz testing, etc.\n" );
326
327 cl_parser.AddUsageText( "If no library files or library table are specified, the program will read from stdin. "
328 "This is useful for fuzz testing, for example.\n" );
329
330 cl_parser.AddUsageText( "Fuzzing with AFL:\n" );
331
332 cl_parser.AddUsageText( " afl-fuzz -i <input_dir> -o <output_dir> -- qa_eeschema_tools lib_parser --loop 1000\n" );
333
334 cl_parser.AddUsageText( "Some fuzzing seeds for the -i option can be found in the KiCad source tree under"
335 " qa/data/fuzzing/kicad_sym\n" );
336
337 cl_parser.AddUsageText( "See the fuzzing documentation in dev-docs for more information." );
338
339 int cmd_parsed_ok = cl_parser.Parse();
340
341 if( cmd_parsed_ok != 0 )
342 {
343 // Help and invalid input both stop here
344 return ( cmd_parsed_ok == -1 ) ? KI_TEST::RET_CODES::OK : KI_TEST::RET_CODES::BAD_CMDLINE;
345 }
346
347 // Load KiCad-configured environment variables so that library table URI
348 // references like ${KICAD10_SYMBOL_DIR} can be resolved.
350
351 const bool verbose = cl_parser.Found( "verbose" );
352 bool ok = true;
353
354 // Collect library paths from positional arguments
355 std::vector<wxString> libPaths;
356
357 for( size_t i = 0; i < cl_parser.GetParamCount(); i++ )
358 libPaths.push_back( cl_parser.GetParam( i ) );
359
360 // Handle --lib-table
361 wxString tablePath;
362
363 if( cl_parser.Found( "lib-table", &tablePath ) )
364 {
365 // Infer the KIPRJMOD environment variable from the table's directory
366 wxFileName tableFn( tablePath );
367 tableFn.MakeAbsolute();
368 wxSetEnv( PROJECT_VAR_NAME, tableFn.GetPath() );
369
370 std::set<wxString> visited;
371 int count = parseLibTable( tablePath, verbose, visited );
372
373 if( count < 0 )
375
376 if( count == 0 && !verbose )
377 {
378 // Only print this when not in verbose mode, since in verbose mode
379 // each failed library already printed its own error.
380 std::cerr << fmt::format( "No libraries successfully parsed from table '{}'", tablePath.ToStdString() )
381 << std::endl;
382 }
383 }
384
385 long aflLoopCount = 1;
386 cl_parser.Found( "loop", &aflLoopCount );
387
388 if( libPaths.empty() )
389 {
390 // If --lib-table was specified, we're done (no stdin fallback).
391 // If nothing was specified, parse from stdin (for fuzzing, probably).
392 if( cl_parser.Found( "lib-table" ) )
394
395#ifdef __AFL_COMPILER
396 while( __AFL_LOOP( aflLoopCount ) )
397#endif
398 {
399 ok = parseStdin();
400 }
401 }
402 else
403 {
404 // Parse each library file given on the command line
405 for( const auto& path : libPaths )
406 {
407 int count = parseLibraryFile( path, verbose );
408
409 if( count < 0 )
410 ok = false;
411 }
412 }
413
414 if( !ok )
416
418}
419
420
422 "lib_parser",
423 "Parse schematic symbol library files",
425} );
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:133
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
std::string path