KiCad PCB EDA Suite
env_paths.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) 2017 Wayne Stambaugh <[email protected]>
5  * Copyright (C) 2017-2022 KiCad Developers, see AUTHORS.txt for contributors.
6  * Copyright (C) 2017 CERN
7  * @author Maciej Suminski <[email protected]>
8  *
9  * This program is free software: you can redistribute it and/or modify it
10  * under the terms of the GNU General Public License as published by the
11  * Free Software Foundation, either version 3 of the License, or (at your
12  * option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <env_paths.h>
24 #include <project.h>
25 #include <wx/filename.h>
26 
27 static bool normalizeAbsolutePaths( const wxFileName& aPathA, const wxFileName& aPathB,
28  wxString* aResultPath )
29 {
30  wxCHECK_MSG( aPathA.IsAbsolute(), false, aPathA.GetPath() + wxT( " is not an absolute path." ) );
31  wxCHECK_MSG( aPathB.IsAbsolute(), false, aPathB.GetPath() + wxT( " is not an absolute path." ) );
32 
33  if( aPathA.GetPath() == aPathB.GetPath() )
34  return true;
35 
36  // Not sure all of volume checks are necessary since wxFileName::GetVolume() returns
37  // an empty string if the path has no volume.
38  if( ( aPathA.GetDirCount() > aPathB.GetDirCount() )
39  || ( aPathA.HasVolume() && !aPathB.HasVolume() )
40  || ( !aPathA.HasVolume() && aPathB.HasVolume() )
41  || ( ( aPathA.HasVolume() && aPathB.HasVolume() )
42  && ( aPathA.GetVolume().CmpNoCase( aPathB.GetVolume() ) != 0 ) ) )
43  return false;
44 
45  wxArrayString aDirs = aPathA.GetDirs();
46  wxArrayString bDirs = aPathB.GetDirs();
47 
48  size_t i = 0;
49 
50  while( i < aDirs.GetCount() )
51  {
52  if( aDirs[i] != bDirs[i] )
53  return false;
54 
55  i++;
56  }
57 
58  if( aResultPath )
59  {
60  while( i < bDirs.GetCount() )
61  {
62  *aResultPath += bDirs[i] + wxT( "/" );
63  i++;
64  }
65  }
66 
67  return true;
68 }
69 
70 
71 wxString NormalizePath( const wxFileName& aFilePath, const ENV_VAR_MAP* aEnvVars,
72  const wxString& aProjectPath )
73 {
74  wxFileName envPath;
75  wxString varName;
76  wxString remainingPath;
77  wxString normalizedFullPath;
78  int pathDepth = 0;
79 
80  if( aEnvVars )
81  {
82  for( auto& entry : *aEnvVars )
83  {
84  // Don't bother normalizing paths that don't exist or the user cannot read.
85  if( !wxFileName::DirExists( entry.second.GetValue() )
86  || !wxFileName::IsDirReadable( entry.second.GetValue() ) )
87  continue;
88 
89  envPath.SetPath( entry.second.GetValue() );
90 
91  wxString tmp;
92 
93  if( normalizeAbsolutePaths( envPath, aFilePath, &tmp ) )
94  {
95  int newDepth = envPath.GetDirs().GetCount();
96 
97  // Only use the variable if it removes more directories than the previous ones
98  if( newDepth > pathDepth )
99  {
100  pathDepth = newDepth;
101  varName = entry.first;
102  remainingPath = tmp;
103  }
104 
105  // @fixme Shouldn't we break here if an environment variable path is found or try
106  // at least try to pick the best match?
107  }
108  }
109  }
110 
111  if( varName.IsEmpty() && !aProjectPath.IsEmpty()
112  && wxFileName( aProjectPath ).IsAbsolute() && wxFileName( aFilePath ).IsAbsolute() )
113  {
114  envPath.SetPath( aProjectPath );
115 
116  if( normalizeAbsolutePaths( envPath, aFilePath, &remainingPath ) )
117  varName = PROJECT_VAR_NAME;
118  }
119 
120  if( varName.IsEmpty() )
121  {
122  normalizedFullPath = aFilePath.GetFullPath();
123  }
124  else
125  {
126  normalizedFullPath = wxString::Format( "${%s}/", varName );
127 
128  if( !remainingPath.IsEmpty() )
129  normalizedFullPath += remainingPath;
130 
131  normalizedFullPath += aFilePath.GetFullName();
132  }
133 
134  return normalizedFullPath;
135 }
136 
137 
138 wxString NormalizePath( const wxFileName& aFilePath, const ENV_VAR_MAP* aEnvVars,
139  const PROJECT* aProject )
140 {
141  if( aProject )
142  return NormalizePath( aFilePath, aEnvVars, aProject->GetProjectPath() );
143  else
144  return NormalizePath( aFilePath, aEnvVars, wxEmptyString );
145 }
146 
147 
148 // Create file path by appending path and file name. This approach allows the filename
149 // to contain a relative path, whereas wxFileName::SetPath() would replace the
150 // relative path
151 static wxString createFilePath( const wxString& aPath, const wxString& aFileName )
152 {
153  wxString path( aPath );
154 
155  if( !path.EndsWith( wxFileName::GetPathSeparator() ) )
156  path.Append( wxFileName::GetPathSeparator() );
157 
158  return path + aFileName;
159 }
160 
161 
162 wxString ResolveFile( const wxString& aFileName, const ENV_VAR_MAP* aEnvVars,
163  const PROJECT* aProject )
164 {
165  wxFileName full( aFileName );
166 
167  if( full.IsAbsolute() )
168  return full.GetFullPath();
169 
170  if( aProject )
171  {
172  wxFileName fn( createFilePath( aProject->GetProjectPath(), aFileName ) );
173 
174  if( fn.Exists() )
175  return fn.GetFullPath();
176  }
177 
178  if( aEnvVars )
179  {
180  for( auto& entry : *aEnvVars )
181  {
182  wxFileName fn( createFilePath( entry.second.GetValue(), aFileName ) );
183 
184  if( fn.Exists() )
185  return fn.GetFullPath();
186  }
187  }
188 
189  return wxEmptyString;
190 }
191 
192 
193 bool PathIsInsideProject( const wxString& aFileName, const PROJECT* aProject, wxFileName* aSubPath )
194 {
195  wxFileName fn( aFileName );
196  wxFileName prj( aProject->GetProjectPath() );
197 
198  wxArrayString pdirs = prj.GetDirs();
199  wxArrayString fdirs = fn.GetDirs();
200 
201  if( fdirs.size() < pdirs.size() )
202  return false;
203 
204  for( size_t i = 0; i < pdirs.size(); i++ )
205  {
206  if( fdirs[i] != pdirs[i] )
207  return false;
208  }
209 
210  // Now we know that fn is inside prj
211  if( aSubPath )
212  {
213  aSubPath->Clear();
214 
215  for( size_t i = pdirs.size(); i < fdirs.size(); i++ )
216  aSubPath->AppendDir( fdirs[i] );
217  }
218 
219  return true;
220 }
wxString ResolveFile(const wxString &aFileName, const ENV_VAR_MAP *aEnvVars, const PROJECT *aProject)
Search the default paths trying to find one with the requested file.
Definition: env_paths.cpp:162
Container for project specific data.
Definition: project.h:62
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition: project.h:38
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:122
static wxString createFilePath(const wxString &aPath, const wxString &aFileName)
Definition: env_paths.cpp:151
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
bool PathIsInsideProject(const wxString &aFileName, const PROJECT *aProject, wxFileName *aSubPath)
Definition: env_paths.cpp:193
wxString NormalizePath(const wxFileName &aFilePath, const ENV_VAR_MAP *aEnvVars, const wxString &aProjectPath)
Normalize a file path to an environmental variable, if possible.
Definition: env_paths.cpp:71
static bool normalizeAbsolutePaths(const wxFileName &aPathA, const wxFileName &aPathB, wxString *aResultPath)
Definition: env_paths.cpp:27