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