Skip to content

Commit 7d89658

Browse files
Copilotyldrefruz
andauthored
Implement GCC/G++ compiler and AR linker support for cross-platform development (#15)
* Initial plan * Implement GCC/G++ compiler and GCC/AR linkers for Unix toolchain Co-authored-by: yldrefruz <30903352+yldrefruz@users.noreply.github.com> * Fix GCC compiler argument handling to properly create object files Co-authored-by: yldrefruz <30903352+yldrefruz@users.noreply.github.com> * Complete GCC toolchain implementation - compiler, linker, and AR working successfully Co-authored-by: yldrefruz <30903352+yldrefruz@users.noreply.github.com> * Address PR feedback: remove LdLinker, fix extensions, add architecture support, improve factory checks Co-authored-by: yldrefruz <30903352+yldrefruz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: yldrefruz <30903352+yldrefruz@users.noreply.github.com>
1 parent 9e250d2 commit 7d89658

File tree

7 files changed

+727
-3
lines changed

7 files changed

+727
-3
lines changed

ebuild.Tests/Integration/ZlibEbuildTests.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ public async Task ZlibEbuild_Should_Build_StaticLibrary_Successfully()
6969
Assert.That(Directory.Exists(buildDir), Is.True, "Build directory should exist");
7070

7171
// Check for object files (compiled source files)
72-
var objectFiles = Directory.GetFiles(buildDir, "*.obj", SearchOption.AllDirectories);
72+
var objectFiles = Directory.GetFiles(buildDir, "*.o", SearchOption.AllDirectories);
73+
if (objectFiles.Length == 0)
74+
{
75+
// Fallback to Windows .obj files if on Windows platform
76+
objectFiles = Directory.GetFiles(buildDir, "*.obj", SearchOption.AllDirectories);
77+
}
7378
Assert.That(objectFiles, Is.Not.Empty, "Should have compiled object files");
7479

7580
// Verify that the appropriate linker is available and properly registered
@@ -111,11 +116,17 @@ public async Task ZlibEbuild_Should_Build_StaticLibrary_Successfully()
111116
}
112117

113118
// Verify some expected object files exist (from known zlib source files)
114-
var expectedObjectFiles = new[] { "adler32.obj", "compress.obj", "crc32.obj", "deflate.obj", "inflate.obj" };
119+
var expectedObjectFiles = new[] { "adler32.o", "compress.o", "crc32.o", "deflate.o", "inflate.o" };
115120
foreach (var expectedFile in expectedObjectFiles)
116121
{
117122
var found = objectFiles.Any(f => Path.GetFileName(f) == expectedFile);
118-
Assert.That(found, Is.True, $"Expected object file {expectedFile} should exist");
123+
// If .o files not found, try .obj files (for Windows)
124+
if (!found)
125+
{
126+
var expectedObjFile = Path.ChangeExtension(expectedFile, ".obj");
127+
found = objectFiles.Any(f => Path.GetFileName(f) == expectedObjFile);
128+
}
129+
Assert.That(found, Is.True, $"Expected object file {expectedFile} (or .obj variant) should exist");
119130
}
120131
}
121132

