GCC Code Coverage Report


Directory: ../
File: src/util/SystemUtil.cpp
Date: 2025-12-19 06:54:40
Coverage Exec Excl Total
Lines: 97.9% 47 10 58
Functions: 100.0% 8 0 8
Branches: 52.8% 57 22 130

Line Branch Exec Source
1 // Copyright (c) 2021-2025 ChilliBits. All rights reserved.
2
3 #include "SystemUtil.h"
4
5 #include <array>
6 #include <cstring>
7 #include <iostream> // IWYU pragma: keep (usage in Windows-only code)
8 #include <vector>
9
10 #include <driver/Driver.h>
11 #include <exception/CompilerError.h>
12 #include <exception/LinkerError.h>
13
14 namespace spice::compiler {
15
16 /**
17 * Execute external command. Used to execute compiled binaries
18 *
19 * @param command Command to execute
20 * @param redirectStdErrToStdOut Redirect StdErr to StdOut
21 * @return Result struct
22 */
23 404 ExecResult SystemUtil::exec(const std::string &command, bool redirectStdErrToStdOut) {
24 #if OS_UNIX
25
1/2
✓ Branch 2 → 3 taken 404 times.
✗ Branch 2 → 41 not taken.
404 std::string redirectedCommand = command;
26
2/2
✓ Branch 3 → 4 taken 202 times.
✓ Branch 3 → 5 taken 202 times.
404 if (redirectStdErrToStdOut)
27
1/2
✓ Branch 4 → 5 taken 202 times.
✗ Branch 4 → 39 not taken.
202 redirectedCommand += " 2>&1"; // Redirect stderr to stdout
28
1/2
✓ Branch 6 → 7 taken 404 times.
✗ Branch 6 → 39 not taken.
404 FILE *pipe = popen(redirectedCommand.c_str(), "r");
29 #elif OS_WINDOWS
30 std::string redirectedCommand = command;
31 if (redirectStdErrToStdOut)
32 redirectedCommand = "\"" + command + " 2>&1\""; // Redirect stderr to stdout
33 FILE *pipe = _popen(redirectedCommand.c_str(), "r");
34 #else
35 #error "Unsupported platform"
36 #endif
37
38 if (!pipe) // GCOV_EXCL_LINE
39 throw CompilerError(IO_ERROR, "Failed to execute command: " + command); // GCOV_EXCL_LINE
40
41 404 std::array<char, 128> buffer{};
42
1/2
✓ Branch 13 → 14 taken 404 times.
✗ Branch 13 → 39 not taken.
404 std::stringstream result;
43
3/4
✓ Branch 22 → 23 taken 6842 times.
✗ Branch 22 → 37 not taken.
✓ Branch 23 → 15 taken 6438 times.
✓ Branch 23 → 24 taken 404 times.
14088 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
44
1/2
✓ Branch 17 → 18 taken 6438 times.
✗ Branch 17 → 37 not taken.
6438 result << buffer.data();
45
46 #if OS_UNIX
47
1/2
✓ Branch 24 → 25 taken 404 times.
✗ Branch 24 → 37 not taken.
404 const int exitCode = pclose(pipe) / 256;
48 #elif OS_WINDOWS
49 const int exitCode = _pclose(pipe);
50 #else
51 #error "Unsupported platform"
52 #endif
53 404 return {result.str(), exitCode};
54
1/2
✓ Branch 25 → 26 taken 404 times.
✗ Branch 25 → 37 not taken.
404 }
55
56 /**
57 * Checks if a certain command is available on the computer
58 *
59 * @param cmd Command to search for
60 * @return Present or not
61 */
62 3 bool SystemUtil::isCommandAvailable(const std::string &cmd) {
63 #if OS_UNIX
64
2/4
✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 13 not taken.
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 11 not taken.
3 const std::string checkCmd = "which " + cmd + " > /dev/null 2>&1";
65 #elif OS_WINDOWS
66 const std::string checkCmd = "where " + cmd + " > nul 2>&1";
67 #else
68 #error "Unsupported platform"
69 #endif
70
1/2
✓ Branch 6 → 7 taken 3 times.
✗ Branch 6 → 14 not taken.
6 return std::system(checkCmd.c_str()) == 0;
71 3 }
72
73 /**
74 * Checks if Graphviz is installed on the system
75 *
76 * @return Present or not
77 */
78
2/4
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 13 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 11 not taken.
3 bool SystemUtil::isGraphvizInstalled() { return isCommandAvailable("dot"); }
79
80 /**
81 * Search for a supported linker invoker on the system and return the executable name or path.
82 * This function may throw a LinkerError if no linker invoker is found.
83 *
84 * @return Name and path to the linker invoker executable
85 */
86 202 ExternalBinaryFinderResult SystemUtil::findLinkerInvoker() {
87 #if OS_UNIX
88
1/2
✓ Branch 26 → 4 taken 202 times.
✗ Branch 26 → 27 not taken.
202 for (const char *linkerInvokerName : {"clang", "gcc"})
89
2/4
✓ Branch 8 → 9 taken 202 times.
✗ Branch 8 → 36 not taken.
✓ Branch 23 → 6 taken 202 times.
✗ Branch 23 → 24 not taken.
404 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
90
4/8
✓ Branch 10 → 11 taken 202 times.
✗ Branch 10 → 43 not taken.
✓ Branch 11 → 12 taken 202 times.
✗ Branch 11 → 41 not taken.
✓ Branch 12 → 13 taken 202 times.
✗ Branch 12 → 39 not taken.
✓ Branch 15 → 16 taken 202 times.
✗ Branch 15 → 18 not taken.
202 if (std::filesystem::exists(path + linkerInvokerName))
91
2/4
✓ Branch 16 → 17 taken 202 times.
✗ Branch 16 → 45 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 25 taken 202 times.
404 return ExternalBinaryFinderResult{linkerInvokerName, path + linkerInvokerName};
92 #elif OS_WINDOWS
93 for (const char *linkerInvokerName : {"clang", "gcc"})
94 if (isCommandAvailable(std::string(linkerInvokerName) + " -v"))
95 return ExternalBinaryFinderResult{linkerInvokerName, linkerInvokerName};
96 #else
97 #error "Unsupported platform"
98 #endif
99 const auto msg = "No supported linker invoker was found on the system. Supported are: clang and gcc"; // LCOV_EXCL_LINE
100 throw LinkerError(LINKER_NOT_FOUND, msg); // LCOV_EXCL_LINE
101 }
102
103 /**
104 * Search for a supported linker on the system and return the executable name or path.
105 * This function may throw a LinkerError if no linker is found.
106 *
107 * @return Name and path to the linker executable
108 */
109 202 ExternalBinaryFinderResult SystemUtil::findLinker([[maybe_unused]] const CliOptions &cliOptions) {
110 #if OS_UNIX
111 202 std::vector<const char *> linkerList;
112
1/2
✓ Branch 2 → 3 taken 202 times.
✗ Branch 2 → 75 not taken.
202 linkerList.reserve(5);
113 // mold does only support linking for unix and darwin
114
1/2
✓ Branch 4 → 5 taken 202 times.
✗ Branch 4 → 7 not taken.
202 if (!cliOptions.targetTriple.isOSWindows())
115
1/2
✓ Branch 5 → 6 taken 202 times.
✗ Branch 5 → 48 not taken.
202 linkerList.push_back("mold");
116
1/2
✓ Branch 7 → 8 taken 202 times.
✗ Branch 7 → 49 not taken.
202 linkerList.push_back("ld.lld");
117
1/2
✓ Branch 8 → 9 taken 202 times.
✗ Branch 8 → 50 not taken.
202 linkerList.push_back("ld64.ddl");
118
1/2
✓ Branch 9 → 10 taken 202 times.
✗ Branch 9 → 51 not taken.
202 linkerList.push_back("gold");
119
1/2
✓ Branch 10 → 11 taken 202 times.
✗ Branch 10 → 52 not taken.
202 linkerList.push_back("ld");
120
121
1/2
✓ Branch 38 → 13 taken 202 times.
✗ Branch 38 → 39 not taken.
202 for (const char *linkerName : linkerList)
122
2/4
✓ Branch 18 → 19 taken 404 times.
✗ Branch 18 → 53 not taken.
✓ Branch 33 → 16 taken 404 times.
✗ Branch 33 → 34 not taken.
808 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
123
5/8
✓ Branch 20 → 21 taken 404 times.
✗ Branch 20 → 60 not taken.
✓ Branch 21 → 22 taken 404 times.
✗ Branch 21 → 58 not taken.
✓ Branch 22 → 23 taken 404 times.
✗ Branch 22 → 56 not taken.
✓ Branch 25 → 26 taken 202 times.
✓ Branch 25 → 28 taken 202 times.
404 if (std::filesystem::exists(path + linkerName))
124
3/4
✓ Branch 26 → 27 taken 202 times.
✗ Branch 26 → 62 not taken.
✓ Branch 30 → 31 taken 202 times.
✓ Branch 30 → 35 taken 202 times.
606 return ExternalBinaryFinderResult{linkerName, path + linkerName};
125 #elif OS_WINDOWS
126 for (const char *linkerName : {"lld", "ld"})
127 if (isCommandAvailable(std::string(linkerName) + " -v"))
128 return ExternalBinaryFinderResult{linkerName, linkerName};
129 #else
130 #error "Unsupported platform"
131 #endif
132 const auto msg = "No supported linker was found on the system. Supported are: mold, lld, gold and ld"; // LCOV_EXCL_LINE
133 throw LinkerError(LINKER_NOT_FOUND, msg); // LCOV_EXCL_LINE
134 202 }
135
136 /**
137 * Retrieve the dir, where the standard library lives.
138 * Returns an empty string if the std was not found.
139 *
140 * @return Std directory
141 */
142 944 std::filesystem::path SystemUtil::getStdDir() {
143 #if OS_UNIX
144
3/6
✓ Branch 2 → 3 taken 944 times.
✗ Branch 2 → 32 not taken.
✓ Branch 3 → 4 taken 944 times.
✗ Branch 3 → 30 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 944 times.
944 if (exists(std::filesystem::path("/usr/lib/spice/std/")))
145 return "/usr/lib/spice/std/";
146 #endif
147
1/2
✓ Branch 8 → 9 taken 944 times.
✗ Branch 8 → 21 not taken.
944 if (std::getenv("SPICE_STD_DIR"))
148
3/6
✓ Branch 10 → 11 taken 944 times.
✗ Branch 10 → 33 not taken.
✓ Branch 11 → 12 taken 944 times.
✗ Branch 11 → 34 not taken.
✓ Branch 12 → 13 taken 944 times.
✗ Branch 12 → 15 not taken.
944 if (const std::filesystem::path stdPath(std::getenv("SPICE_STD_DIR")); exists(stdPath))
149
2/4
✓ Branch 13 → 14 taken 944 times.
✗ Branch 13 → 34 not taken.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 20 taken 944 times.
944 return stdPath;
150 const auto errMsg = "Standard library could not be found. Check if the env var SPICE_STD_DIR exists"; // GCOV_EXCL_LINE
151 throw CompilerError(STD_NOT_FOUND, errMsg); // GCOV_EXCL_LINE
152 }
153
154 /**
155 * Retrieve the dir, where the bootstrap compiler lives.
156 * Returns an empty string if the bootstrap compiler was not found.
157 *
158 * @return
159 */
160 18 std::filesystem::path SystemUtil::getBootstrapDir() {
161
1/2
✓ Branch 3 → 4 taken 18 times.
✗ Branch 3 → 13 not taken.
18 if (std::getenv("SPICE_BOOTSTRAP_DIR")) {
162
3/8
✓ Branch 5 → 6 taken 18 times.
✗ Branch 5 → 22 not taken.
✓ Branch 6 → 7 taken 18 times.
✗ Branch 6 → 23 not taken.
✓ Branch 7 → 8 taken 18 times.
✗ Branch 7 → 9 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
18 if (const std::filesystem::path stdPath(std::getenv("SPICE_BOOTSTRAP_DIR")); exists(stdPath))
163 36 return stdPath;
164 }
165 const auto errMsg = "Bootstrap compiler could not be found. Check if the env var SPICE_BOOTSTRAP_DIR exists"; // GCOV_EXCL_LINE
166 throw CompilerError(BOOTSTRAP_NOT_FOUND, errMsg); // GCOV_EXCL_LINE
167 }
168
169 /**
170 * Retrieve the dir, where output binaries should go when installing them
171 *
172 * @return Installation directory
173 */
174 2 std::filesystem::path SystemUtil::getSpiceBinDir() {
175 #if OS_UNIX
176 2 return "/usr/local/bin/";
177 #elif OS_WINDOWS
178 const char *userProfile = std::getenv("USERPROFILE");
179 assert(userProfile != nullptr && strlen(userProfile) > 0);
180 return std::filesystem::path(userProfile) / "spice" / "bin";
181 #else
182 #error "Unsupported platform"
183 #endif
184 }
185
186 } // namespace spice::compiler
187