| 1 | // tRule.cpp  |
| 2 | //  |
| 3 | // The base class for a rule. Rules support functionality such as setting targets/dependencies, and checking if the  |
| 4 | // build rule is out of date.  |
| 5 | //  |
| 6 | // Copyright (c) 2006, 2017, 2023 Tristan Grimmer.  |
| 7 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
| 8 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
| 9 | //  |
| 10 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
| 11 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
| 12 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
| 13 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
| 14 | // PERFORMANCE OF THIS SOFTWARE.  |
| 15 |   |
| 16 | #include <System/tThrow.h>  |
| 17 | #include <System/tPrint.h>  |
| 18 | #include <System/tFile.h>  |
| 19 | #include "Pipeline/tRule.h"  |
| 20 | #ifdef PLATFORM_WINDOWS  |
| 21 | #include "Pipeline/tSolution.h"  |
| 22 | #endif  |
| 23 | using namespace tPipeline;  |
| 24 |   |
| 25 |   |
| 26 | tRuleError::tRuleError(const char* format, ...) :  |
| 27 | tError("[tRule] " )  |
| 28 | {  |
| 29 | va_list marker;  |
| 30 | va_start(marker, format);  |
| 31 | Message += tsvPrintf(dest&: Message, format, marker);  |
| 32 | }  |
| 33 |   |
| 34 |   |
| 35 | void tRule::SetTarget(const tString& target)  |
| 36 | {  |
| 37 | while (tStringItem* s = Dependencies.Remove())  |
| 38 | delete s;  |
| 39 |   |
| 40 | Target = target;  |
| 41 | }  |
| 42 |   |
| 43 |   |
| 44 | bool tRule::MaybeAddToDependenciesCaseInsensitive(const tString& dep)  |
| 45 | {  |
| 46 | // This function only adds to the dependency list if the file in question hasn't already been added.  |
| 47 | // @todo Does not support changing between relative and absolute.  |
| 48 | if (dep.IsEmpty())  |
| 49 | return false;  |
| 50 |   |
| 51 | tString lowCase = dep;  |
| 52 | lowCase.Replace(search: '\\', replace: '/');  |
| 53 | lowCase.ToLower();  |
| 54 |   |
| 55 | for (tStringItem* s = Dependencies.First(); s; s = s->Next())  |
| 56 | {  |
| 57 | tString lowCasePresent = *s;  |
| 58 | lowCasePresent.Replace(search: '\\', replace: '/');  |
| 59 | lowCasePresent.ToLower();  |
| 60 |   |
| 61 | if (lowCase == lowCasePresent)  |
| 62 | return false;  |
| 63 | }  |
| 64 |   |
| 65 | // If we get here the file hasn't already been added... so we add it now.  |
| 66 | Dependencies.Append(item: new tStringItem(dep));  |
| 67 | return true;  |
| 68 | }  |
| 69 |   |
| 70 |   |
| 71 | void tRule::AddDependency(const tString& dep)  |
| 72 | {  |
| 73 | if (!tSystem::tFileExists(file: dep))  |
| 74 | throw tRuleError("Cannot add dependency [%s]" , dep.Pod());  |
| 75 |   |
| 76 | MaybeAddToDependenciesCaseInsensitive(dep);  |
| 77 | }  |
| 78 |   |
| 79 |   |
| 80 |   |
| 81 | void tRule::AddDependency(tStringItem* dep)  |
| 82 | {  |
| 83 | if (!tSystem::tFileExists(file: *dep))  |
| 84 | throw tRuleError("Cannot add dependency [%s]" , dep->Pod());  |
| 85 |   |
| 86 | MaybeAddToDependenciesCaseInsensitive(dep: *dep);  |
| 87 | }  |
| 88 |   |
| 89 |   |
| 90 | void tRule::AddDependencies(tList<tStringItem>& deps)  |
| 91 | {  |
| 92 | bool success = true;  |
| 93 |   |
| 94 | tString badDependency;  |
| 95 | while (tStringItem* dep = deps.Remove())  |
| 96 | {  |
| 97 | if (!tSystem::tFileExists(file: *dep))  |
| 98 | {  |
| 99 | success = false;  |
| 100 | badDependency = *dep;  |
| 101 | delete dep;  |
| 102 | }  |
| 103 | else  |
| 104 | {  |
| 105 | MaybeAddToDependenciesCaseInsensitive(dep: *dep);  |
| 106 | }  |
| 107 | }  |
| 108 |   |
| 109 | if (!success)  |
| 110 | throw tRuleError("Cannot add dependency [%s]" , badDependency.Chr());  |
| 111 | }  |
| 112 |   |
| 113 |   |
| 114 | void tRule::AddDependencyDir(const tString& dir, const tString& ext)  |
| 115 | {  |
| 116 | tList<tStringItem> deps;  |
| 117 | bool includeHidden = false;  |
| 118 | tSystem::tFindFiles(files&: deps, dir, ext, hidden: includeHidden);  |
| 119 |   |
| 120 | AddDependencies(deps);  |
| 121 | tAssert(deps.IsEmpty());  |
| 122 | }  |
| 123 |   |
| 124 |   |
| 125 | void tRule::AddDependencyDirRec(const tString& dir, const tString& ext)  |
| 126 | {  |
| 127 | tList<tStringItem> deps;  |
| 128 | bool includeHidden = false;  |
| 129 | tSystem::tFindFilesRec(files&: deps, dir, ext, hidden: includeHidden);  |
| 130 |   |
| 131 | AddDependencies(deps);  |
| 132 | tAssert(deps.IsEmpty());  |
| 133 | }  |
| 134 |   |
| 135 |   |
| 136 | bool tRule::OutOfDate(bool checkClean)  |
| 137 | {  |
| 138 | // Returns true if target has been specified and target doesn't exist or is older than any dependency.  |
| 139 | // Also returns false if any dep doesn't exist.  |
| 140 | if (Target.IsEmpty())  |
| 141 | return false;  |
| 142 |   |
| 143 | tStringItem* dep = Dependencies.First();  |
| 144 | while (dep)  |
| 145 | {  |
| 146 | if (!tSystem::tFileExists(file: *dep))  |
| 147 | throw tRuleError("Cannot find dependency [%s] while targetting [%s]." , dep->Chr(), Target.Chr());  |
| 148 |   |
| 149 | dep = dep->Next();  |
| 150 | }  |
| 151 |   |
| 152 | if (!tSystem::tFileExists(file: Target))  |
| 153 | return true;  |
| 154 |   |
| 155 | if (checkClean && Clean)  |
| 156 | return true;  |
| 157 |   |
| 158 | dep = Dependencies.First();  |
| 159 | while (dep)  |
| 160 | {  |
| 161 | if (tSystem::tIsFileNewer(fileA: *dep, fileB: Target))  |
| 162 | return true;  |
| 163 |   |
| 164 | dep = dep->Next();  |
| 165 | }  |
| 166 |   |
| 167 | return false;  |
| 168 | }  |
| 169 | |