1 /* $Id: file_contents.cpp 485908 2015-11-30 14:28:08Z gouriano $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Author: Viatcheslav Gorelenkov
27 *
28 */
29
30 #include <ncbi_pch.hpp>
31 #include "file_contents.hpp"
32 #include "proj_builder_app.hpp"
33 #include "msvc_prj_defines.hpp"
34 #include "ptb_err_codes.hpp"
35 #include <corelib/ncbistr.hpp>
36
37 BEGIN_NCBI_SCOPE
38
MakeFileTypeAsString(EMakeFileType type)39 string MakeFileTypeAsString(EMakeFileType type)
40 {
41 switch (type) {
42 case eMakeType_Undefined: return "";
43 case eMakeType_Expendable: return "EXPENDABLE";
44 case eMakeType_Potential: return "POTENTIAL";
45 case eMakeType_Excluded: return "EXCLUDED";
46 case eMakeType_ExcludedByReq: return "EXCLUDEDBYREQ";
47 default: return "INCORRECT!";
48 }
49 }
50
51 //-----------------------------------------------------------------------------
CSimpleMakeFileContents(void)52 CSimpleMakeFileContents::CSimpleMakeFileContents(void)
53 : m_Type( eMakeType_Undefined ), m_Parent(NULL), m_Raw(false)
54 {
55 }
56
57
CSimpleMakeFileContents(const CSimpleMakeFileContents & contents)58 CSimpleMakeFileContents::CSimpleMakeFileContents
59 (const CSimpleMakeFileContents& contents)
60 {
61 SetFrom(contents);
62 }
63
64
operator =(const CSimpleMakeFileContents & contents)65 CSimpleMakeFileContents& CSimpleMakeFileContents::operator=
66 (const CSimpleMakeFileContents& contents)
67 {
68 if (this != &contents) {
69 SetFrom(contents);
70 }
71 return *this;
72 }
73
74
CSimpleMakeFileContents(const string & file_path,EMakeFileType type)75 CSimpleMakeFileContents::CSimpleMakeFileContents(
76 const string& file_path, EMakeFileType type)
77 {
78 LoadFrom(file_path, this);
79 m_Type = type;
80 m_Parent = NULL;
81 m_Raw = false;
82 }
83
CSimpleMakeFileContents(const string & file_path)84 CSimpleMakeFileContents::CSimpleMakeFileContents(const string& file_path)
85 {
86 LoadFrom(file_path, this);
87 m_Type = eMakeType_Undefined;
88 m_Parent = NULL;
89 m_Raw = true;
90 }
91
92
~CSimpleMakeFileContents(void)93 CSimpleMakeFileContents::~CSimpleMakeFileContents(void)
94 {
95 }
96
97
Clear(void)98 void CSimpleMakeFileContents::Clear(void)
99 {
100 m_Contents.clear();
101 m_Type = eMakeType_Undefined;
102 m_Filename.erase();
103 m_Parent = NULL;
104 m_Raw = false;
105 }
106
107
SetFrom(const CSimpleMakeFileContents & contents)108 void CSimpleMakeFileContents::SetFrom(const CSimpleMakeFileContents& contents)
109 {
110 m_Contents = contents.m_Contents;
111 m_Type = contents.m_Type;
112 m_Filename = contents.m_Filename;
113 m_ValueSeparator = contents.m_ValueSeparator;
114 m_Parent = contents.m_Parent;
115 m_Raw = contents.m_Raw;
116 }
117
118
LoadFrom(const string & file_path,CSimpleMakeFileContents * fc)119 void CSimpleMakeFileContents::LoadFrom(const string& file_path,
120 CSimpleMakeFileContents* fc)
121 {
122 if (fc->m_ValueSeparator.empty()) {
123 fc->m_ValueSeparator = LIST_SEPARATOR;
124 }
125 CSimpleMakeFileContents::SParser parser(fc);
126 fc->Clear();
127
128 CNcbiIfstream ifs(file_path.c_str(), IOS_BASE::in | IOS_BASE::binary);
129 if ( !ifs )
130 NCBI_THROW(CProjBulderAppException, eFileOpen, file_path);
131
132 fc->m_Filename = file_path;
133 parser.StartParse();
134
135 string strline;
136 while ( NcbiGetlineEOL(ifs, strline) )
137 parser.AcceptLine(strline);
138
139 parser.EndParse();
140 }
141
AddDefinition(const string & key,const string & value)142 void CSimpleMakeFileContents::AddDefinition(const string& key,
143 const string& value)
144 {
145 SKeyValue kv;
146 kv.m_Key = key;
147 kv.m_Value = value;
148 AddReadyKV(kv);
149 }
150
RemoveDefinition(const string & key)151 void CSimpleMakeFileContents::RemoveDefinition( const string& key)
152 {
153 TContents::iterator i = m_Contents.find(key);
154 if (i != m_Contents.end()) {
155 m_Contents.erase(i);
156 }
157 }
158
HasDefinition(const string & key) const159 bool CSimpleMakeFileContents::HasDefinition( const string& key) const
160 {
161 return m_Contents.find(key) != m_Contents.end();
162 }
163
DoesValueContain(const string & key,const string & value,bool ifnokey) const164 bool CSimpleMakeFileContents::DoesValueContain(
165 const string& key, const string& value, bool ifnokey /*=true*/) const
166 {
167 TContents::const_iterator k = m_Contents.find(key);
168 if (k != m_Contents.end()) {
169 return find(k->second.begin(), k->second.end(), value) != k->second.end();
170 }
171 return ifnokey;
172 }
173
GetPathValue(const string & key,string & value) const174 bool CSimpleMakeFileContents::GetPathValue(const string& key, string& value) const
175 {
176 if (GetValue(key,value)) {
177 string separator;
178 separator += CDirEntry::GetPathSeparator();
179 NStr::ReplaceInPlace(value,"/",separator);
180 return true;
181 }
182 return false;
183 }
184
GetValue(const string & key,list<string> & value) const185 bool CSimpleMakeFileContents::GetValue(const string& key, list<string>& value) const
186 {
187 value.clear();
188 TContents::const_iterator k = m_Contents.find(key);
189 if (k == m_Contents.end()) {
190 return false;
191 }
192 value = k->second;
193 return true;
194 }
195
GetValue(const string & key,string & value) const196 bool CSimpleMakeFileContents::GetValue(const string& key, string& value) const
197 {
198 value.erase();
199 TContents::const_iterator k = m_Contents.find(key);
200 if (k == m_Contents.end()) {
201 return false;
202 }
203 value = " ";
204 const list<string>& lst = k->second;
205 list<string>::const_iterator i = lst.begin();
206 if (i != lst.end() && *i != "#") {
207 value = *i;
208 ++i;
209 }
210 for (; i != lst.end(); ++i) {
211 if (*i == "#") {
212 break;
213 }
214 value += ' ';
215 value += *i;
216 }
217 if (!value.empty() && !m_Raw) {
218 string::size_type start, end, done = 0;
219 while ((start = value.find("$(", done)) != string::npos) {
220 end = value.find(")", start);
221 if (end == string::npos) {
222 PTB_WARNING_EX(m_Filename, ePTB_MacroInvalid,
223 "Invalid macro definition: " << value);
224 break;
225 }
226 string raw_macro = value.substr(start,end-start+1);
227 if (CSymResolver::IsDefine(raw_macro)) {
228 string macro = CSymResolver::StripDefine(raw_macro);
229 string definition;
230 GetValue(macro, definition);
231 value = NStr::Replace(value, raw_macro, definition);
232 }
233 }
234 #if 0
235 value = NStr::Replace(value,"-l",kEmptyStr);
236 value = NStr::Replace(value,"-dll",kEmptyStr);
237 value = NStr::Replace(value,"-static",kEmptyStr);
238 #endif
239 }
240 return true;
241 }
242
CollectValues(const string & key,list<string> & values,EHowToCollect how) const243 bool CSimpleMakeFileContents::CollectValues(
244 const string& key, list<string>& values, EHowToCollect how) const
245 {
246 TContents::const_iterator k = m_Contents.find(key);
247 if (k != m_Contents.end()) {
248 copy(k->second.begin(), k->second.end(), back_inserter(values));
249 }
250 if (m_Parent) {
251 m_Parent->CollectValues(key,values,eAsIs);
252 }
253 if (values.empty()) {
254 return false;
255 }
256
257 if (how == eSortUnique) {
258 values.sort();
259 values.unique();
260 }
261 else if (how == eMergePlusMinus) {
262 bool erased = false;
263 do {
264 erased = false;
265 for ( list<string>::iterator i = values.begin(); i != values.end(); ++i) {
266 if (i->at(0) == '-') {
267 list<string>::iterator plus;
268 while ((plus = find( i, values.end(), i->c_str()+1)) != values.end()) {
269 values.erase(plus);
270 }
271 values.erase(i);
272 erased = true;
273 break;
274 }
275 }
276 } while (erased);
277 }
278 else if (how == eFirstNonempty) {
279 while ( !values.empty() && values.front().empty()) {
280 values.pop_front();
281 }
282 }
283 return !values.empty();
284 }
285
Save(const string & filename) const286 void CSimpleMakeFileContents::Save(const string& filename) const
287 {
288 CNcbiOfstream ofs(filename.c_str(), IOS_BASE::out | IOS_BASE::trunc );
289 if (ofs.is_open()) {
290 Dump(ofs);
291 }
292 }
293
Dump(CNcbiOstream & ostr,const list<string> * skip) const294 void CSimpleMakeFileContents::Dump(CNcbiOstream& ostr, const list<string>* skip /*=0*/) const
295 {
296 size_t len=0;
297 ITERATE(TContents, p, m_Contents) {
298 if (skip != 0 && find(skip->begin(), skip->end(), p->first) != skip->end()) {
299 continue;
300 }
301 ostr << p->first << " = ";
302 ITERATE(list<string>, m, p->second) {
303 if (len > 60) {
304 ostr << '\\' << endl << " ";
305 len = 0;
306 }
307 ostr << NStr::Replace(*m,"\\","/") << " ";
308 len += m->size() + 1;
309 }
310 ostr << endl;
311 len=0;
312 }
313 }
314
315
SParser(CSimpleMakeFileContents * fc)316 CSimpleMakeFileContents::SParser::SParser(CSimpleMakeFileContents* fc)
317 :m_FileContents(fc)
318 {
319 }
320
321
StartParse(void)322 void CSimpleMakeFileContents::SParser::StartParse(void)
323 {
324 m_Continue = false;
325 m_CurrentKV = SKeyValue();
326 }
327
328
329
330 //------------------------------------------------------------------------------
331 // helpers ---------------------------------------------------------------------
332
s_WillContinue(const string & line)333 static bool s_WillContinue(const string& line)
334 {
335 return NStr::EndsWith(line, "\\");
336 }
337
338
s_StripContinueStr(string * str)339 static void s_StripContinueStr(string* str)
340 {
341 str->erase(str->length() -1, 1); // delete last '\'
342 *str += " ";
343 }
344
345
s_SplitKV(const string & line,SKeyValue & kv)346 static bool s_SplitKV(const string& line, SKeyValue& kv)
347 {
348 if ( !NStr::SplitInTwo(line, "=", kv.m_Key, kv.m_Value) )
349 return false;
350
351 kv.m_Key = NStr::TruncateSpaces(kv.m_Key); // only for key - preserve sp for vals
352 kv.m_Append = NStr::EndsWith(kv.m_Key, "+");
353 if (kv.m_Append) {
354 kv.m_Append = true;
355 NStr::ReplaceInPlace(kv.m_Key, "+", " ");
356 kv.m_Key = NStr::TruncateSpaces(kv.m_Key); // only for key - preserve sp for vals
357 }
358 if ( s_WillContinue(kv.m_Value) )
359 s_StripContinueStr(&kv.m_Value);
360
361 return true;
362 }
363
364
s_IsKVString(const string & str)365 static bool s_IsKVString(const string& str)
366 {
367 size_t eq_pos = str.find("=");
368 if (eq_pos == NPOS)
369 return false;
370 string mb_key = str.substr(0, eq_pos - 1);
371 return mb_key.find_first_of("$()") == NPOS;
372 }
373
374
s_IsCommented(const string & str)375 static bool s_IsCommented(const string& str)
376 {
377 return NStr::StartsWith(str, "#");
378 }
379
380
381
AcceptLine(const string & line)382 void CSimpleMakeFileContents::SParser::AcceptLine(const string& line)
383 {
384 string strline = NStr::TruncateSpaces(line);
385 if ( s_IsCommented(strline) )
386 return;
387
388 if (m_Continue) {
389 m_Continue = s_WillContinue(strline);
390 if ( strline.empty() || strline.find_first_not_of(' ') == string::npos ) {
391 //fix for ill-formed makefiles:
392 m_FileContents->AddReadyKV(m_CurrentKV);
393 return;
394 #if 0
395 // this is dangerous "fix"
396 } else if ( s_IsKVString(strline) ) {
397 //fix for ill-formed makefiles:
398 m_FileContents->AddReadyKV(m_CurrentKV);
399 m_Continue = false; // guard
400 AcceptLine(strline.c_str());
401 #endif
402 }
403 if (m_Continue)
404 s_StripContinueStr(&strline);
405 m_CurrentKV.m_Value += strline;
406 return;
407
408 } else {
409 const string include_token("include ");
410 if ( NStr::StartsWith(strline, include_token) ) {
411 string include = NStr::TruncateSpaces(
412 strline.substr(include_token.length()));
413 string root;
414 string srcdir_token("$(srcdir)");
415 if (NStr::StartsWith(include, srcdir_token)) {
416 root = CDirEntry( m_FileContents->GetFileName()).GetDir();
417 } else {
418 srcdir_token = "$(top_srcdir)";
419 if (NStr::StartsWith(include, srcdir_token)) {
420 root = GetApp().GetProjectTreeInfo().m_Root;
421 }
422 else {
423 #if 0
424 srcdir_token = "$(builddir)";
425 if (NStr::StartsWith(include, srcdir_token)) {
426 root = GetApp().GetBuildRoot();
427 if (root.empty()) {
428 root = CDirEntry(GetApp().m_Solution).GetDir();
429 }
430 }
431 #endif
432 }
433 }
434 if (!root.empty()) {
435 include = CDirEntry::NormalizePath(
436 CDirEntry::ConcatPath( root,
437 NStr::Replace(include, srcdir_token, "")));
438 LoadInclude(include);
439 }
440 } else
441 if ( s_IsKVString(strline) ) {
442 m_FileContents->AddReadyKV(m_CurrentKV);
443 m_Continue = s_WillContinue(strline);
444 s_SplitKV(strline, m_CurrentKV);
445 return;
446 }
447 }
448 }
449
LoadInclude(const string & file_path)450 void CSimpleMakeFileContents::SParser::LoadInclude(const string& file_path)
451 {
452 EndParse();
453 CNcbiIfstream ifs(file_path.c_str(), IOS_BASE::in | IOS_BASE::binary);
454 if ( !ifs ) {
455 PTB_WARNING_EX(m_FileContents->GetFileName(),
456 ePTB_FileNotFound, "Include file not found: " << file_path);
457 return;
458 }
459 StartParse();
460 string strline;
461 while ( NcbiGetlineEOL(ifs, strline) ) {
462 AcceptLine(strline);
463 }
464 EndParse();
465 StartParse();
466 }
467
EndParse(void)468 void CSimpleMakeFileContents::SParser::EndParse(void)
469 {
470 m_FileContents->AddReadyKV(m_CurrentKV);
471 m_Continue = false;
472 m_CurrentKV = SKeyValue();
473 }
474
475
AddReadyKV(const SKeyValue & kv)476 void CSimpleMakeFileContents::AddReadyKV(const SKeyValue& kv)
477 {
478 if ( kv.m_Key.empty() )
479 return;
480
481 if (kv.m_Key == "CHECK_CMD") {
482 m_Contents[kv.m_Key].push_back( kv.m_Value);
483 } else {
484 list<string> values;
485 // if (NStr::EndsWith(kv.m_Key,"LIBS")) {
486 if (NStr::FindCase(kv.m_Key,"LIB") != NPOS ||
487 NStr::FindCase(kv.m_Value," -l") != NPOS ||
488 NStr::FindCase(kv.m_Value,"-rpath") != NPOS ||
489 NStr::FindCase(kv.m_Value,"-framework") != NPOS) {
490 NStr::Split(kv.m_Value, LIST_SEPARATOR_LIBS, values, NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
491 } else {
492 if (m_ValueSeparator.empty()) {
493 m_ValueSeparator = LIST_SEPARATOR;
494 }
495 NStr::Split(kv.m_Value, m_ValueSeparator, values, NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
496 }
497 // change '{' into '(', because I rely on that in many places
498 NON_CONST_ITERATE(list<string>, v, values) {
499 string::size_type start, end;
500 while ((start = v->find("${")) != string::npos) {
501 v->replace(start+1, 1, 1, '(');
502 }
503 while ((end = v->find("}")) != string::npos) {
504 v->replace( end, 1, 1, ')');
505 }
506 }
507 list<string>& dest = m_Contents[kv.m_Key];
508 if (!kv.m_Append) {
509 dest.clear();
510 }
511 string value;
512 size_t start_count=0, end_count=0;
513 ITERATE(list<string>, v, values) {
514 string::size_type start, end;
515 if (!value.empty()) {
516 value += ' ';
517 }
518 value += *v;
519 for (start=0; (start = v->find("$(", start)) != string::npos; ++start)
520 ++start_count;
521 for (end=0; (end = v->find(")", end)) != string::npos; ++end)
522 ++end_count;
523 if (start_count == end_count) {
524 // very primitive GNU make built-in expansion functions
525 if (NStr::StartsWith(value, "$(addsuffix")) {
526 string first, second;
527 string func, arg;
528 NStr::SplitInTwo(CSymResolver::StripDefine(value), ",", first, second);
529 NStr::SplitInTwo(first, " ", func, arg);
530 list<string> tmp;
531 NStr::Split(second, " ", tmp, NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
532 ITERATE(list<string>, t, tmp) {
533 dest.push_back(*t+arg);
534 }
535 } else {
536 if (kv.m_Key == "SRC" && CSymResolver::IsDefine(value)) {
537 value = FilterDefine(value);
538 }
539 dest.push_back(value);
540 }
541 start_count = end_count = 0;
542 value.clear();
543 }
544 }
545 }
546 }
547
548
549 END_NCBI_SCOPE
550