hoshi-lang dev
Yet another programming language
Loading...
Searching...
No Matches
clObjectLinker.cpp
Go to the documentation of this file.
1#include "clObjectLinker.h"
2#include "share/def.hpp"
3#include <cstdlib>
4#include <filesystem>
5#include <iostream>
6#include <stdexcept>
7#include <string>
8#include <vector>
9
10namespace yoi {
11
12 clObjectLinker::clObjectLinker(const yoi::vec<yoi::wstr> &objectPaths, const std::shared_ptr<IRBuildConfig> &config)
13 : ObjectLinker(objectPaths, config) {
14 // Constructor simply calls the base class constructor.
15 }
16
18 std::filesystem::path cl_exe_name = L"cl.exe";
19 std::filesystem::path found_path;
20
21 // 1. Check PATH environment variable
22 const char *path_env = std::getenv("PATH");
23 if (path_env) {
24 std::wstring path_env_str = yoi::string2wstring(path_env);
25 size_t current_pos = 0;
26 size_t delimiter_pos;
27
28 while ((delimiter_pos = path_env_str.find(L';', current_pos)) != std::wstring::npos) {
29 std::filesystem::path dir = path_env_str.substr(current_pos, delimiter_pos - current_pos);
30 std::filesystem::path potential_cl_path = dir / cl_exe_name;
31 if (std::filesystem::exists(potential_cl_path) && std::filesystem::is_regular_file(potential_cl_path)) {
32 found_path = potential_cl_path;
33 break;
34 }
35 current_pos = delimiter_pos + 1;
36 }
37 // Check the last path segment (or if no delimiters were found)
38 if (found_path.empty()) {
39 std::filesystem::path dir = path_env_str.substr(current_pos);
40 std::filesystem::path potential_cl_path = dir / cl_exe_name;
41 if (std::filesystem::exists(potential_cl_path) && std::filesystem::is_regular_file(potential_cl_path)) {
42 found_path = potential_cl_path;
43 }
44 }
45 }
46
47 if (!found_path.empty()) {
48 setLinkerPath(found_path.wstring());
49 return *this;
50 }
51
52 bool isResolved{false};
53
54 // 2. Search common Visual Studio installation paths
55 // This search can be extensive and slow. Limiting to common patterns and x64 host/target.
56 std::vector<std::filesystem::path> vs_install_bases = {
57 L"C:/Program Files (x86)/Microsoft Visual Studio/", // For older VS or 32-bit components
58 L"C:/Program Files/Microsoft Visual Studio/" // For newer VS or 64-bit components
59 };
60 // Windows Kits
61 std::vector<std::filesystem::path> windows_kit_install_bases = {L"C:/Program Files (x86)/Windows Kits/10/Lib",
62 L"C:/Program Files/Windows Kits/10/Lib"};
63
64 for (const auto &base_path : vs_install_bases) {
65 if (!std::filesystem::exists(base_path) || !std::filesystem::is_directory(base_path)) {
66 continue;
67 }
68 for (const auto &year_entry : std::filesystem::directory_iterator(base_path)) {
69 if (!year_entry.is_directory())
70 continue;
71
72 for (const auto &component_entry : std::filesystem::directory_iterator(year_entry.path())) {
73 if (!component_entry.is_directory())
74 continue;
75
76 std::filesystem::path vc_tools_msvc_path = component_entry.path() / L"VC" / L"Tools" / L"MSVC";
77
78 if (!std::filesystem::exists(vc_tools_msvc_path) ||
79 !std::filesystem::is_directory(vc_tools_msvc_path)) {
80 continue;
81 }
82
83 // Iterate through specific MSVC toolchain versions (e.g., "14.37.32822")
84 for (const auto &msvc_version_entry : std::filesystem::directory_iterator(vc_tools_msvc_path)) {
85 if (!msvc_version_entry.is_directory())
86 continue;
87
88 // Common bin paths relative to MSVC version folder (prefer x64 host/target)
89 std::vector<std::filesystem::path> bin_sub_paths = {
90 L"bin\\Hostx64\\x64", // Preferred: 64-bit host, 64-bit target
91 L"bin\\Hostx86\\x64", // 32-bit host, 64-bit target (e.g., when run from VS dev cmd x86)
92 L"bin\\Hostx64\\x86", // 64-bit host, 32-bit target
93 L"bin\\Hostx86\\x86" // 32-bit host, 32-bit target
94 };
95 // Library paths contain the runtime library (e.g., "lib/x64")
96 std::vector<std::filesystem::path> lib_sub_paths = {L"lib\\x64", L"lib\\x86"};
97
98 for (const auto &bin_sub_path : bin_sub_paths) {
99 std::filesystem::path potential_cl_path =
100 msvc_version_entry.path() / bin_sub_path / cl_exe_name;
101 if (std::filesystem::exists(potential_cl_path) &&
102 std::filesystem::is_regular_file(potential_cl_path)) {
103 found_path = potential_cl_path;
104 setLinkerPath(found_path.wstring());
105 std::wcout << L"clObjectLinker: Found cl.exe by searching VS installs: "
106 << getLinkerPath() << std::endl;
107 isResolved = true;
108 break;
109 }
110 }
111
112 yoi_assert(isResolved, 0, 0, "Unable to find cl.exe. Please ensure Visual Studio Build Tools are installed.");
113 isResolved = false;
114
115 for (const auto &lib_sub_path : lib_sub_paths) {
116 std::filesystem::path lib_path = msvc_version_entry.path() / lib_sub_path;
117 // check whether the runtime library dir exists
118 if (std::filesystem::exists(lib_path) && std::filesystem::is_directory(lib_path)) {
119 vsRuntimePath.emplace_back(string2wstring(lib_path.string()));
120 std::wcout << L"clObjectLinker: Found c runtime library by searching VS installs: "
121 << string2wstring(lib_path.string()) << std::endl;
122 isResolved = true;
123 break;
124 }
125 }
126
127 yoi_assert(isResolved, 0, 0, "Unable to find C runtime library. Please ensure Visual Studio Build Tools are installed.");
128 isResolved = false;
129 break;
130 }
131 }
132 }
133 }
134
135 for (const auto &base_path : windows_kit_install_bases) {
136 if (!std::filesystem::exists(base_path) || !std::filesystem::is_directory(base_path)) {
137 continue;
138 }
139 for (const auto &version_entry : std::filesystem::directory_iterator(base_path)) {
140 if (!version_entry.is_directory())
141 continue;
142 // UM Paths
143 std::vector<std::filesystem::path> um_sub_paths = {L"um/x64", L"um/x86"};
144 // Ucrt
145 std::vector<std::filesystem::path> ucrt_sub_paths = {L"ucrt/x64", L"ucrt/x86"};
146 for (const auto &um_sub_path : um_sub_paths) {
147 std::filesystem::path lib_path = version_entry.path() / um_sub_path;
148 // check whether the runtime library dir exists
149 if (std::filesystem::exists(lib_path) && std::filesystem::is_directory(lib_path)) {
150 vsRuntimePath.emplace_back(string2wstring(lib_path.string()));
151 std::wcout << L"clObjectLinker: Found UM library by searching Windows Kits: "
152 << string2wstring(lib_path.string()) << std::endl;
153 isResolved = true;
154 break;
155 }
156 }
157
158 yoi_assert(isResolved, 0, 0, "Unable to find UM library. Please ensure Windows Kits are installed.");
159 isResolved = false;
160
161 for (const auto &ucrt_sub_path : ucrt_sub_paths) {
162 std::filesystem::path lib_path = version_entry.path() / ucrt_sub_path;
163 // check whether the runtime library dir exists
164 if (std::filesystem::exists(lib_path) && std::filesystem::is_directory(lib_path)) {
165 vsRuntimePath.emplace_back(string2wstring(lib_path.string()));
166 std::wcout << L"clObjectLinker: Found UCRT library by searching Windows Kits: "
167 << string2wstring(lib_path.string()) << std::endl;
168 isResolved = true;
169 break;
170 }
171 }
172
173 }
174 }
175
176 if (!isResolved)
177 warning(0, 0, "Unable to find UCRT library. Please ensure Windows Kits are installed.", "UCRT_NOT_FOUND");
178
179 const char* lib_env = std::getenv("LIB");
180 if (lib_env) {
181 std::wstring lib_env_str = yoi::string2wstring(lib_env);
182
183 for (auto pos = lib_env_str.find(L';'); pos != std::wstring::npos; pos = lib_env_str.find(L';', pos + 1)) {
184 std::wstring path = lib_env_str.substr(0, pos);
185 vsRuntimePath.emplace_back(path);
186 }
187 // Add the last path segment
188 std::wstring path = lib_env_str.substr(0, lib_env_str.find(L';'));
189 vsRuntimePath.emplace_back(path);
190 }
191
192 return *this;
193
194 throw std::runtime_error("cl.exe linker not found. Please ensure Visual Studio Build Tools are installed and "
195 "configured, or add cl.exe to your system PATH.");
196 }
197
199 if (getLinkerPath().empty()) {
200 throw std::runtime_error("Linker path not set. Call searchAndSetupLinker() first.");
201 }
202 if (getObjectPaths().empty()) {
203 throw std::runtime_error("Object path is empty.");
204 }
205
206 std::filesystem::path output_fs_path(outputPath);
207 std::filesystem::path elysia_runtime_fs_path(getElysiaRuntimePath());
208
209 std::wstring command = L"\"" + getLinkerPath() + L"\"";
210 for (const auto &objectPath : getObjectPaths()) {
211 command += L" \"" + objectPath + L"\"";
212 }
213
214 // add additional linking files
215 for (const auto &file : this->getConfig()->additionalLinkingFiles) {
216 command += L" \"" + file + L"\"";
217 }
218
219 command += L" /Fe:\"" + output_fs_path.wstring() + L"\"";
220
221 if (this->getConfig()->buildType == IRBuildConfig::BuildType::library) {
222 command += L" /LD"; // Build a shared library
223 }
224
225 command += L" /link";
226
227 if (!elysia_runtime_fs_path.empty()) {
228 command += L" /LIBPATH:\"" + elysia_runtime_fs_path.wstring() + L"\"";
229 command += L" elysia_runtime.lib";
230 } else {
231 warning(0, 0, "Elysia runtime library not specified. Linking may fail if runtime functions are used.", "ELYSIA_RUNTIME_NOT_FOUND");
232 }
233 // also link against msvcrt.lib
234 // command += L" /LIBPATH:\"" + vsRuntimePath + L"\"";
235 for (auto &path : vsRuntimePath) {
236 command += L" /LIBPATH:\"" + path + L"\"";
237 }
238 command += L" msvcrt.lib";
239
240 command += L" /SUBSYSTEM:CONSOLE"; // fuck argc, argv
241
242 // synchronize the release/debug
243 if (this->getConfig()->buildMode == IRBuildConfig::BuildMode::debug) {
244 command += L" /DEBUG";
245 } else {
246 command += L" /RELEASE";
247 }
248
249#if defined(_WIN32)
250 replace_all(command, std::wstring(L"\""), std::wstring(L"\\\""));
251 command = L"powershell.exe -Command \"& " + command + L"\""; // fuck win32 command line
252#endif
253
254 int result = system(yoi::wstring2string(command).c_str());
255
256 if (result != 0) {
257 std::string error_msg = "Linking failed. cl.exe returned error code: " + std::to_string(result) +
258 "\nCommand: " + yoi::wstring2string(command);
259 throw std::runtime_error(error_msg);
260 }
261
262 std::wcout << L"clObjectLinker: Linking successful. Output executable: " << outputPath << std::endl;
263 return *this;
264 }
265
266} // namespace yoi
std::shared_ptr< IRBuildConfig > getConfig() const
ObjectLinker & setLinkerPath(const yoi::wstr &linkerPath)
yoi::wstr getLinkerPath() const
yoi::vec< yoi::wstr > getObjectPaths() const
yoi::wstr getElysiaRuntimePath() const
clObjectLinker(const yoi::vec< yoi::wstr > &objectPaths, const std::shared_ptr< IRBuildConfig > &config)
ObjectLinker & link(const yoi::wstr &outputPath) override
yoi::vec< yoi::wstr > vsRuntimePath
ObjectLinker & searchAndSetupLinker() override
std::string wstring2string(const std::wstring &v)
Definition def.cpp:184
void warning(yoi::indexT line, yoi::indexT col, const std::string &msg, const std::string &label)
Definition def.cpp:143
void replace_all(string_t &str, const string_t &from, const string_t &to)
Definition def.hpp:368
std::vector< t > vec
Definition def.hpp:53
std::wstring string2wstring(const std::string &v)
Definition def.cpp:178
void yoi_assert(bool condition, yoi::indexT line, yoi::indexT col, const std::string &msg)
Asserts a condition that would be true and throws a runtime_error if it is false.
Definition def.cpp:171
std::wstring wstr
Definition def.hpp:48