1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 #include <sngcm/ast/Project.hpp>
  7 #include <sngcm/ast/Solution.hpp>
  8 #include <cmajor/cmtoolchain/ToolChains.hpp>
  9 #include <soulng/util/Path.hpp>
 10 #include <soulng/util/Unicode.hpp>
 11 #include <soulng/util/Sha1.hpp>
 12 #include <soulng/util/CodeFormatter.hpp>
 13 #include <soulng/util/TextUtils.hpp>
 14 #include <boost/filesystem.hpp>
 15 #include <iostream>
 16 
 17 namespace sngcm { namespace ast {
 18 
 19 using namespace soulng::util;
 20 using namespace soulng::unicode;
 21 
 22 ModuleVersionTagVerifier* moduleVersionTagVerifier = nullptr;
 23 
 24 void SetModuleVersionTagVerifier(ModuleVersionTagVerifier* verifier)
 25 {
 26     moduleVersionTagVerifier = verifier;
 27 }
 28 
 29 std::string TargetStr(Target target)
 30 {
 31     switch (target)
 32     {
 33         case Target::program: return "program";
 34         case Target::library: return "library";
 35         case Target::winapp:  return "winapp";
 36         case Target::winlib: return "winlib";
 37         case Target::winguiapp: return "winguiapp";
 38         case Target::unitTest: return "unitTest";
 39     }
 40     return "library";
 41 }
 42 
 43 Target ParseTarget(const std::string& targetStr)
 44 {
 45     if (targetStr == "program")
 46     {
 47         return Target::program;
 48     }
 49     else if (targetStr == "library")
 50     {
 51         return Target::library;
 52     }
 53     else if (targetStr == "winapp")
 54     {
 55         return Target::winapp;
 56     }
 57     else if (targetStr == "winlib")
 58     {
 59         return Target::winlib;
 60     }
 61     else if (targetStr == "winguiapp")
 62     {
 63         return Target::winguiapp;
 64     }
 65     else if (targetStr == "unitTest")
 66     {
 67         return Target::unitTest;
 68     }
 69     return Target::program;
 70 }
 71 
 72 std::string CmajorRootDir()
 73 {
 74     char* e = getenv("CMAJOR_ROOT");
 75     if (e == nullptr || !*e)
 76     {
 77         throw std::runtime_error("please set 'CMAJOR_ROOT' environment variable to contain /path/to/cmajor directory.");
 78     }
 79     return std::string(e);
 80 }
 81 
 82 std::string CmajorSystemLibDir(const std::string& configBackEnd backendconst std::string& toolChainSystemDirKind systemDirKind)
 83 {
 84     if (backend == BackEnd::llvm)
 85     {
 86         boost::filesystem::path sld(CmajorRootDir());
 87         if (systemDirKind == SystemDirKind::repository)
 88         {
 89             sld /= "repository";
 90         }
 91         sld /= "system";
 92         sld /= "lib";
 93         sld /= config;
 94         return GetFullPath(sld.generic_string());
 95     }
 96     else if (backend == BackEnd::cmsx)
 97     {
 98         boost::filesystem::path sld(CmajorRootDir());
 99         sld /= "projects";
100         sld /= "cmsx";
101         sld /= "system";
102         sld /= "lib";
103         sld /= config;
104         return GetFullPath(sld.generic_string());
105     }
106     else if (backend == BackEnd::cppcm)
107     {
108         boost::filesystem::path sld(CmajorRootDir());
109         if (systemDirKind == SystemDirKind::repository)
110         {
111             sld /= "repository";
112         }
113         sld /= "system";
114         sld /= "lib";
115         sld /= "cpp";
116         sld /= toolChain;
117         sld /= config;
118         return GetFullPath(sld.generic_string());
119     }
120     else
121     {
122         return std::string();
123     }
124 }
125 
126 std::string CmajorResourceDir()
127 {
128     boost::filesystem::path rd(CmajorRootDir());
129     rd /= "res";
130     return GetFullPath(rd.generic_string());
131 }
132 
133 std::string CmajorLogFileDir()
134 {
135     std::string cmajorRooDir = CmajorRootDir();
136     std::string logFileDir = Path::Combine(cmajorRooDir"log");
137     boost::filesystem::create_directories(logFileDir);
138     return logFileDir;
139 }
140 
141 std::string outDir;
142 
143 void SetOutDir(const std::string& outDir_)
144 {
145     outDir = outDir_;
146 }
147 
148 const std::string& OutDir()
149 {
150     return outDir;
151 }
152 
153 std::string CmajorSystemModuleFilePath(const std::string& configBackEnd backendconst std::string& toolChainSystemDirKind systemDirKind)
154 {
155     boost::filesystem::path smfp(CmajorSystemLibDir(configbackendtoolChainsystemDirKind));
156     smfp /= "System.cmm";
157     return GetFullPath(smfp.generic_string());
158 }
159 
160 std::string CmajorSystemWindowsModuleFilePath(const std::string& configconst std::string& toolChainSystemDirKind systemDirKind)
161 {
162     boost::filesystem::path smfp(CmajorSystemLibDir(configBackEnd::llvmtoolChainsystemDirKind));
163     smfp /= "System.Windows.cmm";
164     return GetFullPath(smfp.generic_string());
165 }
166 
167 std::string MakeCmajorRootRelativeFilePath(const std::string& filePath)
168 {
169     std::string cmajorRootDir = GetFullPath(CmajorRootDir());
170     std::string fullPath = GetFullPath(filePath);
171     if (StartsWith(fullPathcmajorRootDir))
172     {
173         return "$CMAJOR_ROOT$" + fullPath.substr(cmajorRootDir.length());
174     }
175     else
176     {
177         return fullPath;
178     }
179 }
180 
181 std::string ExpandCmajorRootRelativeFilePath(const std::string& filePath)
182 {
183     if (StartsWith(filePath"$CMAJOR_ROOT$"))
184     {
185         std::string cmajorRootDir = GetFullPath(CmajorRootDir());
186         return cmajorRootDir + filePath.substr(std::string("$CMAJOR_ROOT$").length());
187     }
188     else
189     {
190         return GetFullPath(filePath);
191     }
192 }
193 
194 std::std::vector<Project*>GetReferencedProjects(Project*projectSolution*solution)
195 {
196     std::vector<Project*> referencedProjects;
197     for (const std::string& referencedProjectFilePath : project->ReferencedProjectFilePaths())
198     {
199         std::string rpfp = GetFullPath(referencedProjectFilePath);
200         int n = solution->Projects().size();
201         bool found = false;
202         int i = 0;
203         while (i < n && !found)
204         {
205             Project* solutionProject = solution->Projects()[i].get();
206             std::string fp = GetFullPath(solutionProject->FilePath());
207             if (fp == rpfp)
208             {
209                 referencedProjects.push_back(solutionProject);
210                 found = true;
211             }
212             ++i;
213         }
214     }
215     return referencedProjects;
216 }
217 
218 std::std::set<Project*>GetAllReferencedProjects(Project*projectSolution*solution)
219 {
220     std::set<Project*> allReferencedProjects;
221     AddReferencedProjects(allReferencedProjectsprojectsolution);
222     return allReferencedProjects;
223 }
224 
225 void AddReferencedProjects(std::std::set<Project*>&allReferencedProjectsProject*projectSolution*solution)
226 {
227     std::vector<Project*> referencedProjects = GetReferencedProjects(projectsolution);
228     for (Project* referencedProject : referencedProjects)
229     {
230         if (allReferencedProjects.find(referencedProject) == allReferencedProjects.cend())
231         {
232             allReferencedProjects.insert(referencedProject);
233             AddReferencedProjects(allReferencedProjectsreferencedProjectsolution);
234         }
235     }
236 }
237 
238 ProjectDeclaration::ProjectDeclaration(ProjectDeclarationType declarationType_) : declarationType(declarationType_)
239 {
240 }
241 
242 ProjectDeclaration::~ProjectDeclaration()
243 {
244 }
245 
246 ReferenceDeclaration::ReferenceDeclaration(const std::string& filePath_) : ProjectDeclaration(ProjectDeclarationType::referenceDeclaration)filePath(filePath_)
247 {
248 }
249 
250 void ReferenceDeclaration::Write(CodeFormatter& formatter)
251 {
252     formatter.WriteLine("reference <" + filePath + ">;");
253 }
254 
255 SourceFileDeclaration::SourceFileDeclaration(const std::string& filePath_) : ProjectDeclaration(ProjectDeclarationType::sourceFileDeclaration)filePath(filePath_)
256 {
257 }
258 
259 void SourceFileDeclaration::Write(CodeFormatter& formatter)
260 {
261     formatter.WriteLine("source <" + filePath + ">;");
262 }
263 
264 ResourceFileDeclaration::ResourceFileDeclaration(const std::string& filePath_) : ProjectDeclaration(ProjectDeclarationType::resourceFileDeclaration)filePath(filePath_)
265 {
266 }
267 
268 void ResourceFileDeclaration::Write(CodeFormatter& formatter)
269 {
270     formatter.WriteLine("resource <" + filePath + ">;");
271 }
272 
273 TextFileDeclaration::TextFileDeclaration(const std::string& filePath_) : ProjectDeclaration(ProjectDeclarationType::textFileDeclaration)filePath(filePath_)
274 {
275 }
276 
277 void TextFileDeclaration::Write(CodeFormatter& formatter)
278 {
279     formatter.WriteLine("text <" + filePath + ">;");
280 }
281 
282 TargetDeclaration::TargetDeclaration(Target target_) : ProjectDeclaration(ProjectDeclarationType::targetDeclaration)target(target_)
283 {
284 }
285 
286 void TargetDeclaration::Write(CodeFormatter& formatter)
287 {
288     formatter.WriteLine("target=" + TargetStr(target) + ";");
289 }
290 
291 Project::Project(const std::u32string& name_const std::string& filePath_const std::string& config_BackEnd backend_const std::string& toolChain_
292     SystemDirKind systemDirKind) :
293     backend(backend_)name(name_)filePath(filePath_)config(config_)target(Target::program)sourceBasePath(filePath)outdirBasePath(filePath)
294     isSystemProject(false)logStreamId(0)built(false)toolChain(toolChain_)
295 {
296     std::string platform = GetPlatform();
297     if (!outDir.empty())
298     {
299         sourceBasePath.remove_filename();
300         outdirBasePath = outDir;
301         outdirBasePath /= ToUtf8(name);
302     }
303     else
304     {
305         sourceBasePath.remove_filename();
306         outdirBasePath = sourceBasePath;
307     }
308     systemLibDir = CmajorSystemLibDir(configbackendtoolChainsystemDirKind);
309     boost::filesystem::path mfp(filePath);
310     boost::filesystem::path fn = mfp.filename();
311     mfp.remove_filename();
312     if (!outDir.empty())
313     {
314         mfp = outdirBasePath;
315     }
316     mfp /= "lib";
317     if (backend == BackEnd::cppcm)
318     {
319         mfp /= "cpp";
320         mfp /= toolChain;
321     }
322     mfp /= config;
323     mfp /= fn;
324     mfp.replace_extension(".cmm");
325     moduleFilePath = GetFullPath(mfp.generic_string());
326     boost::filesystem::path lfp(mfp);
327 #ifdef _WIN32
328 
329 
330 
331 
332 
333 
334 
335 
336 
337 
338 
339 
340 
341 #else
342     if (backend == BackEnd::cppcm)
343     {
344         const Tool& libraryManagerTool = GetLibraryManagerTool(platformtoolChain);
345         lfp.replace_extension(libraryManagerTool.outputFileExtension);
346     }
347     else
348     {
349         lfp.replace_extension(".a");
350     }
351 #endif
352     libraryFilePath = GetFullPath(lfp.generic_string());
353     boost::filesystem::path efp(filePath);
354     efp.remove_filename();
355     if (!outDir.empty())
356     {
357         efp = outdirBasePath;
358     }
359     efp /= "bin";
360     if (backend == BackEnd::cppcm)
361     {
362         efp /= "cpp";
363         efp /= toolChain;
364     }
365     efp /= config;
366     efp /= fn;
367 #ifdef _WIN32
368 
369 
370 
371 
372 
373 
374 
375 
376 
377 
378 
379 
380 
381 #else
382     if (backend == BackEnd::cppcm)
383     {
384         const Tool& linkerTool = GetLinkerTool(platformtoolChain);
385         efp.replace_extension(linkerTool.outputFileExtension);
386     }
387     else
388     {
389         efp.replace_extension();
390     }
391 #endif
392     executableFilePath = GetFullPath(efp.generic_string());
393 }
394 
395 void Project::AddDeclaration(ProjectDeclaration* declaration)
396 {
397     declarations.push_back(std::unique_ptr<ProjectDeclaration>(declaration));
398 }
399 
400 void Project::ResolveDeclarations()
401 {
402     for (const std::std::unique_ptr<ProjectDeclaration>&declaration : declarations)
403     {
404         switch (declaration->GetDeclarationType())
405         {
406             case ProjectDeclarationType::referenceDeclaration:
407             {
408                 ReferenceDeclaration* reference = static_cast<ReferenceDeclaration*>(declaration.get());
409                 boost::filesystem::path rp(reference->FilePath());
410                 relativeReferencedProjectFilePaths.push_back(rp.generic_string());
411                 if (rp.is_absolute())
412                 {
413                     referencedProjectFilePaths.push_back(GetFullPath(rp.generic_string()));
414                 }
415                 else
416                 {
417                     boost::filesystem::path ar = sourceBasePath / rp;
418                     referencedProjectFilePaths.push_back(GetFullPath(ar.generic_string()));
419                 }
420                 boost::filesystem::path fn = rp.filename();
421                 rp.remove_filename();
422                 if (rp.is_relative())
423                 {
424                     rp = systemLibDir / rp;
425                 }
426                 rp /= fn;
427                 if (rp.extension() == ".cmp" || rp.extension() == ".cmproj")
428                 {
429                     rp.replace_extension(".cmm");
430                 }
431                 if (rp.extension() != ".cmm")
432                 {
433                     throw std::runtime_error("invalid reference path extension '" + rp.generic_string() + "' (not .cmp, .cmproj or .cmm)");
434                 }
435                 if (!boost::filesystem::exists(rp))
436                 {
437                     rp = reference->FilePath();
438                     rp.remove_filename();
439                     if (rp.is_relative())
440                     {
441                         rp = outdirBasePath / rp;
442                     }
443                     rp /= "lib";
444                     if (backend == BackEnd::cppcm)
445                     {
446                         rp /= "cpp";
447                         rp /= toolChain;
448                     }
449                     rp /= config;
450                     rp /= fn;
451                     if (rp.extension() == ".cmp" || rp.extension() == ".cmproj")
452                     {
453                         rp.replace_extension(".cmm");
454                     }
455                     if (rp.extension() != ".cmm")
456                     {
457                         throw std::runtime_error("invalid reference path extension '" + rp.generic_string() + "' (not .cmp, .cmproj or .cmm)");
458                     }
459                 }
460                 std::string referencePath = GetFullPath(rp.generic_string());
461                 if (std::find(references.cbegin()references.cend()referencePath) == references.cend())
462                 {
463                     references.push_back(referencePath);
464                 }
465                 break;
466             }
467             case ProjectDeclarationType::sourceFileDeclaration:
468             {
469                 SourceFileDeclaration* sourceFileDeclaration = static_cast<SourceFileDeclaration*>(declaration.get());
470                 boost::filesystem::path sfp(sourceFileDeclaration->FilePath());
471                 relativeSourceFilePaths.push_back(sfp.generic_string());
472                 if (sfp.is_relative())
473                 {
474                     sfp = sourceBasePath / sfp;
475                 }
476                 if (sfp.extension() != ".cm")
477                 {
478                     throw std::runtime_error("invalid source file extension '" + sfp.generic_string() + "' (not .cm)");
479                 }
480                 if (!boost::filesystem::exists(sfp))
481                 {
482                     throw std::runtime_error("source file path '" + GetFullPath(sfp.generic_string()) + "' not found");
483                 }
484                 std::string sourceFilePath = GetFullPath(sfp.generic_string());
485                 if (std::find(sourceFilePaths.cbegin()sourceFilePaths.cend()sourceFilePath) == sourceFilePaths.cend() && sourceFilePath != excludeSourceFilePath)
486                 {
487                     sourceFilePaths.push_back(sourceFilePath);
488                 }
489                 break;
490             }
491             case ProjectDeclarationType::resourceFileDeclaration:
492             {
493                 ResourceFileDeclaration* resourceFileDeclaration = static_cast<ResourceFileDeclaration*>(declaration.get());
494                 boost::filesystem::path rfp(resourceFileDeclaration->FilePath());
495                 relativeResourceFilePaths.push_back(rfp.generic_string());
496                 if (rfp.is_relative())
497                 {
498                     rfp = sourceBasePath / rfp;
499                 }
500                 if (rfp.extension() != ".xml")
501                 {
502                     throw std::runtime_error("invalid resource file extension '" + rfp.generic_string() + "' (not .xml)");
503                 }
504                 if (!boost::filesystem::exists(rfp))
505                 {
506                     throw std::runtime_error("resource file path '" + GetFullPath(rfp.generic_string()) + "' not found");
507                 }
508                 std::string resourceFilePath = GetFullPath(rfp.generic_string());
509                 if (std::find(resourceFilePaths.cbegin()resourceFilePaths.cend()resourceFilePath) == resourceFilePaths.cend())
510                 {
511                     resourceFilePaths.push_back(resourceFilePath);
512                 }
513                 break;
514             }
515             case ProjectDeclarationType::targetDeclaration:
516             {
517                 TargetDeclaration* targetDeclaration = static_cast<TargetDeclaration*>(declaration.get());
518                 target = targetDeclaration->GetTarget();
519                 break;
520             }
521             case ProjectDeclarationType::textFileDeclaration:
522             {
523                 TextFileDeclaration* textFileDeclaration = static_cast<TextFileDeclaration*>(declaration.get());
524                 boost::filesystem::path tfp(textFileDeclaration->FilePath());
525                 relativeTextFilePaths.push_back(tfp.generic_string());
526                 if (tfp.is_relative())
527                 {
528                     tfp = sourceBasePath / tfp;
529                 }
530                 std::string textFilePath = GetFullPath(tfp.generic_string());
531                 textFilePaths.push_back(textFilePath);
532                 break;
533             }
534             default:
535             {
536                 throw std::runtime_error("unknown project declaration");
537             }
538         }
539     }
540 }
541 
542 void Project::Write(const std::string& projectFilePath)
543 {
544     std::ofstream projectFile(projectFilePath);
545     CodeFormatter formatter(projectFile);
546     formatter.WriteLine("project " + ToUtf8(Name()) + ";");
547     for (const std::std::unique_ptr<ProjectDeclaration>&declaration : declarations)
548     {
549         declaration->Write(formatter);
550     }
551 }
552 
553 bool Project::DependsOn(Project* that) const
554 {
555     return std::find(references.cbegin()references.cend()that->moduleFilePath) != references.cend();
556 }
557 
558 void Project::AddDependsOnProjects(Project* dependsOnProject)
559 {
560     dependsOn.push_back(dependsOnProject);
561 }
562 
563 void Project::SetModuleFilePath(const std::string& moduleFilePath_)
564 {
565     moduleFilePath = moduleFilePath_;
566 }
567 
568 void Project::SetLibraryFilePath(const std::string& libraryFilePath_)
569 {
570     libraryFilePath = libraryFilePath_;
571 }
572 
573 void Project::SetReferencedProjects(const std::std::vector<Project*>&referencedProjects)
574 {
575     referencedProjectFilePaths.clear();
576     relativeReferencedProjectFilePaths.clear();
577     std::string absoluteProjectDirPath = GetFullPath(sourceBasePath.generic_string());
578     for (Project* referencedProject : referencedProjects)
579     {
580         referencedProjectFilePaths.push_back(referencedProject->FilePath());
581         std::string absoluteReferenceDirPath = GetFullPath(Path::GetDirectoryName(referencedProject->FilePath()));
582         std::string referenceProjectDir = MakeRelativeDirPath(absoluteReferenceDirPathabsoluteProjectDirPath);
583         relativeReferencedProjectFilePaths.push_back(Path::Combine(referenceProjectDirPath::GetFileName(referencedProject->FilePath())));
584     }
585 }
586 
587 bool Project::IsUpToDate(const std::string& systemModuleFilePath) const
588 {
589     if (!boost::filesystem::exists(moduleFilePath))
590     {
591         return false;
592     }
593     for (const std::string& sourceFilePath : sourceFilePaths)
594     {
595         if (boost::filesystem::last_write_time(sourceFilePath) > boost::filesystem::last_write_time(moduleFilePath))
596         {
597             return false;
598         }
599     }
600     for (const std::string& resourceFilePath : resourceFilePaths)
601     {
602         if (boost::filesystem::last_write_time(resourceFilePath) > boost::filesystem::last_write_time(moduleFilePath))
603         {
604             return false;
605         }
606     }
607     for (const std::string& referenceFilePath : references)
608     {
609         if (boost::filesystem::last_write_time(referenceFilePath) > boost::filesystem::last_write_time(moduleFilePath))
610         {
611             return false;
612         }
613     }
614     if (!systemModuleFilePath.empty() && !IsSystemProject() && boost::filesystem::exists(systemModuleFilePath))
615     {
616         if (boost::filesystem::last_write_time(systemModuleFilePath) > boost::filesystem::last_write_time(moduleFilePath))
617         {
618             return false;
619         }
620     }
621     return true;
622 }
623 
624 bool Project::Built()
625 {
626     return built;
627 }
628 
629 void Project::SetBuilt()
630 {
631     built = true;
632 }
633 
634 bool Project::Ready()
635 {
636     for (Project* dependOn : dependsOn)
637     {
638         if (!dependOn->Built())
639         {
640             return false;
641         }
642     }
643     return true;
644 }
645 
646 void Project::SetExcludeSourceFilePath(const std::string& excludeSourceFilePath_)
647 {
648     excludeSourceFilePath = excludeSourceFilePath_;
649 }
650 
651 std::string Project::Id() const
652 {
653     std::string id = "project_";
654     id.append(ToUtf8(name)).append(1'_').append(GetSha1MessageDigest(filePath));
655     return id;
656 }
657 
658 void Project::AddDependsOnId(const std::string& dependsOnId)
659 {
660     dependsOnIds.push_back(dependsOnId);
661 }
662 
663 bool Project::HasSourceFile(const std::string& sourceFilePath) const
664 {
665     for (const auto& filePath : sourceFilePaths)
666     {
667         if (filePath == sourceFilePath)
668         {
669             return true;
670         }
671     }
672     return false;
673 }
674 
675 bool Project::HasResourceFile(const std::string& resourceFilePath) const
676 {
677     for (const auto& filePath : resourceFilePaths)
678     {
679         if (filePath == resourceFilePath)
680         {
681             return true;
682         }
683     }
684     return false;
685 }
686 
687 bool Project::HasTextFile(const std::string& textFilePath) const
688 {
689     for (const auto& filePath : textFilePaths)
690     {
691         if (filePath == textFilePath)
692         {
693             return true;
694         }
695     }
696     return false;
697 }
698 
699 void Project::AddSourceFileName(const std::string& sourceFileNameconst std::string& sourceFilePath)
700 {
701     relativeSourceFilePaths.push_back(sourceFileName);
702     std::sort(relativeSourceFilePaths.begin()relativeSourceFilePaths.end());
703     sourceFilePaths.push_back(sourceFilePath);
704     std::sort(sourceFilePaths.begin()sourceFilePaths.end());
705 }
706 
707 void Project::AddResourceFileName(const std::string& resourceFileNameconst std::string& resourceFilePath)
708 {
709     relativeResourceFilePaths.push_back(resourceFileName);
710     std::sort(relativeResourceFilePaths.begin()relativeResourceFilePaths.end());
711     resourceFilePaths.push_back(resourceFilePath);
712     std::sort(resourceFilePaths.begin()resourceFilePaths.end());
713 }
714 
715 void Project::AddTextFileName(const std::string& textFileNameconst std::string& textFilePath)
716 {
717     relativeTextFilePaths.push_back(textFileName);
718     std::sort(relativeTextFilePaths.begin()relativeTextFilePaths.end());
719     textFilePaths.push_back(textFilePath);
720     std::sort(textFilePaths.begin()textFilePaths.end());
721 }
722 
723 void Project::RemoveFile(const std::string& filePathconst std::string& fileName)
724 {
725     relativeSourceFilePaths.erase(std::remove(relativeSourceFilePaths.begin()relativeSourceFilePaths.end()fileName)relativeSourceFilePaths.end());
726     sourceFilePaths.erase(std::remove(sourceFilePaths.begin()sourceFilePaths.end()filePath)sourceFilePaths.end());
727     relativeResourceFilePaths.erase(std::remove(relativeResourceFilePaths.begin()relativeResourceFilePaths.end()fileName)relativeResourceFilePaths.end());
728     resourceFilePaths.erase(std::remove(resourceFilePaths.begin()resourceFilePaths.end()filePath)resourceFilePaths.end());
729     relativeTextFilePaths.erase(std::remove(relativeTextFilePaths.begin()relativeTextFilePaths.end()fileName)relativeTextFilePaths.end());
730     textFilePaths.erase(std::remove(textFilePaths.begin()textFilePaths.end()filePath)textFilePaths.end());
731 }
732 
733 void Project::Save()
734 {
735     std::ofstream file(filePath);
736     CodeFormatter formatter(file);
737     formatter.WriteLine("project " + ToUtf8(name) + ";");
738     formatter.WriteLine("target=" + TargetStr(target) + ";");
739     for (const std::string& relativeReferenceFilePath : relativeReferencedProjectFilePaths)
740     {
741         formatter.WriteLine("reference <" + relativeReferenceFilePath + ">;");
742     }
743     for (const std::string& relativeSourceFilePath : relativeSourceFilePaths)
744     {
745         formatter.WriteLine("source <" + relativeSourceFilePath + ">;");
746     }
747     for (const std::string& relativeResourceFilePath : relativeResourceFilePaths)
748     {
749         formatter.WriteLine("resource <" + relativeResourceFilePath + ">;");
750     }
751     for (const std::string& relativeTextFilePath : relativeTextFilePaths)
752     {
753         formatter.WriteLine("text <" + relativeTextFilePath + ">;");
754     }
755 }
756 
757 } } // namespace sngcm::ast