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