Line | Branch | Exec | Source |
---|---|---|---|
1 | // Copyright (c) 2021-2025 ChilliBits. All rights reserved. | ||
2 | |||
3 | #include "FileUtil.h" | ||
4 | |||
5 | #include <array> | ||
6 | #include <filesystem> | ||
7 | #include <iostream> // IWYU pragma: keep (usage in Windows-only code) | ||
8 | |||
9 | #include <driver/Driver.h> | ||
10 | #include <exception/CompilerError.h> | ||
11 | #include <exception/LinkerError.h> | ||
12 | #include <util/CommonUtil.h> | ||
13 | |||
14 | namespace spice::compiler { | ||
15 | |||
16 | /** | ||
17 | * Creates a file and writes fileContent to it. | ||
18 | * | ||
19 | * @param filePath File path | ||
20 | * @param fileContent String to write into the file | ||
21 | */ | ||
22 | ✗ | void FileUtil::writeToFile(const std::filesystem::path &filePath, const std::string &fileContent) { | |
23 | ✗ | std::ofstream file(filePath); | |
24 | ✗ | if (!file) | |
25 | ✗ | throw CompilerError(IO_ERROR, "Failed to open file: " + filePath.string()); | |
26 | ✗ | file << fileContent; | |
27 | ✗ | file.flush(); | |
28 | ✗ | file.close(); | |
29 | ✗ | } | |
30 | |||
31 | /** | ||
32 | * Retrieve the contents of a file as a string | ||
33 | * | ||
34 | * @param filePath File path | ||
35 | * @return File contents as a string | ||
36 | */ | ||
37 | 585 | std::string FileUtil::getFileContent(const std::filesystem::path &filePath) { | |
38 |
1/2✓ Branch 0 (2→3) taken 585 times.
✗ Branch 1 (2→35) not taken.
|
585 | std::ifstream file(filePath); |
39 |
2/4✓ Branch 0 (3→4) taken 585 times.
✗ Branch 1 (3→33) not taken.
✗ Branch 2 (4→5) not taken.
✓ Branch 3 (4→12) taken 585 times.
|
585 | if (!file) |
40 | ✗ | throw CompilerError(IO_ERROR, "Failed to open file: " + filePath.string()); | |
41 |
1/2✓ Branch 0 (12→13) taken 585 times.
✗ Branch 1 (12→33) not taken.
|
585 | std::stringstream stringStream; |
42 |
1/2✓ Branch 0 (14→15) taken 585 times.
✗ Branch 1 (14→31) not taken.
|
585 | stringStream << file.rdbuf(); |
43 |
1/2✓ Branch 0 (15→16) taken 585 times.
✗ Branch 1 (15→31) not taken.
|
585 | file.close(); |
44 |
1/2✓ Branch 0 (16→17) taken 585 times.
✗ Branch 1 (16→31) not taken.
|
1170 | return stringStream.str(); |
45 | 585 | } | |
46 | |||
47 | /** | ||
48 | * Retrieve the number of lines of a file | ||
49 | * | ||
50 | * @param filePath File path | ||
51 | * @return Number of lines | ||
52 | */ | ||
53 | ✗ | size_t FileUtil::getLineCount(const std::filesystem::path &filePath) { | |
54 | ✗ | std::ifstream file(filePath); | |
55 | ✗ | if (!file) | |
56 | ✗ | throw CompilerError(IO_ERROR, "Failed to open file: " + filePath.string()); | |
57 | ✗ | size_t lineCount = 0; | |
58 | ✗ | std::string line; | |
59 | ✗ | while (std::getline(file, line)) | |
60 | ✗ | lineCount++; | |
61 | ✗ | file.close(); | |
62 | ✗ | return lineCount; | |
63 | ✗ | } | |
64 | |||
65 | /** | ||
66 | * Execute external command. Used to execute compiled binaries | ||
67 | * | ||
68 | * @param command Command to execute | ||
69 | * @param redirectStdErrToStdOut Redirect StdErr to StdOut | ||
70 | * @return Result struct | ||
71 | */ | ||
72 | 380 | ExecResult FileUtil::exec(const std::string &command, bool redirectStdErrToStdOut) { | |
73 | #if OS_UNIX | ||
74 |
1/2✓ Branch 0 (2→3) taken 380 times.
✗ Branch 1 (2→41) not taken.
|
380 | std::string redirectedCommand = command; |
75 |
2/2✓ Branch 0 (3→4) taken 190 times.
✓ Branch 1 (3→5) taken 190 times.
|
380 | if (redirectStdErrToStdOut) |
76 |
1/2✓ Branch 0 (4→5) taken 190 times.
✗ Branch 1 (4→39) not taken.
|
190 | redirectedCommand += " 2>&1"; // Redirect stderr to stdout |
77 |
1/2✓ Branch 0 (6→7) taken 380 times.
✗ Branch 1 (6→39) not taken.
|
380 | FILE *pipe = popen(redirectedCommand.c_str(), "r"); |
78 | #elif OS_WINDOWS | ||
79 | std::string redirectedCommand = command; | ||
80 | if (redirectStdErrToStdOut) | ||
81 | redirectedCommand = "\"" + command + " 2>&1\""; // Redirect stderr to stdout | ||
82 | FILE *pipe = _popen(redirectedCommand.c_str(), "r"); | ||
83 | #else | ||
84 | #error "Unsupported platform" | ||
85 | #endif | ||
86 | |||
87 | − | if (!pipe) // GCOV_EXCL_LINE | |
88 | − | throw CompilerError(IO_ERROR, "Failed to execute command: " + command); // GCOV_EXCL_LINE | |
89 | |||
90 | 380 | std::array<char, 128> buffer{}; | |
91 |
1/2✓ Branch 0 (13→14) taken 380 times.
✗ Branch 1 (13→39) not taken.
|
380 | std::stringstream result; |
92 |
3/4✓ Branch 0 (22→23) taken 6775 times.
✗ Branch 1 (22→37) not taken.
✓ Branch 2 (23→15) taken 6395 times.
✓ Branch 3 (23→24) taken 380 times.
|
13930 | while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) |
93 |
1/2✓ Branch 0 (17→18) taken 6395 times.
✗ Branch 1 (17→37) not taken.
|
6395 | result << buffer.data(); |
94 | |||
95 | #if OS_UNIX | ||
96 |
1/2✓ Branch 0 (24→25) taken 380 times.
✗ Branch 1 (24→37) not taken.
|
380 | const int exitCode = pclose(pipe) / 256; |
97 | #elif OS_WINDOWS | ||
98 | const int exitCode = _pclose(pipe); | ||
99 | #else | ||
100 | #error "Unsupported platform" | ||
101 | #endif | ||
102 | 380 | return {result.str(), exitCode}; | |
103 |
1/2✓ Branch 0 (25→26) taken 380 times.
✗ Branch 1 (25→37) not taken.
|
380 | } |
104 | |||
105 | /** | ||
106 | * Checks if a certain command is available on the computer | ||
107 | * | ||
108 | * @param cmd Command to search for | ||
109 | * @return Present or not | ||
110 | */ | ||
111 | ✗ | bool FileUtil::isCommandAvailable(const std::string &cmd) { | |
112 | #if OS_UNIX | ||
113 | ✗ | const std::string checkCmd = "which " + cmd + " > /dev/null 2>&1"; | |
114 | #elif OS_WINDOWS | ||
115 | const std::string checkCmd = "where " + cmd + " > nul 2>&1"; | ||
116 | #else | ||
117 | #error "Unsupported platform" | ||
118 | #endif | ||
119 | ✗ | return std::system(checkCmd.c_str()) == 0; | |
120 | ✗ | } | |
121 | |||
122 | /** | ||
123 | * Checks if Graphviz is installed on the system | ||
124 | * | ||
125 | * @return Present or not | ||
126 | */ | ||
127 | ✗ | bool FileUtil::isGraphvizInstalled() { return std::system("dot -V") == 0; } | |
128 | |||
129 | /** | ||
130 | * Search for a supported linker invoker on the system and return the executable name or path. | ||
131 | * This function may throw a LinkerError if no linker invoker is found. | ||
132 | * | ||
133 | * @return Name of path to the linker invoker executable | ||
134 | */ | ||
135 | 190 | ExternalBinaryFinderResult FileUtil::findLinkerInvoker() { | |
136 | #if OS_UNIX | ||
137 |
1/2✓ Branch 0 (26→4) taken 190 times.
✗ Branch 1 (26→27) not taken.
|
190 | for (const char *linkerInvokerName : {"clang", "gcc"}) |
138 |
2/4✓ Branch 0 (8→9) taken 190 times.
✗ Branch 1 (8→36) not taken.
✓ Branch 2 (23→6) taken 190 times.
✗ Branch 3 (23→24) not taken.
|
380 | for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"}) |
139 |
4/8✓ Branch 0 (10→11) taken 190 times.
✗ Branch 1 (10→43) not taken.
✓ Branch 2 (11→12) taken 190 times.
✗ Branch 3 (11→41) not taken.
✓ Branch 4 (12→13) taken 190 times.
✗ Branch 5 (12→39) not taken.
✓ Branch 6 (15→16) taken 190 times.
✗ Branch 7 (15→18) not taken.
|
190 | if (std::filesystem::exists(path + linkerInvokerName)) |
140 |
2/4✓ Branch 0 (16→17) taken 190 times.
✗ Branch 1 (16→45) not taken.
✗ Branch 2 (20→21) not taken.
✓ Branch 3 (20→25) taken 190 times.
|
380 | return ExternalBinaryFinderResult{linkerInvokerName, path + linkerInvokerName}; |
141 | #elif OS_WINDOWS | ||
142 | for (const char *linkerInvokerName : {"clang", "gcc"}) | ||
143 | if (isCommandAvailable(std::string(linkerInvokerName) + " -v")) | ||
144 | return ExternalBinaryFinderResult{linkerInvokerName, linkerInvokerName}; | ||
145 | #else | ||
146 | #error "Unsupported platform" | ||
147 | #endif | ||
148 | − | const auto msg = "No supported linker invoker was found on the system. Supported are: clang and gcc"; // LCOV_EXCL_LINE | |
149 | − | throw LinkerError(LINKER_NOT_FOUND, msg); // LCOV_EXCL_LINE | |
150 | } | ||
151 | |||
152 | /** | ||
153 | * Search for a supported linker on the system and return the executable name or path. | ||
154 | * This function may throw a LinkerError if no linker is found. | ||
155 | * | ||
156 | * @return Name of path to the linker executable | ||
157 | */ | ||
158 | 190 | ExternalBinaryFinderResult FileUtil::findLinker([[maybe_unused]] const CliOptions &cliOptions) { | |
159 | #if OS_UNIX | ||
160 | 190 | std::vector<const char *> linkerList; | |
161 |
1/2✓ Branch 0 (2→3) taken 190 times.
✗ Branch 1 (2→75) not taken.
|
190 | linkerList.reserve(5); |
162 | // mold does only support linking for unix and darwin | ||
163 |
2/4✓ Branch 0 (3→4) taken 190 times.
✗ Branch 1 (3→75) not taken.
✓ Branch 2 (4→5) taken 190 times.
✗ Branch 3 (4→7) not taken.
|
190 | if (cliOptions.targetOs != "windows") |
164 |
1/2✓ Branch 0 (5→6) taken 190 times.
✗ Branch 1 (5→48) not taken.
|
190 | linkerList.push_back("mold"); |
165 |
1/2✓ Branch 0 (7→8) taken 190 times.
✗ Branch 1 (7→49) not taken.
|
190 | linkerList.push_back("ld.lld"); |
166 |
1/2✓ Branch 0 (8→9) taken 190 times.
✗ Branch 1 (8→50) not taken.
|
190 | linkerList.push_back("ld64.ddl"); |
167 |
1/2✓ Branch 0 (9→10) taken 190 times.
✗ Branch 1 (9→51) not taken.
|
190 | linkerList.push_back("gold"); |
168 |
1/2✓ Branch 0 (10→11) taken 190 times.
✗ Branch 1 (10→52) not taken.
|
190 | linkerList.push_back("ld"); |
169 | |||
170 |
1/2✓ Branch 0 (38→13) taken 190 times.
✗ Branch 1 (38→39) not taken.
|
190 | for (const char *linkerName : linkerList) |
171 |
2/4✓ Branch 0 (18→19) taken 380 times.
✗ Branch 1 (18→53) not taken.
✓ Branch 2 (33→16) taken 380 times.
✗ Branch 3 (33→34) not taken.
|
760 | for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"}) |
172 |
5/8✓ Branch 0 (20→21) taken 380 times.
✗ Branch 1 (20→60) not taken.
✓ Branch 2 (21→22) taken 380 times.
✗ Branch 3 (21→58) not taken.
✓ Branch 4 (22→23) taken 380 times.
✗ Branch 5 (22→56) not taken.
✓ Branch 6 (25→26) taken 190 times.
✓ Branch 7 (25→28) taken 190 times.
|
380 | if (std::filesystem::exists(path + linkerName)) |
173 |
3/4✓ Branch 0 (26→27) taken 190 times.
✗ Branch 1 (26→62) not taken.
✓ Branch 2 (30→31) taken 190 times.
✓ Branch 3 (30→35) taken 190 times.
|
570 | return ExternalBinaryFinderResult{linkerName, path + linkerName}; |
174 | #elif OS_WINDOWS | ||
175 | for (const char *linkerName : {"lld", "ld"}) | ||
176 | if (isCommandAvailable(std::string(linkerName) + " -v")) | ||
177 | return ExternalBinaryFinderResult{linkerName, linkerName}; | ||
178 | #else | ||
179 | #error "Unsupported platform" | ||
180 | #endif | ||
181 | − | const auto msg = "No supported linker was found on the system. Supported are: mold, lld, gold and ld"; // LCOV_EXCL_LINE | |
182 | − | throw LinkerError(LINKER_NOT_FOUND, msg); // LCOV_EXCL_LINE | |
183 | 190 | } | |
184 | |||
185 | /** | ||
186 | * Retrieve the dir, where the standard library lives. | ||
187 | * Returns an empty string if the std was not found. | ||
188 | * | ||
189 | * @return Std directory | ||
190 | */ | ||
191 | 762 | std::filesystem::path FileUtil::getStdDir() { | |
192 | #if OS_UNIX | ||
193 |
3/6✓ Branch 0 (2→3) taken 762 times.
✗ Branch 1 (2→25) not taken.
✓ Branch 2 (3→4) taken 762 times.
✗ Branch 3 (3→23) not taken.
✗ Branch 4 (5→6) not taken.
✓ Branch 5 (5→7) taken 762 times.
|
762 | if (exists(std::filesystem::path("/usr/lib/spice/std/"))) |
194 | ✗ | return {"/usr/lib/spice/std/"}; | |
195 | #endif | ||
196 |
1/2✓ Branch 0 (8→9) taken 762 times.
✗ Branch 1 (8→21) not taken.
|
762 | if (std::getenv("SPICE_STD_DIR")) |
197 |
3/6✓ Branch 0 (10→11) taken 762 times.
✗ Branch 1 (10→26) not taken.
✓ Branch 2 (11→12) taken 762 times.
✗ Branch 3 (11→27) not taken.
✓ Branch 4 (12→13) taken 762 times.
✗ Branch 5 (12→15) not taken.
|
762 | if (const std::filesystem::path stdPath(std::getenv("SPICE_STD_DIR")); exists(stdPath)) |
198 |
2/4✓ Branch 0 (13→14) taken 762 times.
✗ Branch 1 (13→27) not taken.
✗ Branch 2 (17→18) not taken.
✓ Branch 3 (17→20) taken 762 times.
|
762 | return stdPath; |
199 | − | return ""; // GCOV_EXCL_LINE | |
200 | } | ||
201 | |||
202 | /** | ||
203 | * Retrieve the dir, where the bootstrap compiler lives. | ||
204 | * Returns an empty string if the bootstrap compiler was not found. | ||
205 | * | ||
206 | * @return | ||
207 | */ | ||
208 | 18 | std::filesystem::path FileUtil::getBootstrapDir() { | |
209 |
1/2✓ Branch 0 (3→4) taken 18 times.
✗ Branch 1 (3→13) not taken.
|
18 | if (std::getenv("SPICE_BOOTSTRAP_DIR")) { |
210 |
3/8✓ Branch 0 (5→6) taken 18 times.
✗ Branch 1 (5→16) not taken.
✓ Branch 2 (6→7) taken 18 times.
✗ Branch 3 (6→17) not taken.
✓ Branch 4 (7→8) taken 18 times.
✗ Branch 5 (7→9) not taken.
✗ Branch 6 (17→18) not taken.
✗ Branch 7 (17→19) not taken.
|
18 | if (const std::filesystem::path stdPath(std::getenv("SPICE_BOOTSTRAP_DIR")); exists(stdPath)) |
211 | 18 | return stdPath; | |
212 | } | ||
213 | − | return ""; // GCOV_EXCL_LINE | |
214 | } | ||
215 | |||
216 | /** | ||
217 | * Retrieve the dir, where output binaries should go when installing them | ||
218 | * | ||
219 | * @return Installation directory | ||
220 | */ | ||
221 | 2 | std::filesystem::path FileUtil::getSpiceBinDir() { | |
222 | #if OS_UNIX | ||
223 | 2 | return "/usr/local/bin/"; | |
224 | #elif OS_WINDOWS | ||
225 | return std::filesystem::path(std::getenv("USERPROFILE")) / "spice" / "bin"; | ||
226 | #else | ||
227 | #error "Unsupported platform" | ||
228 | #endif | ||
229 | } | ||
230 | |||
231 | } // namespace spice::compiler | ||
232 |