KiCad PCB EDA Suite
filename_resolver.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 (C) 2015-2020 Cirilo Bernardo <cirilo.bernardo@gmail.com>
5  * Copyright (C) 2015-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU 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, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include <fstream>
26 #include <mutex>
27 #include <sstream>
28 
29 #include <wx/filename.h>
30 #include <wx/log.h>
31 #include <wx/msgdlg.h>
32 #include <pgm_base.h>
33 #include <trace_helpers.h>
34 
35 #include "common.h"
36 #include "filename_resolver.h"
37 
38 // configuration file version
39 #define CFGFILE_VERSION 1
40 #define RESOLVER_CONFIG wxT( "3Dresolver.cfg" )
41 
42 // flag bits used to track different one-off messages to users
43 #define ERRFLG_ALIAS (1)
44 #define ERRFLG_RELPATH (2)
45 #define ERRFLG_ENVPATH (4)
46 
47 #define MASK_3D_RESOLVER "3D_RESOLVER"
48 
49 static std::mutex mutex_resolver;
50 
51 static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult );
52 
53 
55  m_pgm( nullptr ),
56  m_project( nullptr )
57 {
58  m_errflags = 0;
59 }
60 
61 
62 bool FILENAME_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
63 {
64  if( aConfigDir.empty() )
65  return false;
66 
67  wxFileName cfgdir( ExpandEnvVarSubstitutions( aConfigDir, m_project ), "" );
68 
69  cfgdir.Normalize();
70 
71  if( !cfgdir.DirExists() )
72  return false;
73 
74  m_configDir = cfgdir.GetPath();
76 
77  return true;
78 }
79 
80 
81 bool FILENAME_RESOLVER::SetProject( PROJECT* aProject, bool* flgChanged )
82 {
83  m_project = aProject;
84 
85  if( !aProject )
86  return false;
87 
88  wxFileName projdir( ExpandEnvVarSubstitutions( aProject->GetProjectPath(), aProject ), "" );
89 
90  projdir.Normalize();
91 
92  if( !projdir.DirExists() )
93  return false;
94 
95  m_curProjDir = projdir.GetPath();
96 
97  if( flgChanged )
98  *flgChanged = false;
99 
100  if( m_paths.empty() )
101  {
102  SEARCH_PATH al;
103  al.m_Alias = "${KIPRJMOD}";
104  al.m_Pathvar = "${KIPRJMOD}";
105  al.m_Pathexp = m_curProjDir;
106  m_paths.push_back( al );
107 
108  if( flgChanged )
109  *flgChanged = true;
110  }
111  else
112  {
113  if( m_paths.front().m_Pathexp != m_curProjDir )
114  {
115  m_paths.front().m_Pathexp = m_curProjDir;
116 
117  if( flgChanged )
118  *flgChanged = true;
119  }
120  else
121  {
122  return true;
123  }
124  }
125 
126 #ifdef DEBUG
127  {
128  std::ostringstream ostr;
129  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
130  ostr << " * [INFO] changed project dir to ";
131  ostr << m_paths.front().m_Pathexp.ToUTF8();
132  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
133  }
134 #endif
135 
136  return true;
137 }
138 
139 
141 {
142  return m_curProjDir;
143 }
144 
145 
147 {
148  m_pgm = aBase;
149 
150  if( !m_pgm || m_paths.empty() )
151  return;
152 
153  // recreate the path list
154  m_paths.clear();
155  createPathList();
156 }
157 
158 
160 {
161  if( !m_paths.empty() )
162  return true;
163 
164  wxString kmod;
165 
166  // add an entry for the default search path; at this point
167  // we cannot set a sensible default so we use an empty string.
168  // the user may change this later with a call to SetProjectDir()
169 
170  SEARCH_PATH lpath;
171  lpath.m_Alias = "${KIPRJMOD}";
172  lpath.m_Pathvar = "${KIPRJMOD}";
173  lpath.m_Pathexp = m_curProjDir;
174  m_paths.push_back( lpath );
175  wxFileName fndummy;
176  wxUniChar psep = fndummy.GetPathSeparator();
177  std::list< wxString > epaths;
178 
179  if( GetKicadPaths( epaths ) )
180  {
181  for( const wxString& curr_path : epaths )
182  {
183  wxString pathVal = ExpandEnvVarSubstitutions( curr_path, m_project );
184 
185  if( pathVal.empty() )
186  {
187  lpath.m_Pathexp.clear();
188  }
189  else
190  {
191  fndummy.Assign( pathVal, "" );
192  fndummy.Normalize();
193  lpath.m_Pathexp = fndummy.GetFullPath();
194  }
195 
196  lpath.m_Alias = curr_path;
197  lpath.m_Pathvar = curr_path;
198 
199  if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
200  lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
201 
202  m_paths.push_back( lpath );
203  }
204  }
205 
206  if( !m_configDir.empty() )
207  readPathList();
208 
209  if( m_paths.empty() )
210  return false;
211 
212 #ifdef DEBUG
213  wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" );
214  std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
215 
216  while( sPL != m_paths.end() )
217  {
218  wxLogTrace( MASK_3D_RESOLVER, " + %s : '%s'\n", (*sPL).m_Alias.GetData(),
219  (*sPL).m_Pathexp.GetData() );
220  ++sPL;
221  }
222 #endif
223 
224  return true;
225 }
226 
227 
228 bool FILENAME_RESOLVER::UpdatePathList( const std::vector< SEARCH_PATH >& aPathList )
229 {
230  wxUniChar envMarker( '$' );
231 
232  while( !m_paths.empty() && envMarker != *m_paths.back().m_Alias.rbegin() )
233  m_paths.pop_back();
234 
235  for( const SEARCH_PATH& path : aPathList )
236  addPath( path );
237 
238  return WritePathList( m_configDir, RESOLVER_CONFIG, false );
239 }
240 
241 
242 wxString FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
243 {
244  std::lock_guard<std::mutex> lock( mutex_resolver );
245 
246  if( aFileName.empty() )
247  return wxEmptyString;
248 
249  if( m_paths.empty() )
250  createPathList();
251 
252  // first attempt to use the name as specified:
253  wxString tname = aFileName;
254 
255  #ifdef _WIN32
256  // translate from KiCad's internal UNIX-like path to MSWin paths
257  tname.Replace( wxT( "/" ), wxT( "\\" ) );
258  #endif
259 
260  // Note: variable expansion must preferably be performed via a threadsafe wrapper for the
261  // getenv() system call. If we allow the wxFileName::Normalize() routine to perform expansion
262  // then we will have a race condition since wxWidgets does not assure a threadsafe wrapper
263  // for getenv().
264  tname = ExpandEnvVarSubstitutions( tname, m_project );
265 
266  wxFileName tmpFN( tname );
267 
268  // this case covers full paths, leading expanded vars, and paths relative to the current
269  // working directory (which is not necessarily the current project directory)
270  if( tmpFN.FileExists() )
271  {
272  tmpFN.Normalize();
273  tname = tmpFN.GetFullPath();
274 
275  // special case: if a path begins with ${ENV_VAR} but is not in the resolver's path list
276  // then add it.
277  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
278  checkEnvVarPath( aFileName );
279 
280  return tname;
281  }
282 
283  // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the file either does
284  // not exist or the ENV_VAR is not defined
285  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
286  {
287  if( !( m_errflags & ERRFLG_ENVPATH ) )
288  {
290  wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
291  errmsg.append( "\n" );
292  errmsg.append( tname );
293  errmsg.append( "\n" );
294  wxLogTrace( tracePathsAndFiles, errmsg );
295  }
296 
297  return wxEmptyString;
298  }
299 
300  // at this point aFileName is:
301  // a. an aliased shortened name or
302  // b. cannot be determined
303 
304  // check the path relative to the current project directory;
305  // NB: this is not necessarily the same as the current working directory, which has already
306  // been checked. This case accounts for partial paths which do not contain ${KIPRJMOD}.
307  // This check is performed before checking the path relative to ${KICAD6_3DMODEL_DIR} so that
308  // users can potentially override a model within ${KICAD6_3DMODEL_DIR}.
309  if( !m_paths.begin()->m_Pathexp.empty() && !tname.StartsWith( ":" ) )
310  {
311  tmpFN.Assign( m_paths.begin()->m_Pathexp, "" );
312  wxString fullPath = tmpFN.GetPathWithSep() + tname;
313 
314  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
315 
316  if( wxFileName::FileExists( fullPath ) )
317  {
318  tmpFN.Assign( fullPath );
319  tmpFN.Normalize();
320  tname = tmpFN.GetFullPath();
321  return tname;
322  }
323 
324  }
325 
326  // check the partial path relative to ${KICAD6_3DMODEL_DIR} (legacy behavior)
327  if( !tname.StartsWith( ":" ) )
328  {
329  wxFileName fpath;
330  wxString fullPath( "${KICAD6_3DMODEL_DIR}" );
331  fullPath.Append( fpath.GetPathSeparator() );
332  fullPath.Append( tname );
333  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
334  fpath.Assign( fullPath );
335 
336  if( fpath.Normalize() && fpath.FileExists() )
337  {
338  tname = fpath.GetFullPath();
339  return tname;
340  }
341 
342  }
343 
344  // at this point the filename must contain an alias or else it is invalid
345  wxString alias; // the alias portion of the short filename
346  wxString relpath; // the path relative to the alias
347 
348  if( !SplitAlias( tname, alias, relpath ) )
349  {
350  if( !( m_errflags & ERRFLG_RELPATH ) )
351  {
352  // this can happen if the file was intended to be relative to ${KICAD6_3DMODEL_DIR}
353  // but ${KICAD6_3DMODEL_DIR} is not set or is incorrect.
355  wxString errmsg = "[3D File Resolver] No such path";
356  errmsg.append( "\n" );
357  errmsg.append( tname );
358  errmsg.append( "\n" );
359  wxLogTrace( tracePathsAndFiles, errmsg );
360  }
361 
362  return wxEmptyString;
363  }
364 
365  for( const SEARCH_PATH& path : m_paths )
366  {
367  // ${ENV_VAR} paths have already been checked; skip them
368  if( path.m_Alias.StartsWith( "${" ) || path.m_Alias.StartsWith( "$(" ) )
369  continue;
370 
371  if( path.m_Alias == alias && !path.m_Pathexp.empty() )
372  {
373  wxFileName fpath( wxFileName::DirName( path.m_Pathexp ) );
374  wxString fullPath = fpath.GetPathWithSep() + relpath;
375 
376  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
377 
378  if( wxFileName::FileExists( fullPath ) )
379  {
380  tname = fullPath;
381 
382  wxFileName tmp( fullPath );
383 
384  if( tmp.Normalize() )
385  tname = tmp.GetFullPath();
386 
387  return tname;
388  }
389  }
390  }
391 
392  if( !( m_errflags & ERRFLG_ALIAS ) )
393  {
395  wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
396  errmsg.append( "\n" );
397  errmsg.append( tname.substr( 1 ) );
398  errmsg.append( "\n" );
399  wxLogTrace( tracePathsAndFiles, errmsg );
400  }
401 
402  return wxEmptyString;
403 }
404 
405 
407 {
408  if( aPath.m_Alias.empty() || aPath.m_Pathvar.empty() )
409  return false;
410 
411  std::lock_guard<std::mutex> lock( mutex_resolver );
412 
413  SEARCH_PATH tpath = aPath;
414 
415  #ifdef _WIN32
416  while( tpath.m_Pathvar.EndsWith( wxT( "\\" ) ) )
417  tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
418  #else
419  while( tpath.m_Pathvar.EndsWith( wxT( "/" ) ) && tpath.m_Pathvar.length() > 1 )
420  tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
421  #endif
422 
423  wxFileName path( ExpandEnvVarSubstitutions( tpath.m_Pathvar, m_project ), "" );
424 
425  path.Normalize();
426 
427  if( !path.DirExists() )
428  {
429  if( aPath.m_Pathvar == "${KICAD6_3DMODEL_DIR}"
430  || aPath.m_Pathvar == "${KIPRJMOD}" || aPath.m_Pathvar == "$(KIPRJMOD)"
431  || aPath.m_Pathvar == "${KISYS3DMOD}" || aPath.m_Pathvar == "$(KISYS3DMOD)" )
432  {
433  // suppress the message if the missing pathvar is a system variable
434  }
435  else
436  {
437  wxString msg = _( "The given path does not exist" );
438  msg.append( wxT( "\n" ) );
439  msg.append( tpath.m_Pathvar );
440  wxMessageBox( msg, _( "3D model search path" ) );
441  }
442 
443  tpath.m_Pathexp.clear();
444  }
445  else
446  {
447  tpath.m_Pathexp = path.GetFullPath();
448 
449 #ifdef _WIN32
450  while( tpath.m_Pathexp.EndsWith( wxT( "\\" ) ) )
451  tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
452 #else
453  while( tpath.m_Pathexp.EndsWith( wxT( "/" ) ) && tpath.m_Pathexp.length() > 1 )
454  tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
455 #endif
456  }
457 
458  std::list< SEARCH_PATH >::iterator sPL = m_paths.begin();
459  std::list< SEARCH_PATH >::iterator ePL = m_paths.end();
460 
461  while( sPL != ePL )
462  {
463  if( tpath.m_Alias == sPL->m_Alias )
464  {
465  wxString msg = _( "Alias: " );
466  msg.append( tpath.m_Alias );
467  msg.append( wxT( "\n" ) );
468  msg.append( _( "This path:" ) + wxS( " " ) );
469  msg.append( tpath.m_Pathvar );
470  msg.append( wxT( "\n" ) );
471  msg.append( _( "Existing path:" ) + wxS( " " ) );
472  msg.append( sPL->m_Pathvar );
473  wxMessageBox( msg, _( "Bad alias (duplicate name)" ) );
474 
475  return false;
476  }
477 
478  ++sPL;
479  }
480 
481  m_paths.push_back( tpath );
482  return true;
483 }
484 
485 
487 {
488  if( m_configDir.empty() )
489  {
490  std::ostringstream ostr;
491  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
492  wxString errmsg = "3D configuration directory is unknown";
493  ostr << " * " << errmsg.ToUTF8();
494  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
495  return false;
496  }
497 
498  wxFileName cfgpath( m_configDir, RESOLVER_CONFIG );
499  cfgpath.Normalize();
500  wxString cfgname = cfgpath.GetFullPath();
501 
502  size_t nitems = m_paths.size();
503 
504  std::ifstream cfgFile;
505  std::string cfgLine;
506 
507  if( !wxFileName::Exists( cfgname ) )
508  {
509  std::ostringstream ostr;
510  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
511  wxString errmsg = "no 3D configuration file";
512  ostr << " * " << errmsg.ToUTF8() << " '";
513  ostr << cfgname.ToUTF8() << "'";
514  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
515  return false;
516  }
517 
518  cfgFile.open( cfgname.ToUTF8() );
519 
520  if( !cfgFile.is_open() )
521  {
522  std::ostringstream ostr;
523  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
524  wxString errmsg = "Could not open configuration file";
525  ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
526  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
527  return false;
528  }
529 
530  int lineno = 0;
531  SEARCH_PATH al;
532  size_t idx;
533  int vnum = 0; // version number
534 
535  while( cfgFile.good() )
536  {
537  cfgLine.clear();
538  std::getline( cfgFile, cfgLine );
539  ++lineno;
540 
541  if( cfgLine.empty() )
542  {
543  if( cfgFile.eof() )
544  break;
545 
546  continue;
547  }
548 
549  if( 1 == lineno && cfgLine.compare( 0, 2, "#V" ) == 0 )
550  {
551  // extract the version number and parse accordingly
552  if( cfgLine.size() > 2 )
553  {
554  std::istringstream istr;
555  istr.str( cfgLine.substr( 2 ) );
556  istr >> vnum;
557  }
558 
559  continue;
560  }
561 
562  idx = 0;
563 
564  if( !getHollerith( cfgLine, idx, al.m_Alias ) )
565  continue;
566 
567  // Don't add KICAD6_3DMODEL_DIR, one of its legacy equivalents, or KIPRJMOD from a
568  // config file. They're system variables are are defined at runtime.
569  if( al.m_Alias == "${KICAD6_3DMODEL_DIR}"
570  || al.m_Alias == "${KIPRJMOD}" || al.m_Alias == "$(KIPRJMOD)"
571  || al.m_Alias == "${KISYS3DMOD}" || al.m_Alias == "$(KISYS3DMOD)" )
572  {
573  continue;
574  }
575 
576  if( !getHollerith( cfgLine, idx, al.m_Pathvar ) )
577  continue;
578 
579  if( !getHollerith( cfgLine, idx, al.m_Description ) )
580  continue;
581 
582  addPath( al );
583  }
584 
585  cfgFile.close();
586 
587  if( vnum < CFGFILE_VERSION )
589 
590  return( m_paths.size() != nitems );
591 }
592 
593 
594 bool FILENAME_RESOLVER::WritePathList( const wxString& aDir, const wxString& aFilename,
595  bool aResolvePaths )
596 {
597  if( aDir.empty() )
598  {
599  std::ostringstream ostr;
600  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
601  wxString errmsg = _( "3D configuration directory is unknown" );
602  ostr << " * " << errmsg.ToUTF8();
603  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
604  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
605 
606  return false;
607  }
608 
609  std::list<SEARCH_PATH>::const_iterator sPL = m_paths.begin();
610 
611  if( !aResolvePaths )
612  {
613  // skip all ${ENV_VAR} alias names
614 
615  while( sPL != m_paths.end()
616  && ( sPL->m_Alias.StartsWith( "${" ) || sPL->m_Alias.StartsWith( "$(" ) ) )
617  {
618  ++sPL;
619  }
620  }
621 
622  wxFileName cfgpath( aDir, aFilename );
623  wxString cfgname = cfgpath.GetFullPath();
624  std::ofstream cfgFile;
625 
626  cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc );
627 
628  if( !cfgFile.is_open() )
629  {
630  std::ostringstream ostr;
631  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
632  wxString errmsg = _( "Could not open configuration file" );
633  ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
634  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
635  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
636 
637  return false;
638  }
639 
640  cfgFile << "#V" << CFGFILE_VERSION << "\n";
641  std::string tstr;
642 
643  while( sPL != m_paths.end() )
644  {
645  tstr = sPL->m_Alias.ToUTF8();
646  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
647 
648  if( aResolvePaths )
649  tstr = ExpandEnvVarSubstitutions( sPL->m_Pathvar, m_project ).ToUTF8();
650  else
651  tstr = sPL->m_Pathvar.ToUTF8();
652 
653  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
654 
655  tstr = sPL->m_Description.ToUTF8();
656  cfgFile << "\"" << tstr.size() << ":" << tstr << "\"\n";
657 
658  ++sPL;
659  }
660 
661  bool bad = cfgFile.bad();
662  cfgFile.close();
663 
664  if( bad )
665  {
666  wxMessageBox( _( "Problems writing configuration file" ),
667  _( "Write 3D search path list" ) );
668 
669  return false;
670  }
671 
672  return true;
673 }
674 
675 
676 void FILENAME_RESOLVER::checkEnvVarPath( const wxString& aPath )
677 {
678  bool useParen = false;
679 
680  if( aPath.StartsWith( "$(" ) )
681  useParen = true;
682  else if( !aPath.StartsWith( "${" ) )
683  return;
684 
685  size_t pEnd;
686 
687  if( useParen )
688  pEnd = aPath.find( ")" );
689  else
690  pEnd = aPath.find( "}" );
691 
692  if( pEnd == wxString::npos )
693  return;
694 
695  wxString envar = aPath.substr( 0, pEnd + 1 );
696 
697  // check if the alias exists; if not then add it to the end of the
698  // env var section of the path list
699  auto sPL = m_paths.begin();
700  auto ePL = m_paths.end();
701 
702  while( sPL != ePL )
703  {
704  if( sPL->m_Alias == envar )
705  return;
706 
707  if( !sPL->m_Alias.StartsWith( "${" ) )
708  break;
709 
710  ++sPL;
711  }
712 
713  SEARCH_PATH lpath;
714  lpath.m_Alias = envar;
715  lpath.m_Pathvar = lpath.m_Alias;
716  wxFileName tmpFN( ExpandEnvVarSubstitutions( lpath.m_Alias, m_project ), "" );
717 
718  wxUniChar psep = tmpFN.GetPathSeparator();
719  tmpFN.Normalize();
720 
721  if( !tmpFN.DirExists() )
722  return;
723 
724  lpath.m_Pathexp = tmpFN.GetFullPath();
725 
726  if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
727  lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
728 
729  if( lpath.m_Pathexp.empty() )
730  return;
731 
732  m_paths.insert( sPL, lpath );
733 }
734 
735 
736 wxString FILENAME_RESOLVER::ShortenPath( const wxString& aFullPathName )
737 {
738  wxString fname = aFullPathName;
739 
740  if( m_paths.empty() )
741  createPathList();
742 
743  std::lock_guard<std::mutex> lock( mutex_resolver );
744 
745  std::list< SEARCH_PATH >::const_iterator sL = m_paths.begin();
746  size_t idx;
747 
748  while( sL != m_paths.end() )
749  {
750  // undefined paths do not participate in the
751  // file name shortening procedure
752  if( sL->m_Pathexp.empty() )
753  {
754  ++sL;
755  continue;
756  }
757 
758  wxFileName fpath;
759 
760  // in the case of aliases, ensure that we use the most recent definition
761  if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
762  {
763  wxString tpath = ExpandEnvVarSubstitutions( sL->m_Alias, m_project );
764 
765  if( tpath.empty() )
766  {
767  ++sL;
768  continue;
769  }
770 
771  fpath.Assign( tpath, wxT( "" ) );
772  }
773  else
774  {
775  fpath.Assign( sL->m_Pathexp, wxT( "" ) );
776  }
777 
778  wxString fps = fpath.GetPathWithSep();
779  wxString tname;
780 
781  idx = fname.find( fps );
782 
783  if( idx == 0 )
784  {
785  fname = fname.substr( fps.size() );
786 
787  #ifdef _WIN32
788  // ensure only the '/' separator is used in the internal name
789  fname.Replace( wxT( "\\" ), wxT( "/" ) );
790  #endif
791 
792  if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
793  {
794  // old style ENV_VAR
795  tname = sL->m_Alias;
796  tname.Append( "/" );
797  tname.append( fname );
798  }
799  else
800  {
801  // new style alias
802  tname = ":";
803  tname.append( sL->m_Alias );
804  tname.append( ":" );
805  tname.append( fname );
806  }
807 
808  return tname;
809  }
810 
811  ++sL;
812  }
813 
814 #ifdef _WIN32
815  // it is strange to convert an MSWin full path to use the
816  // UNIX separator but this is done for consistency and can
817  // be helpful even when transferring project files from
818  // MSWin to *NIX.
819  fname.Replace( wxT( "\\" ), wxT( "/" ) );
820 #endif
821 
822  return fname;
823 }
824 
825 
826 
827 const std::list< SEARCH_PATH >* FILENAME_RESOLVER::GetPaths() const
828 {
829  return &m_paths;
830 }
831 
832 
833 bool FILENAME_RESOLVER::SplitAlias( const wxString& aFileName,
834  wxString& anAlias, wxString& aRelPath ) const
835 {
836  anAlias.clear();
837  aRelPath.clear();
838 
839  size_t searchStart = 0;
840 
841  if( aFileName.StartsWith( wxT( ":" ) ) )
842  searchStart = 1;
843 
844  size_t tagpos = aFileName.find( wxT( ":" ), searchStart );
845 
846  if( tagpos == wxString::npos || tagpos == searchStart )
847  return false;
848 
849  if( tagpos + 1 >= aFileName.length() )
850  return false;
851 
852  anAlias = aFileName.substr( searchStart, tagpos - searchStart );
853  aRelPath = aFileName.substr( tagpos + 1 );
854 
855  return true;
856 }
857 
858 
859 static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult )
860 {
861  aResult.clear();
862 
863  if( aIndex >= aString.size() )
864  {
865  std::ostringstream ostr;
866  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
867  wxString errmsg = "bad Hollerith string on line";
868  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
869  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
870 
871  return false;
872  }
873 
874  size_t i2 = aString.find( '"', aIndex );
875 
876  if( std::string::npos == i2 )
877  {
878  std::ostringstream ostr;
879  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
880  wxString errmsg = "missing opening quote mark in config file";
881  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
882  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
883 
884  return false;
885  }
886 
887  ++i2;
888 
889  if( i2 >= aString.size() )
890  {
891  std::ostringstream ostr;
892  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
893  wxString errmsg = "invalid entry (unexpected end of line)";
894  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
895  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
896 
897  return false;
898  }
899 
900  std::string tnum;
901 
902  while( aString[i2] >= '0' && aString[i2] <= '9' )
903  tnum.append( 1, aString[i2++] );
904 
905  if( tnum.empty() || aString[i2++] != ':' )
906  {
907  std::ostringstream ostr;
908  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
909  wxString errmsg = "bad Hollerith string on line";
910  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
911  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
912 
913  return false;
914  }
915 
916  std::istringstream istr;
917  istr.str( tnum );
918  size_t nchars;
919  istr >> nchars;
920 
921  if( (i2 + nchars) >= aString.size() )
922  {
923  std::ostringstream ostr;
924  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
925  wxString errmsg = "invalid entry (unexpected end of line)";
926  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
927  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
928 
929  return false;
930  }
931 
932  if( nchars > 0 )
933  {
934  aResult = wxString::FromUTF8( aString.substr( i2, nchars ).c_str() );
935  i2 += nchars;
936  }
937 
938  if( i2 >= aString.size() || aString[i2] != '"' )
939  {
940  std::ostringstream ostr;
941  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
942  wxString errmsg = "missing closing quote mark in config file";
943  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
944  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
945 
946  return false;
947  }
948 
949  aIndex = i2 + 1;
950  return true;
951 }
952 
953 
954 bool FILENAME_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias ) const
955 {
956  // Rules:
957  // 1. The generic form of an aliased 3D relative path is:
958  // ALIAS:relative/path
959  // 2. ALIAS is a UTF string excluding wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" )
960  // 3. The relative path must be a valid relative path for the platform
961  hasAlias = false;
962 
963  if( aFileName.empty() )
964  return false;
965 
966  wxString filename = aFileName;
967  wxString lpath;
968  size_t aliasStart = aFileName.StartsWith( ':' ) ? 1 : 0;
969  size_t aliasEnd = aFileName.find( ':', aliasStart );
970 
971  // ensure that the file separators suit the current platform
972 #ifdef __WINDOWS__
973  filename.Replace( wxT( "/" ), wxT( "\\" ) );
974 
975  // if we see the :\ pattern then it must be a drive designator
976  if( aliasEnd != wxString::npos )
977  {
978  size_t pos1 = filename.find( wxT( ":\\" ) );
979 
980  if( pos1 != wxString::npos && ( pos1 != aliasEnd || pos1 != 1 ) )
981  return false;
982 
983  // if we have a drive designator then we have no alias
984  if( pos1 != wxString::npos )
985  aliasEnd = wxString::npos;
986  }
987 #else
988  filename.Replace( wxT( "\\" ), wxT( "/" ) );
989 #endif
990 
991  // names may not end with ':'
992  if( aliasEnd == aFileName.length() -1 )
993  return false;
994 
995  if( aliasEnd != wxString::npos )
996  {
997  // ensure the alias component is not empty
998  if( aliasEnd == aliasStart )
999  return false;
1000 
1001  lpath = filename.substr( aliasStart, aliasEnd );
1002 
1003  // check the alias for restricted characters
1004  if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) )
1005  return false;
1006 
1007  hasAlias = true;
1008  lpath = aFileName.substr( aliasEnd + 1 );
1009  }
1010  else
1011  {
1012  lpath = aFileName;
1013 
1014  // in the case of ${ENV_VAR}|$(ENV_VAR)/path, strip the
1015  // environment string before testing
1016  aliasEnd = wxString::npos;
1017 
1018  if( aFileName.StartsWith( "${" ) )
1019  aliasEnd = aFileName.find( '}' );
1020  else if( aFileName.StartsWith( "$(" ) )
1021  aliasEnd = aFileName.find( ')' );
1022 
1023  if( aliasEnd != wxString::npos )
1024  lpath = aFileName.substr( aliasEnd + 1 );
1025 
1026  }
1027 
1028  // Test for forbidden chars in filenames. Should be wxFileName::GetForbiddenChars()
1029  // On MSW, the list returned by wxFileName::GetForbiddenChars() contains separators
1030  // '\'and '/' used here because lpath can be a full path.
1031  // So remove separators
1032  wxString lpath_no_sep = lpath;
1033 #ifdef __WINDOWS__
1034  lpath_no_sep.Replace( "/", " " );
1035  lpath_no_sep.Replace( "\\", " " );
1036 
1037  // A disk identifier is allowed, and therefore remove its separator
1038  if( lpath_no_sep.Length() > 1 && lpath_no_sep[1] == ':' )
1039  lpath_no_sep[1] = ' ';
1040 #endif
1041 
1042  if( wxString::npos != lpath_no_sep.find_first_of( wxFileName::GetForbiddenChars() ) )
1043  return false;
1044 
1045  return true;
1046 }
1047 
1048 
1049 bool FILENAME_RESOLVER::GetKicadPaths( std::list< wxString >& paths ) const
1050 {
1051  paths.clear();
1052 
1053  if( !m_pgm )
1054  return false;
1055 
1056  bool hasKisys3D = false;
1057 
1058 
1059  // iterate over the list of internally defined ENV VARs
1060  // and add them to the paths list
1063 
1064  while( mS != mE )
1065  {
1066  // filter out URLs, template directories, and known system paths
1067  if( mS->first == wxString( "KICAD_PTEMPLATES" )
1068  || mS->first == wxString( "KICAD6_FOOTPRINT_DIR" ) )
1069  {
1070  ++mS;
1071  continue;
1072  }
1073 
1074  if( wxString::npos != mS->second.GetValue().find( wxString( "://" ) ) )
1075  {
1076  ++mS;
1077  continue;
1078  }
1079 
1080  wxString tmp( "${" );
1081  tmp.Append( mS->first );
1082  tmp.Append( "}" );
1083  paths.push_back( tmp );
1084 
1085  if( tmp == "${KICAD6_3DMODEL_DIR}" )
1086  hasKisys3D = true;
1087 
1088  ++mS;
1089  }
1090 
1091  if( !hasKisys3D )
1092  paths.emplace_back("${KICAD6_3DMODEL_DIR}" );
1093 
1094  return true;
1095 }
#define CFGFILE_VERSION
std::list< SEARCH_PATH > m_paths
Container for project specific data.
Definition: project.h:62
#define ERRFLG_ENVPATH
bool readPathList(void)
Read a list of path names from a configuration file.
Container for data for KiCad programs.
Definition: pgm_base.h:93
bool UpdatePathList(const std::vector< SEARCH_PATH > &aPathList)
Clear the current path list and substitutes the given path list and update the path configuration fil...
bool WritePathList(const wxString &aDir, const wxString &aFilename, bool aResolvePaths)
Write the current path list to a config file.
bool SplitAlias(const wxString &aFileName, wxString &anAlias, wxString &aRelPath) const
Return true if the given name contains an alias and populates the string anAlias with the alias and a...
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
bool createPathList(void)
Build the path list using available information such as KICAD6_3DMODEL_DIR and the 3d_path_list confi...
static bool getHollerith(const std::string &aString, size_t &aIndex, wxString &aResult)
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:122
bool ValidateFileName(const wxString &aFileName, bool &hasAlias) const
Returns true if the given path is a valid aliased relative path.
wxString m_Alias
#define ERRFLG_ALIAS
bool addPath(const SEARCH_PATH &aPath)
Check that a path is valid and adds it to the search list.
wxString ShortenPath(const wxString &aFullPathName)
Produce a relative path based on the existing search directories or returns the same path if the path...
wxString m_Description
#define ERRFLG_RELPATH
wxString m_Pathvar
const std::list< SEARCH_PATH > * GetPaths() const
Return a pointer to the internal path list; the items in:load.
#define _(s)
wxLogTrace helper definitions.
wxString ResolvePath(const wxString &aFileName)
Determines the full path of the given file name.
#define RESOLVER_CONFIG
see class PGM_BASE
static std::mutex mutex_resolver
bool SetProject(PROJECT *aProject, bool *flgChanged=nullptr)
Set the current KiCad project directory as the first entry in the model path list.
The common library.
bool Set3DConfigDir(const wxString &aConfigDir)
Set the user's configuration directory for 3D models.
bool GetKicadPaths(std::list< wxString > &paths) const
Return a list of path environment variables local to KiCad.
void checkEnvVarPath(const wxString &aPath)
Check the ${ENV_VAR} component of a path and adds it to the resolver's path list if it is not yet in ...
void SetProgramBase(PGM_BASE *aBase)
Set a pointer to the application's PGM_BASE instance used to extract the local env vars.
std::map< wxString, ENV_VAR_ITEM >::const_iterator ENV_VAR_MAP_CITER
wxString GetProjectDir() const
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition: pgm_base.cpp:635
#define MASK_3D_RESOLVER
wxString m_Pathexp