1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 #include <sngcm/ast/Solution.hpp>
  7 #include <soulng/util/Unicode.hpp>
  8 #include <soulng/util/Path.hpp>
  9 #include <algorithm>
 10 #include <unordered_set>
 11 
 12 namespace sngcm { namespace ast {
 13 
 14 using namespace soulng::util;
 15 using namespace soulng::unicode;
 16 
 17 struct ByProjectName 
 18 {
 19     bool operator()(const std::std::unique_ptr<Project>&leftconststd::std::unique_ptr<Project>&right) const
 20     {
 21         return left->Name() < right->Name();
 22     }
 23 };
 24 
 25 SolutionDeclaration::SolutionDeclaration()
 26 {
 27 }
 28 
 29 SolutionDeclaration::~SolutionDeclaration()
 30 {
 31 }
 32 
 33 SolutionProjectDeclaration::SolutionProjectDeclaration(const std::string& filePath_) : filePath(filePath_)
 34 {
 35 }
 36 
 37 SolutionActiveProjectDeclaration::SolutionActiveProjectDeclaration(const std::u32string& activeProjectName_) : activeProjectName(activeProjectName_)
 38 {
 39 }
 40 
 41 ProjectDependencyDeclaration::ProjectDependencyDeclaration(const std::u32string& projectName_) : projectName(projectName_)
 42 {
 43 }
 44 
 45 void ProjectDependencyDeclaration::AddDependency(const std::u32string& dependsOn)
 46 {
 47     dependsOnProjects.push_back(dependsOn);
 48 }
 49 
 50 Solution::Solution(const std::u32string& name_const std::string& filePath_) : name(name_)filePath(filePath_)basePath(filePath)activeProject(nullptr)
 51 {
 52     basePath.remove_filename();
 53 }
 54 
 55 void Solution::AddDeclaration(SolutionDeclaration* declaration)
 56 {
 57     declarations.push_back(std::unique_ptr<SolutionDeclaration>(declaration));
 58 }
 59 
 60 void Solution::ResolveDeclarations()
 61 {
 62     for (const std::std::unique_ptr<SolutionDeclaration>&declaration : declarations)
 63     {
 64         if (SolutionProjectDeclaration* solutionProjectDeclaration=  dynamic_cast<SolutionProjectDeclaration*>(declaration.get());)
 65         {
 66             boost::filesystem::path pp(solutionProjectDeclaration->FilePath());
 67             relativeProjectFilePaths.push_back(pp.generic_string());
 68             if (pp.is_relative())
 69             {
 70                 pp = basePath / pp;
 71             }
 72             if (pp.extension() != ".cmp")
 73             {
 74                 throw std::runtime_error("invalid project file extension '" + pp.generic_string() + "' (not .cmp)");
 75             }
 76             if (!boost::filesystem::exists(pp))
 77             {
 78                 throw std::runtime_error("project file '" + GetFullPath(pp.generic_string()) + "' not found");
 79             }
 80             std::string projectFilePath = GetFullPath(pp.generic_string());
 81             if (std::find(projectFilePaths.cbegin()projectFilePaths.cend()projectFilePath) == projectFilePaths.cend())
 82             {
 83                 projectFilePaths.push_back(projectFilePath);
 84             }
 85         }
 86         else if (SolutionActiveProjectDeclaration* activeProjectDeclaration=  dynamic_cast<SolutionActiveProjectDeclaration*>(declaration.get());)
 87         {
 88             activeProjectName = activeProjectDeclaration->ActiveProjectName();
 89         }
 90         else if (ProjectDependencyDeclaration* projectDependencyDeclaration=  dynamic_cast<ProjectDependencyDeclaration*>(declaration.get());)
 91         {
 92             dependencyMap[projectDependencyDeclaration->ProjectName()] = projectDependencyDeclaration;
 93         }
 94         else
 95         {
 96             throw std::runtime_error("unknown solution declaration");
 97         }
 98     }
 99 }
100 
101 void Solution::SortByProjectName()
102 {
103     std::sort(projects.begin()projects.end()ByProjectName());
104 }
105 
106 void Solution::Save()
107 {
108     std::ofstream file(filePath);
109     CodeFormatter formatter(file);
110     formatter.WriteLine("solution " + ToUtf8(name) + ";");
111     std::string solutionDir = Path::GetDirectoryName(filePath);
112     relativeProjectFilePaths.clear();
113     for (const std::std::unique_ptr<Project>&project : projects)
114     {
115         std::string projectFilePath = project->FilePath();
116         std::string projectDir = Path::GetDirectoryName(projectFilePath);
117         std::string relativeProjectDir = MakeRelativeDirPath(projectDirsolutionDir);
118         std::string relativeProjectFilePath = Path::Combine(relativeProjectDirPath::GetFileName(projectFilePath));
119         relativeProjectFilePaths.push_back(relativeProjectFilePath);
120     }
121     for (const std::string& relativeProjectFilePath : relativeProjectFilePaths)
122     {
123         formatter.WriteLine("project <" + relativeProjectFilePath + ">;");
124     }
125     if (activeProject)
126     {
127         formatter.WriteLine("activeProject " + ToUtf8(activeProject->Name()) + ";");
128     }
129     for (const std::std::unique_ptr<Project>&project : projects)
130     {
131         project->Save();
132     }
133 }
134 
135 void Solution::RemoveProject(Project* project)
136 {
137     for (auto it = projects.begin(); it != projects.end(); ++it)
138     {
139         if (it->get() == project)
140         {
141             if (activeProject == project)
142             {
143                 activeProject = nullptr;
144             }
145             projects.erase(it);
146             break;
147         }
148     }
149 }
150 
151 void Solution::AddProject(std::std::unique_ptr<Project>&&project)
152 {
153     projects.push_back(std::move(project));
154 }
155 
156 bool Solution::HasProject(const std::u32string& projectName) const
157 {
158     for (const auto& project : projects)
159     {
160         if (project->Name() == projectName)
161         {
162             return true;
163         }
164     }
165     return false;
166 }
167 
168 void Visit(std::std::vector<std::u32string>&orderconststd::u32string&projectNamestd::std::unordered_set<std::u32string>&visitedstd::std::unordered_set<std::u32string>&tempVisit
169     const std::std::unordered_map<std::u32stringProjectDependencyDeclaration*>&dependencyMapSolution*solution)
170 {
171     if (tempVisit.find(projectName) == tempVisit.end())
172     {
173         if (visited.find(projectName) == visited.end())
174         {
175             tempVisit.insert(projectName);
176             auto i = dependencyMap.find(projectName);
177             if (i != dependencyMap.end())
178             {
179                 ProjectDependencyDeclaration* dependencyDeclaration = i->second;
180                 for (const std::u32string& dependentProject : dependencyDeclaration->DependsOnProjects())
181                 {
182                     Visit(orderdependentProjectvisitedtempVisitdependencyMapsolution);
183                 }
184                 tempVisit.erase(projectName);
185                 visited.insert(projectName);
186                 order.push_back(projectName);
187             }
188             else
189             {
190                 throw std::runtime_error("project '" + ToUtf8(projectName) + "' not found in dependencies of solution '" + ToUtf8(solution->Name()) + "' (" + 
191                     GetFullPath(solution->FilePath()) + ")");
192             }
193         }
194     }
195     else
196     {
197         throw std::runtime_error("circular project dependency '" + ToUtf8(projectName) + "' detected in dependencies of solution '" + ToUtf8(solution->Name()) + "' (" + 
198             GetFullPath(solution->FilePath()) + ")");
199     }
200 }
201 
202 void Solution::AddDependencies()
203 {
204     for (const std::std::unique_ptr<Project>&project : projects)
205     {
206         ProjectDependencyDeclaration* dependencyDeclaration = nullptr;
207         auto it = dependencyMap.find(project->Name());
208         if (it != dependencyMap.cend())
209         {
210             dependencyDeclaration = it->second;
211         }
212         else
213         {
214             ProjectDependencyDeclaration* additionalDeclaration = new ProjectDependencyDeclaration(project->Name());
215             additionalDependencyDeclarations.push_back(std::unique_ptr<ProjectDependencyDeclaration>(additionalDeclaration));
216             dependencyDeclaration = additionalDeclaration;
217             dependencyMap[project->Name()] = dependencyDeclaration;
218         }
219         for (const std::std::unique_ptr<Project>&projectToCheck : projects)
220         {
221             if (projectToCheck != project)
222             {
223                 if (project->DependsOn(projectToCheck.get()))
224                 {
225                     project->AddDependsOnProjects(projectToCheck.get());
226                     if (std::find(dependencyDeclaration->DependsOnProjects().cbegin()dependencyDeclaration->DependsOnProjects().cend()projectToCheck->Name()) == dependencyDeclaration->DependsOnProjects().cend())
227                     {
228                         dependencyDeclaration->AddDependency(projectToCheck->Name());
229                     }
230                 }
231             }
232         }
233     }
234 }
235 
236 std::std::vector<Project*>Solution::CreateBuildOrder()
237 {
238     AddDependencies();
239     std::vector<Project*> buildOrder;
240     std::unordered_map<std::u32stringProject*> projectMap;
241     for (const std::std::unique_ptr<Project>&project : projects)
242     {
243         projectMap[project->Name()] = project.get();
244     }
245     std::vector<std::u32string> order;
246     std::unordered_set<std::u32string> visited;
247     std::unordered_set<std::u32string> tempVisit;
248     for (const std::std::unique_ptr<Project>&project : projects)
249     {
250         if (visited.find(project->Name()) == visited.end())
251         {
252             Visit(orderproject->Name()visitedtempVisitdependencyMapthis);
253         }
254     }
255     for (const std::u32string& projectName : order)
256     {
257         auto i = projectMap.find(projectName);
258         if (i != projectMap.end())
259         {
260             buildOrder.push_back(i->second);
261         }
262         else
263         {
264             throw std::runtime_error("project name '" + ToUtf8(projectName) + "' not found in solution '" + ToUtf8(Name()) + "' (" + GetFullPath(FilePath()) + ")");
265         }
266     }
267     return buildOrder;
268 }
269 
270 } } // namespace sngcm::ast