From 52f243afef412289c22b523050ded0e8d521a83d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Oct 2025 07:07:34 +0000
Subject: [PATCH 1/3] Initial plan
From ba9369ad17c50f8622e4d48d5dba5a8159179853 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Oct 2025 07:18:58 +0000
Subject: [PATCH 2/3] Migrate CompilationDatabase from JSON to SQLite
Co-authored-by: yldrefruz <30903352+yldrefruz@users.noreply.github.com>
---
ebuild.Tests/Unit/CompilationDatabaseTests.cs | 15 +-
.../Modules/BuildGraph/CompilationDatabase.cs | 136 ++++++++++++++----
ebuild/ebuild.csproj | 1 +
3 files changed, 116 insertions(+), 36 deletions(-)
diff --git a/ebuild.Tests/Unit/CompilationDatabaseTests.cs b/ebuild.Tests/Unit/CompilationDatabaseTests.cs
index e8769ac..145898a 100644
--- a/ebuild.Tests/Unit/CompilationDatabaseTests.cs
+++ b/ebuild.Tests/Unit/CompilationDatabaseTests.cs
@@ -109,21 +109,22 @@ public void CreateFromSettings_ShouldCreateValidEntry()
}
[Test]
- public void GetEntry_WithCorruptedFile_ShouldReturnNull()
+ public void GetEntry_WithCorruptedDatabase_ShouldReturnNull()
{
// Arrange
- var database = CompilationDatabase.Get(_testDir, "TestModule", "test.cpp");
-
- // Create corrupted file
var dbDir = Path.Combine(_testDir, ".ebuild", "TestModule");
Directory.CreateDirectory(dbDir);
- var dbFile = Path.Combine(dbDir, "test.compile.json");
- File.WriteAllText(dbFile, "corrupted json content");
+ var dbFile = Path.Combine(dbDir, "compilation.db");
+
+ // Create corrupted database file (not a valid SQLite database)
+ File.WriteAllText(dbFile, "corrupted database content");
+
+ var database = CompilationDatabase.Get(_testDir, "TestModule", "test.cpp");
// Act
var entry = database.GetEntry();
- // Assert
+ // Assert - Should return null when database is corrupted
Assert.That(entry, Is.Null);
}
diff --git a/ebuild/Modules/BuildGraph/CompilationDatabase.cs b/ebuild/Modules/BuildGraph/CompilationDatabase.cs
index dbe5e2d..f557369 100644
--- a/ebuild/Modules/BuildGraph/CompilationDatabase.cs
+++ b/ebuild/Modules/BuildGraph/CompilationDatabase.cs
@@ -1,27 +1,25 @@
using System.Security.Cryptography;
using System.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
using ebuild.api.Compiler;
+using Microsoft.Data.Sqlite;
namespace ebuild.Modules.BuildGraph;
///
-/// Manages compilation state tracking for incremental builds
+/// Manages compilation state tracking for incremental builds using SQLite
///
public class CompilationDatabase
{
private static readonly Dictionary _dbCache = [];
-#pragma warning disable IDE0052 // Fields are used for serialization
private readonly string _databasePath;
private readonly string _sourceFile;
+ private readonly string _moduleName;
private CompilationEntry? _cached;
-#pragma warning restore IDE0052
private CompilationDatabase(string moduleDirectory, string moduleName, string sourceFile)
{
- var dbDir = Path.Combine(moduleDirectory, ".ebuild", moduleName, "compdb");
+ var dbDir = Path.Combine(moduleDirectory, ".ebuild", moduleName);
try
{
Directory.CreateDirectory(dbDir);
@@ -32,26 +30,49 @@ private CompilationDatabase(string moduleDirectory, string moduleName, string so
// This allows the class to be constructed but operations will fail gracefully
}
- var sourceFileName = Path.GetFileNameWithoutExtension(sourceFile);
- var hash = SHA256.HashData(Encoding.UTF8.GetBytes(sourceFile));
- var hexHash = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
- _databasePath = Path.Combine(dbDir, $"{sourceFileName}-${hexHash}.compile.json");
+ _databasePath = Path.Combine(dbDir, "compilation.db");
_sourceFile = sourceFile;
+ _moduleName = moduleName;
+
+ InitializeDatabase();
+ }
+
+ private void InitializeDatabase()
+ {
+ try
+ {
+ using var connection = new SqliteConnection($"Data Source={_databasePath}");
+ connection.Open();
+
+ var command = connection.CreateCommand();
+ command.CommandText = @"
+ CREATE TABLE IF NOT EXISTS compilation_entries (
+ source_file TEXT PRIMARY KEY,
+ output_file TEXT NOT NULL,
+ last_compiled TEXT NOT NULL,
+ definitions TEXT NOT NULL,
+ include_paths TEXT NOT NULL,
+ force_includes TEXT NOT NULL,
+ dependencies TEXT NOT NULL
+ )";
+ command.ExecuteNonQuery();
+ }
+ catch
+ {
+ // Ignore initialization errors - operations will fail gracefully
+ }
}
public static CompilationDatabase Get(string moduleDirectory, string moduleName, string sourceFile)
{
- var dbDir = Path.Combine(moduleDirectory, ".ebuild", moduleName, "compdb");
- var sourceFileName = Path.GetFileNameWithoutExtension(sourceFile);
- var hash = SHA256.HashData(Encoding.UTF8.GetBytes(sourceFile));
- var hexHash = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
- var dbPath = Path.Combine(dbDir, $"{sourceFileName}-${hexHash}.compile.json");
+ var dbPath = Path.Combine(moduleDirectory, ".ebuild", moduleName, "compilation.db");
+ var cacheKey = $"{dbPath}:{sourceFile}";
lock (_dbCache)
{
- if (_dbCache.TryGetValue(dbPath, out var db))
+ if (_dbCache.TryGetValue(cacheKey, out var db))
return db;
db = new CompilationDatabase(moduleDirectory, moduleName, sourceFile);
- _dbCache[dbPath] = db;
+ _dbCache[cacheKey] = db;
return db;
}
}
@@ -61,14 +82,35 @@ public static CompilationDatabase Get(string moduleDirectory, string moduleName,
if (_cached != null)
return _cached;
- if (!File.Exists(_databasePath))
- return null;
-
try
{
- var json = File.ReadAllText(_databasePath);
- _cached = JsonSerializer.Deserialize(json);
- return _cached;
+ using var connection = new SqliteConnection($"Data Source={_databasePath}");
+ connection.Open();
+
+ var command = connection.CreateCommand();
+ command.CommandText = @"
+ SELECT source_file, output_file, last_compiled, definitions, include_paths, force_includes, dependencies
+ FROM compilation_entries
+ WHERE source_file = $sourceFile";
+ command.Parameters.AddWithValue("$sourceFile", _sourceFile);
+
+ using var reader = command.ExecuteReader();
+ if (reader.Read())
+ {
+ _cached = new CompilationEntry
+ {
+ SourceFile = reader.GetString(0),
+ OutputFile = reader.GetString(1),
+ LastCompiled = DateTime.Parse(reader.GetString(2)),
+ Definitions = DeserializeList(reader.GetString(3)),
+ IncludePaths = DeserializeList(reader.GetString(4)),
+ ForceIncludes = DeserializeList(reader.GetString(5)),
+ Dependencies = DeserializeList(reader.GetString(6))
+ };
+ return _cached;
+ }
+
+ return null;
}
catch
{
@@ -80,8 +122,24 @@ public void SaveEntry(CompilationEntry entry)
{
try
{
- var json = JsonSerializer.Serialize(entry, new JsonSerializerOptions { WriteIndented = true });
- File.WriteAllText(_databasePath, json);
+ using var connection = new SqliteConnection($"Data Source={_databasePath}");
+ connection.Open();
+
+ var command = connection.CreateCommand();
+ command.CommandText = @"
+ INSERT OR REPLACE INTO compilation_entries
+ (source_file, output_file, last_compiled, definitions, include_paths, force_includes, dependencies)
+ VALUES ($sourceFile, $outputFile, $lastCompiled, $definitions, $includePaths, $forceIncludes, $dependencies)";
+
+ command.Parameters.AddWithValue("$sourceFile", entry.SourceFile);
+ command.Parameters.AddWithValue("$outputFile", entry.OutputFile);
+ command.Parameters.AddWithValue("$lastCompiled", entry.LastCompiled.ToString("o"));
+ command.Parameters.AddWithValue("$definitions", SerializeList(entry.Definitions));
+ command.Parameters.AddWithValue("$includePaths", SerializeList(entry.IncludePaths));
+ command.Parameters.AddWithValue("$forceIncludes", SerializeList(entry.ForceIncludes));
+ command.Parameters.AddWithValue("$dependencies", SerializeList(entry.Dependencies));
+
+ command.ExecuteNonQuery();
_cached = entry;
}
catch
@@ -94,10 +152,14 @@ public void RemoveEntry()
{
try
{
- if (File.Exists(_databasePath))
- {
- File.Delete(_databasePath);
- }
+ using var connection = new SqliteConnection($"Data Source={_databasePath}");
+ connection.Open();
+
+ var command = connection.CreateCommand();
+ command.CommandText = "DELETE FROM compilation_entries WHERE source_file = $sourceFile";
+ command.Parameters.AddWithValue("$sourceFile", _sourceFile);
+ command.ExecuteNonQuery();
+
_cached = null;
}
catch
@@ -106,6 +168,22 @@ public void RemoveEntry()
}
}
+ private static string SerializeList(List list)
+ {
+ return string.Join("\n", list.Select(s => Convert.ToBase64String(Encoding.UTF8.GetBytes(s))));
+ }
+
+ private static List DeserializeList(string serialized)
+ {
+ if (string.IsNullOrEmpty(serialized))
+ return new List();
+
+ return serialized.Split('\n')
+ .Where(s => !string.IsNullOrEmpty(s))
+ .Select(s => Encoding.UTF8.GetString(Convert.FromBase64String(s)))
+ .ToList();
+ }
+
public static CompilationEntry CreateFromSettings(CompilerSettings settings, string outputFile)
{
return new CompilationEntry
diff --git a/ebuild/ebuild.csproj b/ebuild/ebuild.csproj
index 48ccdf9..628b212 100644
--- a/ebuild/ebuild.csproj
+++ b/ebuild/ebuild.csproj
@@ -8,6 +8,7 @@
+
From 4bec44ee7ca52619e57cd26f7f463cc6ca22b814 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Oct 2025 07:20:39 +0000
Subject: [PATCH 3/3] Add test for multiple source files using same SQLite
database
Co-authored-by: yldrefruz <30903352+yldrefruz@users.noreply.github.com>
---
ebuild.Tests/Unit/CompilationDatabaseTests.cs | 43 +++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/ebuild.Tests/Unit/CompilationDatabaseTests.cs b/ebuild.Tests/Unit/CompilationDatabaseTests.cs
index 145898a..3c76920 100644
--- a/ebuild.Tests/Unit/CompilationDatabaseTests.cs
+++ b/ebuild.Tests/Unit/CompilationDatabaseTests.cs
@@ -185,4 +185,47 @@ public void RemoveEntry_WithInvalidPath_ShouldNotThrow()
// Act & Assert
Assert.DoesNotThrow(() => database.RemoveEntry());
}
+
+ [Test]
+ public void MultipleSourceFiles_ShouldUseSameDatabase()
+ {
+ // Arrange - Create entries for multiple source files in the same module
+ var database1 = CompilationDatabase.Get(_testDir, "TestModule", "file1.cpp");
+ var database2 = CompilationDatabase.Get(_testDir, "TestModule", "file2.cpp");
+
+ var entry1 = new CompilationEntry
+ {
+ SourceFile = "file1.cpp",
+ OutputFile = "file1.obj",
+ LastCompiled = DateTime.UtcNow,
+ Definitions = new List { "FILE1" }
+ };
+
+ var entry2 = new CompilationEntry
+ {
+ SourceFile = "file2.cpp",
+ OutputFile = "file2.obj",
+ LastCompiled = DateTime.UtcNow,
+ Definitions = new List { "FILE2" }
+ };
+
+ // Act - Save both entries
+ database1.SaveEntry(entry1);
+ database2.SaveEntry(entry2);
+
+ // Assert - Both entries should be retrievable
+ var retrieved1 = database1.GetEntry();
+ var retrieved2 = database2.GetEntry();
+
+ Assert.That(retrieved1, Is.Not.Null);
+ Assert.That(retrieved2, Is.Not.Null);
+ Assert.That(retrieved1!.SourceFile, Is.EqualTo("file1.cpp"));
+ Assert.That(retrieved2!.SourceFile, Is.EqualTo("file2.cpp"));
+ Assert.That(retrieved1.Definitions, Contains.Item("FILE1"));
+ Assert.That(retrieved2.Definitions, Contains.Item("FILE2"));
+
+ // Verify they use the same database file
+ var dbPath = Path.Combine(_testDir, ".ebuild", "TestModule", "compilation.db");
+ Assert.That(File.Exists(dbPath), Is.True, "Database file should exist");
+ }
}
\ No newline at end of file