819 lines
23 KiB
C++
819 lines
23 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2015-2020 Cirilo Bernardo <cirilo.bernardo@gmail.com>
|
|
* Copyright (C) 2015-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <fstream>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
|
|
#include <wx/log.h>
|
|
#include <pgm_base.h>
|
|
#include <trace_helpers.h>
|
|
|
|
#include <common.h>
|
|
#include <filename_resolver.h>
|
|
#include <confirm.h>
|
|
#include <wx_filename.h>
|
|
|
|
// configuration file version
|
|
#define CFGFILE_VERSION 1
|
|
|
|
// flag bits used to track different one-off messages to users
|
|
#define ERRFLG_ALIAS (1)
|
|
#define ERRFLG_RELPATH (2)
|
|
#define ERRFLG_ENVPATH (4)
|
|
|
|
#define MASK_3D_RESOLVER "3D_RESOLVER"
|
|
|
|
static std::mutex mutex_resolver;
|
|
|
|
|
|
FILENAME_RESOLVER::FILENAME_RESOLVER() :
|
|
m_pgm( nullptr ),
|
|
m_project( nullptr )
|
|
{
|
|
m_errflags = 0;
|
|
}
|
|
|
|
|
|
bool FILENAME_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
|
|
{
|
|
if( aConfigDir.empty() )
|
|
return false;
|
|
|
|
wxFileName cfgdir( ExpandEnvVarSubstitutions( aConfigDir, m_project ), "" );
|
|
|
|
cfgdir.Normalize( FN_NORMALIZE_FLAGS );
|
|
|
|
if( !cfgdir.DirExists() )
|
|
return false;
|
|
|
|
m_configDir = cfgdir.GetPath();
|
|
createPathList();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool FILENAME_RESOLVER::SetProject( PROJECT* aProject, bool* flgChanged )
|
|
{
|
|
m_project = aProject;
|
|
|
|
if( !aProject )
|
|
return false;
|
|
|
|
wxFileName projdir( ExpandEnvVarSubstitutions( aProject->GetProjectPath(), aProject ), "" );
|
|
|
|
projdir.Normalize( FN_NORMALIZE_FLAGS );
|
|
|
|
if( !projdir.DirExists() )
|
|
return false;
|
|
|
|
m_curProjDir = projdir.GetPath();
|
|
|
|
if( flgChanged )
|
|
*flgChanged = false;
|
|
|
|
if( m_paths.empty() )
|
|
{
|
|
SEARCH_PATH al;
|
|
al.m_Alias = wxS( "${KIPRJMOD}" );
|
|
al.m_Pathvar = wxS( "${KIPRJMOD}" );
|
|
al.m_Pathexp = m_curProjDir;
|
|
m_paths.push_back( al );
|
|
|
|
if( flgChanged )
|
|
*flgChanged = true;
|
|
}
|
|
else
|
|
{
|
|
if( m_paths.front().m_Pathexp != m_curProjDir )
|
|
{
|
|
m_paths.front().m_Pathexp = m_curProjDir;
|
|
|
|
if( flgChanged )
|
|
*flgChanged = true;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
std::ostringstream ostr;
|
|
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
|
|
ostr << " * [INFO] changed project dir to ";
|
|
ostr << m_paths.front().m_Pathexp.ToUTF8();
|
|
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
wxString FILENAME_RESOLVER::GetProjectDir() const
|
|
{
|
|
return m_curProjDir;
|
|
}
|
|
|
|
|
|
void FILENAME_RESOLVER::SetProgramBase( PGM_BASE* aBase )
|
|
{
|
|
m_pgm = aBase;
|
|
|
|
if( !m_pgm || m_paths.empty() )
|
|
return;
|
|
|
|
// recreate the path list
|
|
m_paths.clear();
|
|
createPathList();
|
|
}
|
|
|
|
|
|
bool FILENAME_RESOLVER::createPathList()
|
|
{
|
|
if( !m_paths.empty() )
|
|
return true;
|
|
|
|
// add an entry for the default search path; at this point
|
|
// we cannot set a sensible default so we use an empty string.
|
|
// the user may change this later with a call to SetProjectDir()
|
|
|
|
SEARCH_PATH lpath;
|
|
lpath.m_Alias = wxS( "${KIPRJMOD}" );
|
|
lpath.m_Pathvar = wxS( "${KIPRJMOD}" );
|
|
lpath.m_Pathexp = m_curProjDir;
|
|
m_paths.push_back( lpath );
|
|
wxFileName fndummy;
|
|
wxUniChar psep = fndummy.GetPathSeparator();
|
|
std::list< wxString > epaths;
|
|
|
|
if( GetKicadPaths( epaths ) )
|
|
{
|
|
for( const wxString& currPath : epaths )
|
|
{
|
|
wxString currPathVarFormat = currPath;
|
|
currPathVarFormat.Prepend( wxS( "${" ) );
|
|
currPathVarFormat.Append( wxS( "}" ) );
|
|
|
|
wxString pathVal = ExpandEnvVarSubstitutions( currPathVarFormat, m_project );
|
|
|
|
if( pathVal.empty() )
|
|
{
|
|
lpath.m_Pathexp.clear();
|
|
}
|
|
else
|
|
{
|
|
fndummy.Assign( pathVal, "" );
|
|
fndummy.Normalize( FN_NORMALIZE_FLAGS );
|
|
lpath.m_Pathexp = fndummy.GetFullPath();
|
|
}
|
|
|
|
lpath.m_Alias = currPath;
|
|
lpath.m_Pathvar = currPath;
|
|
|
|
if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
|
|
lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
|
|
|
|
// we add it first with the alias set to the non-variable format
|
|
m_paths.push_back( lpath );
|
|
|
|
// now add it with the "new variable format ${VAR}"
|
|
lpath.m_Alias = currPathVarFormat;
|
|
m_paths.push_back( lpath );
|
|
}
|
|
}
|
|
|
|
if( m_paths.empty() )
|
|
return false;
|
|
|
|
#ifdef DEBUG
|
|
wxLogTrace( MASK_3D_RESOLVER, wxS( " * [3D model] search paths:\n" ) );
|
|
std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
|
|
|
|
while( sPL != m_paths.end() )
|
|
{
|
|
wxLogTrace( MASK_3D_RESOLVER, wxS( " + %s : '%s'\n" ), (*sPL).m_Alias.GetData(),
|
|
(*sPL).m_Pathexp.GetData() );
|
|
++sPL;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool FILENAME_RESOLVER::UpdatePathList( const std::vector< SEARCH_PATH >& aPathList )
|
|
{
|
|
wxUniChar envMarker( '$' );
|
|
|
|
while( !m_paths.empty() && envMarker != *m_paths.back().m_Alias.rbegin() )
|
|
m_paths.pop_back();
|
|
|
|
for( const SEARCH_PATH& path : aPathList )
|
|
addPath( path );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
wxString FILENAME_RESOLVER::ResolvePath( const wxString& aFileName, const wxString& aWorkingPath )
|
|
{
|
|
std::lock_guard<std::mutex> lock( mutex_resolver );
|
|
|
|
if( aFileName.empty() )
|
|
return wxEmptyString;
|
|
|
|
if( m_paths.empty() )
|
|
createPathList();
|
|
|
|
// first attempt to use the name as specified:
|
|
wxString tname = aFileName;
|
|
|
|
// Note: variable expansion must preferably be performed via a threadsafe wrapper for the
|
|
// getenv() system call. If we allow the wxFileName::Normalize() routine to perform expansion
|
|
// then we will have a race condition since wxWidgets does not assure a threadsafe wrapper
|
|
// for getenv().
|
|
tname = ExpandEnvVarSubstitutions( tname, m_project );
|
|
|
|
wxFileName tmpFN( tname );
|
|
|
|
// this case covers full paths, leading expanded vars, and paths relative to the current
|
|
// working directory (which is not necessarily the current project directory)
|
|
if( tmpFN.FileExists() )
|
|
{
|
|
tmpFN.Normalize( FN_NORMALIZE_FLAGS );
|
|
tname = tmpFN.GetFullPath();
|
|
|
|
// special case: if a path begins with ${ENV_VAR} but is not in the resolver's path list
|
|
// then add it.
|
|
if( aFileName.StartsWith( wxS( "${" ) ) || aFileName.StartsWith( wxS( "$(" ) ) )
|
|
checkEnvVarPath( aFileName );
|
|
|
|
return tname;
|
|
}
|
|
|
|
// if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the file either does
|
|
// not exist or the ENV_VAR is not defined
|
|
if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
|
|
{
|
|
if( !( m_errflags & ERRFLG_ENVPATH ) )
|
|
{
|
|
m_errflags |= ERRFLG_ENVPATH;
|
|
wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
|
|
errmsg.append( "\n" );
|
|
errmsg.append( tname );
|
|
errmsg.append( "\n" );
|
|
wxLogTrace( tracePathsAndFiles, errmsg );
|
|
}
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
// at this point aFileName is:
|
|
// a. an aliased shortened name or
|
|
// b. cannot be determined
|
|
|
|
// check the path relative to the current project directory;
|
|
// NB: this is not necessarily the same as the current working directory, which has already
|
|
// been checked. This case accounts for partial paths which do not contain ${KIPRJMOD}.
|
|
// This check is performed before checking the path relative to ${KICAD7_3DMODEL_DIR} so that
|
|
// users can potentially override a model within ${KICAD7_3DMODEL_DIR}.
|
|
if( !m_paths.begin()->m_Pathexp.empty() && !tname.StartsWith( ":" ) )
|
|
{
|
|
tmpFN.Assign( m_paths.begin()->m_Pathexp, "" );
|
|
wxString fullPath = tmpFN.GetPathWithSep() + tname;
|
|
|
|
fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
|
|
|
|
if( wxFileName::FileExists( fullPath ) )
|
|
{
|
|
tmpFN.Assign( fullPath );
|
|
tmpFN.Normalize( FN_NORMALIZE_FLAGS );
|
|
tname = tmpFN.GetFullPath();
|
|
return tname;
|
|
}
|
|
|
|
}
|
|
|
|
// check path relative to search path
|
|
if( !aWorkingPath.IsEmpty() && !tname.StartsWith( ":" ) )
|
|
{
|
|
wxString tmp = aWorkingPath;
|
|
tmp.Append( tmpFN.GetPathSeparator() );
|
|
tmp.Append( tname );
|
|
tmpFN.Assign( tmp );
|
|
|
|
if( tmpFN.MakeAbsolute() && tmpFN.FileExists() )
|
|
{
|
|
tname = tmpFN.GetFullPath();
|
|
return tname;
|
|
}
|
|
}
|
|
|
|
// check the partial path relative to ${KICAD7_3DMODEL_DIR} (legacy behavior)
|
|
if( !tname.StartsWith( wxS( ":" ) ) )
|
|
{
|
|
wxFileName fpath;
|
|
wxString fullPath( wxS( "${KICAD7_3DMODEL_DIR}" ) );
|
|
fullPath.Append( fpath.GetPathSeparator() );
|
|
fullPath.Append( tname );
|
|
fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
|
|
fpath.Assign( fullPath );
|
|
|
|
if( fpath.Normalize( FN_NORMALIZE_FLAGS ) && fpath.FileExists() )
|
|
{
|
|
tname = fpath.GetFullPath();
|
|
return tname;
|
|
}
|
|
|
|
}
|
|
|
|
// at this point the filename must contain an alias or else it is invalid
|
|
wxString alias; // the alias portion of the short filename
|
|
wxString relpath; // the path relative to the alias
|
|
|
|
if( !SplitAlias( tname, alias, relpath ) )
|
|
{
|
|
if( !( m_errflags & ERRFLG_RELPATH ) )
|
|
{
|
|
// this can happen if the file was intended to be relative to ${KICAD7_3DMODEL_DIR}
|
|
// but ${KICAD7_3DMODEL_DIR} is not set or is incorrect.
|
|
m_errflags |= ERRFLG_RELPATH;
|
|
wxString errmsg = "[3D File Resolver] No such path";
|
|
errmsg.append( wxS( "\n" ) );
|
|
errmsg.append( tname );
|
|
errmsg.append( wxS( "\n" ) );
|
|
wxLogTrace( tracePathsAndFiles, errmsg );
|
|
}
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
for( const SEARCH_PATH& path : m_paths )
|
|
{
|
|
// ${ENV_VAR} paths have already been checked; skip them
|
|
if( path.m_Alias.StartsWith( wxS( "${" ) ) || path.m_Alias.StartsWith( wxS( "$(" ) ) )
|
|
continue;
|
|
|
|
if( path.m_Alias == alias && !path.m_Pathexp.empty() )
|
|
{
|
|
wxFileName fpath( wxFileName::DirName( path.m_Pathexp ) );
|
|
wxString fullPath = fpath.GetPathWithSep() + relpath;
|
|
|
|
fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
|
|
|
|
if( wxFileName::FileExists( fullPath ) )
|
|
{
|
|
tname = fullPath;
|
|
|
|
wxFileName tmp( fullPath );
|
|
|
|
if( tmp.Normalize( FN_NORMALIZE_FLAGS ) )
|
|
tname = tmp.GetFullPath();
|
|
|
|
return tname;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !( m_errflags & ERRFLG_ALIAS ) )
|
|
{
|
|
m_errflags |= ERRFLG_ALIAS;
|
|
wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
|
|
errmsg.append( "\n" );
|
|
errmsg.append( tname.substr( 1 ) );
|
|
errmsg.append( "\n" );
|
|
wxLogTrace( tracePathsAndFiles, errmsg );
|
|
}
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
|
|
bool FILENAME_RESOLVER::addPath( const SEARCH_PATH& aPath )
|
|
{
|
|
if( aPath.m_Alias.empty() || aPath.m_Pathvar.empty() )
|
|
return false;
|
|
|
|
std::lock_guard<std::mutex> lock( mutex_resolver );
|
|
|
|
SEARCH_PATH tpath = aPath;
|
|
|
|
#ifdef _WIN32
|
|
while( tpath.m_Pathvar.EndsWith( wxT( "\\" ) ) )
|
|
tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
|
|
#else
|
|
while( tpath.m_Pathvar.EndsWith( wxT( "/" ) ) && tpath.m_Pathvar.length() > 1 )
|
|
tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
|
|
#endif
|
|
|
|
wxFileName path( ExpandEnvVarSubstitutions( tpath.m_Pathvar, m_project ), "" );
|
|
|
|
path.Normalize( FN_NORMALIZE_FLAGS );
|
|
|
|
if( !path.DirExists() )
|
|
{
|
|
if( aPath.m_Pathvar == wxS( "${KICAD7_3DMODEL_DIR}" )
|
|
|| aPath.m_Pathvar == wxS( "${KIPRJMOD}" ) || aPath.m_Pathvar == wxS( "$(KIPRJMOD)" )
|
|
|| aPath.m_Pathvar == wxS( "${KISYS3DMOD}" ) || aPath.m_Pathvar == wxS( "$(KISYS3DMOD)" ) )
|
|
{
|
|
// suppress the message if the missing pathvar is a system variable
|
|
}
|
|
else
|
|
{
|
|
wxString msg = _( "The given path does not exist" );
|
|
msg.append( wxT( "\n" ) );
|
|
msg.append( tpath.m_Pathvar );
|
|
DisplayErrorMessage( nullptr, msg );
|
|
}
|
|
|
|
tpath.m_Pathexp.clear();
|
|
}
|
|
else
|
|
{
|
|
tpath.m_Pathexp = path.GetFullPath();
|
|
|
|
#ifdef _WIN32
|
|
while( tpath.m_Pathexp.EndsWith( wxT( "\\" ) ) )
|
|
tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
|
|
#else
|
|
while( tpath.m_Pathexp.EndsWith( wxT( "/" ) ) && tpath.m_Pathexp.length() > 1 )
|
|
tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
|
|
#endif
|
|
}
|
|
|
|
std::list< SEARCH_PATH >::iterator sPL = m_paths.begin();
|
|
std::list< SEARCH_PATH >::iterator ePL = m_paths.end();
|
|
|
|
while( sPL != ePL )
|
|
{
|
|
if( tpath.m_Alias == sPL->m_Alias )
|
|
{
|
|
wxString msg = _( "Alias: " );
|
|
msg.append( tpath.m_Alias );
|
|
msg.append( wxT( "\n" ) );
|
|
msg.append( _( "This path:" ) + wxS( " " ) );
|
|
msg.append( tpath.m_Pathvar );
|
|
msg.append( wxT( "\n" ) );
|
|
msg.append( _( "Existing path:" ) + wxS( " " ) );
|
|
msg.append( sPL->m_Pathvar );
|
|
DisplayErrorMessage( nullptr, _( "Bad alias (duplicate name)" ), msg );
|
|
return false;
|
|
}
|
|
|
|
++sPL;
|
|
}
|
|
|
|
m_paths.push_back( tpath );
|
|
return true;
|
|
}
|
|
|
|
|
|
void FILENAME_RESOLVER::checkEnvVarPath( const wxString& aPath )
|
|
{
|
|
bool useParen = false;
|
|
|
|
if( aPath.StartsWith( wxS( "$(" ) ) )
|
|
useParen = true;
|
|
else if( !aPath.StartsWith( wxS( "${" ) ) )
|
|
return;
|
|
|
|
size_t pEnd;
|
|
|
|
if( useParen )
|
|
pEnd = aPath.find( wxS( ")" ) );
|
|
else
|
|
pEnd = aPath.find( wxS( "}" ) );
|
|
|
|
if( pEnd == wxString::npos )
|
|
return;
|
|
|
|
wxString envar = aPath.substr( 0, pEnd + 1 );
|
|
|
|
// check if the alias exists; if not then add it to the end of the
|
|
// env var section of the path list
|
|
auto sPL = m_paths.begin();
|
|
auto ePL = m_paths.end();
|
|
|
|
while( sPL != ePL )
|
|
{
|
|
if( sPL->m_Alias == envar )
|
|
return;
|
|
|
|
if( !sPL->m_Alias.StartsWith( wxS( "${" ) ) )
|
|
break;
|
|
|
|
++sPL;
|
|
}
|
|
|
|
SEARCH_PATH lpath;
|
|
lpath.m_Alias = envar;
|
|
lpath.m_Pathvar = lpath.m_Alias;
|
|
wxFileName tmpFN( ExpandEnvVarSubstitutions( lpath.m_Alias, m_project ), "" );
|
|
|
|
wxUniChar psep = tmpFN.GetPathSeparator();
|
|
tmpFN.Normalize( FN_NORMALIZE_FLAGS );
|
|
|
|
if( !tmpFN.DirExists() )
|
|
return;
|
|
|
|
lpath.m_Pathexp = tmpFN.GetFullPath();
|
|
|
|
if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
|
|
lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
|
|
|
|
if( lpath.m_Pathexp.empty() )
|
|
return;
|
|
|
|
m_paths.insert( sPL, lpath );
|
|
}
|
|
|
|
|
|
wxString FILENAME_RESOLVER::ShortenPath( const wxString& aFullPathName )
|
|
{
|
|
wxString fname = aFullPathName;
|
|
|
|
if( m_paths.empty() )
|
|
createPathList();
|
|
|
|
std::lock_guard<std::mutex> lock( mutex_resolver );
|
|
|
|
std::list< SEARCH_PATH >::const_iterator sL = m_paths.begin();
|
|
size_t idx;
|
|
|
|
while( sL != m_paths.end() )
|
|
{
|
|
// undefined paths do not participate in the
|
|
// file name shortening procedure
|
|
if( sL->m_Pathexp.empty() )
|
|
{
|
|
++sL;
|
|
continue;
|
|
}
|
|
|
|
wxFileName fpath;
|
|
|
|
// in the case of aliases, ensure that we use the most recent definition
|
|
if( sL->m_Alias.StartsWith( wxS( "${" ) ) || sL->m_Alias.StartsWith( wxS( "$(" ) ) )
|
|
{
|
|
wxString tpath = ExpandEnvVarSubstitutions( sL->m_Alias, m_project );
|
|
|
|
if( tpath.empty() )
|
|
{
|
|
++sL;
|
|
continue;
|
|
}
|
|
|
|
fpath.Assign( tpath, wxT( "" ) );
|
|
}
|
|
else
|
|
{
|
|
fpath.Assign( sL->m_Pathexp, wxT( "" ) );
|
|
}
|
|
|
|
wxString fps = fpath.GetPathWithSep();
|
|
wxString tname;
|
|
|
|
idx = fname.find( fps );
|
|
|
|
if( idx == 0 )
|
|
{
|
|
fname = fname.substr( fps.size() );
|
|
|
|
#ifdef _WIN32
|
|
// ensure only the '/' separator is used in the internal name
|
|
fname.Replace( wxT( "\\" ), wxT( "/" ) );
|
|
#endif
|
|
|
|
if( sL->m_Alias.StartsWith( wxS( "${" ) ) || sL->m_Alias.StartsWith( wxS( "$(" ) ) )
|
|
{
|
|
// old style ENV_VAR
|
|
tname = sL->m_Alias;
|
|
tname.Append( wxS( "/" ) );
|
|
tname.append( fname );
|
|
}
|
|
else
|
|
{
|
|
// new style alias
|
|
tname = ":";
|
|
tname.append( sL->m_Alias );
|
|
tname.append( wxS( ":" ) );
|
|
tname.append( fname );
|
|
}
|
|
|
|
return tname;
|
|
}
|
|
|
|
++sL;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// it is strange to convert an MSWin full path to use the
|
|
// UNIX separator but this is done for consistency and can
|
|
// be helpful even when transferring project files from
|
|
// MSWin to *NIX.
|
|
fname.Replace( wxT( "\\" ), wxT( "/" ) );
|
|
#endif
|
|
|
|
return fname;
|
|
}
|
|
|
|
|
|
|
|
const std::list< SEARCH_PATH >* FILENAME_RESOLVER::GetPaths() const
|
|
{
|
|
return &m_paths;
|
|
}
|
|
|
|
|
|
bool FILENAME_RESOLVER::SplitAlias( const wxString& aFileName,
|
|
wxString& anAlias, wxString& aRelPath ) const
|
|
{
|
|
anAlias.clear();
|
|
aRelPath.clear();
|
|
|
|
size_t searchStart = 0;
|
|
|
|
if( aFileName.StartsWith( wxT( ":" ) ) )
|
|
searchStart = 1;
|
|
|
|
size_t tagpos = aFileName.find( wxT( ":" ), searchStart );
|
|
|
|
if( tagpos == wxString::npos || tagpos == searchStart )
|
|
return false;
|
|
|
|
if( tagpos + 1 >= aFileName.length() )
|
|
return false;
|
|
|
|
anAlias = aFileName.substr( searchStart, tagpos - searchStart );
|
|
aRelPath = aFileName.substr( tagpos + 1 );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool FILENAME_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias ) const
|
|
{
|
|
// Rules:
|
|
// 1. The generic form of an aliased 3D relative path is:
|
|
// ALIAS:relative/path
|
|
// 2. ALIAS is a UTF string excluding wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" )
|
|
// 3. The relative path must be a valid relative path for the platform
|
|
hasAlias = false;
|
|
|
|
if( aFileName.empty() )
|
|
return false;
|
|
|
|
wxString filename = aFileName;
|
|
wxString lpath;
|
|
size_t aliasStart = aFileName.StartsWith( ':' ) ? 1 : 0;
|
|
size_t aliasEnd = aFileName.find( ':', aliasStart );
|
|
|
|
// ensure that the file separators suit the current platform
|
|
#ifdef __WINDOWS__
|
|
filename.Replace( wxT( "/" ), wxT( "\\" ) );
|
|
|
|
// if we see the :\ pattern then it must be a drive designator
|
|
if( aliasEnd != wxString::npos )
|
|
{
|
|
size_t pos1 = filename.find( wxT( ":\\" ) );
|
|
|
|
if( pos1 != wxString::npos && ( pos1 != aliasEnd || pos1 != 1 ) )
|
|
return false;
|
|
|
|
// if we have a drive designator then we have no alias
|
|
if( pos1 != wxString::npos )
|
|
aliasEnd = wxString::npos;
|
|
}
|
|
#else
|
|
filename.Replace( wxT( "\\" ), wxT( "/" ) );
|
|
#endif
|
|
|
|
// names may not end with ':'
|
|
if( aliasEnd == aFileName.length() -1 )
|
|
return false;
|
|
|
|
if( aliasEnd != wxString::npos )
|
|
{
|
|
// ensure the alias component is not empty
|
|
if( aliasEnd == aliasStart )
|
|
return false;
|
|
|
|
lpath = filename.substr( aliasStart, aliasEnd );
|
|
|
|
// check the alias for restricted characters
|
|
if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) )
|
|
return false;
|
|
|
|
hasAlias = true;
|
|
lpath = aFileName.substr( aliasEnd + 1 );
|
|
}
|
|
else
|
|
{
|
|
lpath = aFileName;
|
|
|
|
// in the case of ${ENV_VAR}|$(ENV_VAR)/path, strip the
|
|
// environment string before testing
|
|
aliasEnd = wxString::npos;
|
|
|
|
if( aFileName.StartsWith( wxS( "${" ) ) )
|
|
aliasEnd = aFileName.find( '}' );
|
|
else if( aFileName.StartsWith( wxS( "$(" ) ) )
|
|
aliasEnd = aFileName.find( ')' );
|
|
|
|
if( aliasEnd != wxString::npos )
|
|
lpath = aFileName.substr( aliasEnd + 1 );
|
|
|
|
}
|
|
|
|
// Test for forbidden chars in filenames. Should be wxFileName::GetForbiddenChars()
|
|
// On MSW, the list returned by wxFileName::GetForbiddenChars() contains separators
|
|
// '\'and '/' used here because lpath can be a full path.
|
|
// So remove separators
|
|
wxString lpath_no_sep = lpath;
|
|
#ifdef __WINDOWS__
|
|
lpath_no_sep.Replace( "/", " " );
|
|
lpath_no_sep.Replace( "\\", " " );
|
|
|
|
// A disk identifier is allowed, and therefore remove its separator
|
|
if( lpath_no_sep.Length() > 1 && lpath_no_sep[1] == ':' )
|
|
lpath_no_sep[1] = ' ';
|
|
#endif
|
|
|
|
if( wxString::npos != lpath_no_sep.find_first_of( wxFileName::GetForbiddenChars() ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool FILENAME_RESOLVER::GetKicadPaths( std::list< wxString >& paths ) const
|
|
{
|
|
paths.clear();
|
|
|
|
if( !m_pgm )
|
|
return false;
|
|
|
|
bool hasKisys3D = false;
|
|
|
|
|
|
// iterate over the list of internally defined ENV VARs
|
|
// and add them to the paths list
|
|
ENV_VAR_MAP_CITER mS = m_pgm->GetLocalEnvVariables().begin();
|
|
ENV_VAR_MAP_CITER mE = m_pgm->GetLocalEnvVariables().end();
|
|
|
|
while( mS != mE )
|
|
{
|
|
// filter out URLs, template directories, and known system paths
|
|
if( mS->first == wxS( "KICAD_PTEMPLATES" )
|
|
|| mS->first.Matches( wxS( "KICAD*_FOOTPRINT_DIR") ) )
|
|
{
|
|
++mS;
|
|
continue;
|
|
}
|
|
|
|
if( wxString::npos != mS->second.GetValue().find( wxS( "://" ) ) )
|
|
{
|
|
++mS;
|
|
continue;
|
|
}
|
|
|
|
//also add the path without the ${} to act as legacy alias support for older files
|
|
paths.push_back( mS->first );
|
|
|
|
if( mS->first.Matches( wxS("KICAD*_3DMODEL_DIR") ) )
|
|
hasKisys3D = true;
|
|
|
|
++mS;
|
|
}
|
|
|
|
if( !hasKisys3D )
|
|
paths.emplace_back( wxS("KICAD7_3DMODEL_DIR") );
|
|
|
|
return true;
|
|
}
|