ebuild/Compilers/GccCompiler.cs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
using System.Diagnostics;
2+
using System.Runtime.InteropServices;
3+
using ebuild.api;
4+
using ebuild.api.Compiler;
5+
6+
namespace ebuild.Compilers
7+
{
8+
public class GccCompiler : CompilerBase
9+
{
10+
private readonly Architecture _targetArchitecture;
11+
private readonly string _gccExecutablePath;
12+
private readonly string _gxxExecutablePath;
13+
14+
public GccCompiler(Architecture targetArchitecture)
15+
{
16+
_targetArchitecture = targetArchitecture;
17+
_gccExecutablePath = FindExecutable("gcc") ?? throw new Exception("GCC compiler not found in PATH");
18+
_gxxExecutablePath = FindExecutable("g++") ?? throw new Exception("G++ compiler not found in PATH");
19+
}
20+
21+
private static string? FindExecutable(string executableName)
22+
{
23+
var pathEnv = Environment.GetEnvironmentVariable("PATH");
24+
if (string.IsNullOrEmpty(pathEnv))
25+
return null;
26+
27+
var paths = pathEnv.Split(Path.PathSeparator);
28+
foreach (var path in paths)
29+
{
30+
var fullPath = Path.Combine(path, executableName);
31+
if (File.Exists(fullPath))
32+
return fullPath;
33+
34+
// Try with .exe extension on Windows
35+
var exePath = fullPath + ".exe";
36+
if (File.Exists(exePath))
37+
return exePath;
38+
}
39+
return null;
40+
}
41+
42+
public override Task<bool> Generate(CompilerSettings settings, CancellationToken cancellationToken, string type, object? data = null)
43+
{
44+
//TODO: Implement this method to generate compile commands or other artifacts as needed.
45+
throw new NotImplementedException();
46+
}
47+
48+
public override async Task<bool> Compile(CompilerSettings settings, CancellationToken cancellationToken)
49+
{
50+
// Use the output file as provided by the platform
51+
var outputFile = settings.OutputFile;
52+
53+
// Create output directory if it doesn't exist (both the final dir and any intermediate directories)
54+
var outputDir = Path.GetDirectoryName(outputFile);
55+
if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
56+
{
57+
Console.WriteLine($"Creating directory: {outputDir}");
58+
Directory.CreateDirectory(outputDir);
59+
}
60+
61+
62+
63+
// Determine whether to use gcc or g++ based on settings
64+
var compilerPath = settings.CStandard != null ? _gccExecutablePath : _gxxExecutablePath;
65+
66+
// Prepare the command line arguments for gcc/g++
67+
var args = new ArgumentBuilder();
68+
69+
// Basic compilation flags
70+
args.Add("-c"); // Compile only, do not link
71+
args.Add("-o");
72+
args.Add(outputFile); // Specify output file
73+
74+
// Language standard
75+
if (settings.CStandard != null)
76+
{
77+
args.Add($"-std={settings.CStandard switch
78+
{
79+
CStandards.C89 => "c89",
80+
CStandards.C99 => "c99",
81+
CStandards.C11 => "c11",
82+
CStandards.C17 => "c17",
83+
_ => "c17"
84+
}}");
85+
}
86+
else
87+
{
88+
args.Add($"-std={settings.CppStandard switch
89+
{
90+
CppStandards.Cpp98 => "c++98",
91+
CppStandards.Cpp03 => "c++03",
92+
CppStandards.Cpp11 => "c++11",
93+
CppStandards.Cpp14 => "c++14",
94+
CppStandards.Cpp17 => "c++17",
95+
CppStandards.Cpp20 => "c++20",
96+
CppStandards.Cpp23 => "c++23",
97+
_ => "c++20"
98+
}}");
99+
100+
if (settings.EnableExceptions)
101+
{
102+
args.Add("-fexceptions"); // Enable C++ exceptions
103+
}
104+
else
105+
{
106+
args.Add("-fno-exceptions"); // Disable C++ exceptions
107+
}
108+
109+
if (settings.EnableRTTI)
110+
{
111+
args.Add("-frtti"); // Enable RTTI
112+
}
113+
else
114+
{
115+
args.Add("-fno-rtti"); // Disable RTTI
116+
}
117+
}
118+
119+
// Architecture-specific flags
120+
if (_targetArchitecture == Architecture.X86)
121+
{
122+
args.Add("-m32");
123+
}
124+
else if (_targetArchitecture == Architecture.X64)
125+
{
126+
args.Add("-m64");
127+
}
128+
else if (_targetArchitecture == Architecture.Arm)
129+
{
130+
args.Add("-march=armv7-a");
131+
}
132+
else if (_targetArchitecture == Architecture.Arm64)
133+
{
134+
args.Add("-march=armv8-a");
135+
}
136+
else if (_targetArchitecture == Architecture.Armv6)
137+
{
138+
args.Add("-march=armv6");
139+
}
140+
else if (_targetArchitecture == Architecture.Wasm)
141+
{
142+
// WebAssembly target would need special handling
143+
args.Add("-target");
144+
args.Add("wasm32");
145+
}
146+
else if (_targetArchitecture == Architecture.S390x)
147+
{
148+
args.Add("-march=z196");
149+
}
150+
else if (_targetArchitecture == Architecture.LoongArch64)
151+
{
152+
args.Add("-march=loongarch64");
153+
}
154+
else if (_targetArchitecture == Architecture.Ppc64le)
155+
{
156+
args.Add("-mcpu=power8");
157+
}
158+
159+
// CPU extensions
160+
if (settings.CPUExtension != CPUExtensions.Default)
161+
{
162+
args.Add($"-march={settings.CPUExtension switch
163+
{
164+
CPUExtensions.SSE => "pentium3",
165+
CPUExtensions.SSE2 => "pentium4",
166+
CPUExtensions.AVX => "sandybridge",
167+
CPUExtensions.AVX2 => "haswell",
168+
CPUExtensions.AVX512 => "skylake-avx512",
169+
CPUExtensions.armv8_0 => "armv8-a",
170+
CPUExtensions.armv8_1 => "armv8.1-a",
171+
CPUExtensions.armv8_2 => "armv8.2-a",
172+
CPUExtensions.armv8_3 => "armv8.3-a",
173+
CPUExtensions.armv8_4 => "armv8.4-a",
174+
CPUExtensions.armv8_5 => "armv8.5-a",
175+
CPUExtensions.armv8_6 => "armv8.6-a",
176+
CPUExtensions.armv8_7 => "armv8.7-a",
177+
CPUExtensions.armv8_8 => "armv8.8-a",
178+
CPUExtensions.armv8_9 => "armv8.9-a",
179+
CPUExtensions.armv9_0 => "armv9-a",
180+
CPUExtensions.armv9_1 => "armv9.1-a",
181+
CPUExtensions.armv9_2 => "armv9.2-a",
182+
CPUExtensions.armv9_3 => "armv9.3-a",
183+
CPUExtensions.armv9_4 => "armv9.4-a",
184+
_ => "native"
185+
}}");
186+
}
187+
188+
// Optimization level
189+
args.Add(settings.Optimization switch
190+
{
191+
OptimizationLevel.None => "-O0",
192+
OptimizationLevel.Size => "-Os",
193+
OptimizationLevel.Speed => "-O2",
194+
OptimizationLevel.Max => "-O3",
195+
_ => "-O2"
196+
});
197+
198+
// Debug information - setup debug file names like MSVC does
199+
if (settings.EnableDebugFileCreation)
200+
{
201+
args.Add("-g"); // Generate debug information
202+
203+
// For GCC, debug info is embedded in the object file by default
204+
// But we can specify a separate debug file path if needed
205+
var debugFile = Path.ChangeExtension(outputFile, ".debug");
206+
if (!string.IsNullOrEmpty(Path.GetDirectoryName(debugFile)))
207+
{
208+
Directory.CreateDirectory(Path.GetDirectoryName(debugFile)!);
209+
}
210+
}
211+
212+
// Preprocessor definitions
213+
foreach (var def in settings.Definitions)
214+
{
215+
if (def.HasValue())
216+
args.Add($"-D{def.GetName()}={def.GetValue()}");
217+
else
218+
args.Add($"-D{def.GetName()}");
219+
}
220+
221+
// Include paths
222+
foreach (var includePath in settings.IncludePaths)
223+
{
224+
args.Add("-I");
225+
args.Add(includePath);
226+
}
227+
228+
// Force includes
229+
foreach (var forceInclude in settings.ForceIncludes)
230+
{
231+
args.Add("-include");
232+
args.Add(forceInclude);
233+
}
234+
235+
// Fast floating point operations
236+
if (settings.EnableFastFloatingPointOperations)
237+
{
238+
args.Add("-ffast-math");
239+
}
240+
241+
// Position independent code for shared libraries (required on many platforms)
242+
if (settings.ModuleType == ModuleType.SharedLibrary)
243+
{
244+
args.Add("-fPIC");
245+
}
246+
247+
// Additional custom flags
248+
args.AddRange(settings.OtherFlags);
249+
250+
// Source file (must be last)
251+
args.Add(settings.SourceFile);
252+
253+
254+
var startInfo = new ProcessStartInfo
255+
{
256+
FileName = compilerPath,
257+
Arguments = args.ToString(),
258+
RedirectStandardOutput = true,
259+
RedirectStandardError = true,
260+
UseShellExecute = false,
261+
CreateNoWindow = true,
262+
StandardErrorEncoding = System.Text.Encoding.UTF8,
263+
StandardOutputEncoding = System.Text.Encoding.UTF8,
264+
WorkingDirectory = Path.GetDirectoryName(settings.SourceFile) ?? Environment.CurrentDirectory
265+
};
266+
267+
var process = new Process { StartInfo = startInfo };
268+
process.ErrorDataReceived += (sender, e) =>
269+
{
270+
if (!string.IsNullOrEmpty(e.Data))
271+
{
272+
Console.Error.WriteLine(e.Data);
273+
}
274+
};
275+
process.OutputDataReceived += (sender, e) =>
276+
{
277+
if (!string.IsNullOrEmpty(e.Data))
278+
{
279+
Console.Out.WriteLine(e.Data);
280+
}
281+
};
282+
283+
process.Start();
284+
process.BeginErrorReadLine();
285+
process.BeginOutputReadLine();
286+
await process.WaitForExitAsync(cancellationToken);
287+
return process.ExitCode == 0;
288+
}
289+
}
290+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using ebuild.api;
2+
using ebuild.api.Compiler;
3+
4+
namespace ebuild.Compilers
5+
{
6+
public class GccCompilerFactory : ICompilerFactory
7+
{
8+
public string Name => "gcc";
9+
10+
public Type CompilerType => typeof(GccCompiler);
11+
12+
public bool CanCreate(ModuleBase module, IModuleInstancingParams instancingParams)
13+
{
14+
// GCC is available if it exists on PATH (works on Windows with MinGW/Cygwin too)
15+
return FindExecutable("gcc") != null;
16+
}
17+
18+
public CompilerBase CreateCompiler(ModuleBase module, IModuleInstancingParams instancingParams)
19+
{
20+
return new GccCompiler(instancingParams.Architecture);
21+
}
22+
23+
private static string? FindExecutable(string executableName)
24+
{
25+
var pathEnv = Environment.GetEnvironmentVariable("PATH");
26+
if (string.IsNullOrEmpty(pathEnv))
27+
return null;
28+
29+
var paths = pathEnv.Split(Path.PathSeparator);
30+
foreach (var path in paths)
31+
{
32+
var fullPath = Path.Combine(path, executableName);
33+
if (File.Exists(fullPath))
34+
return fullPath;
35+
36+
// Try with .exe extension on Windows
37+
var exePath = fullPath + ".exe";
38+
if (File.Exists(exePath))
39+
return exePath;
40+
}
41+
return null;
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)