Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ namespace NServiceBus.Persistence.Sql.ScriptBuilder
}
public class ScriptGenerator
{
public ScriptGenerator(string assemblyPath, string destinationDirectory, bool clean = true, bool overwrite = true, System.Collections.Generic.IReadOnlyList<NServiceBus.Persistence.Sql.ScriptBuilder.BuildSqlDialect>? dialectOptions = null, System.Func<string, string>? promotionFinder = null, System.Action<string, string>? logError = null) { }
public ScriptGenerator(string assemblyPath, string destinationDirectory, bool clean = true, bool overwrite = true, System.Collections.Generic.IReadOnlyList<NServiceBus.Persistence.Sql.ScriptBuilder.BuildSqlDialect>? dialectOptions = null, System.Collections.Generic.IReadOnlyList<string>? referencePaths = null, System.Func<string, string>? promotionFinder = null, System.Action<string, string>? logError = null) { }
public void Generate() { }
public static void Generate(string assemblyPath, string targetDirectory, System.Action<string, string> logError, System.Func<string, string> promotionPathFinder) { }
public static void Generate(string assemblyPath, string targetDirectory, string[] referencePaths, System.Action<string, string> logError, System.Func<string, string> promotionPathFinder) { }
}
public abstract class ScriptWriter
{
Expand Down
8 changes: 3 additions & 5 deletions src/ScriptBuilder/AttributeReading/SettingsAttributeReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,16 @@ public static Settings Read(Assembly assembly)
return ReadFromProperties(properties);
}

public static Settings ReadFromProperties(Dictionary<string, object?> properties)
{
return new Settings
public static Settings ReadFromProperties(Dictionary<string, object?> properties) =>
new()
{
BuildDialects = ReadBuildDialects(properties).ToList(),
BuildDialects = [.. ReadBuildDialects(properties)],
ScriptPromotionPath = ReadScriptPromotionPath(properties),
ProduceSagaScripts = GetBoolProperty(properties, "ProduceSagaScripts", true),
ProduceTimeoutScripts = GetBoolProperty(properties, "ProduceTimeoutScripts", true),
ProduceSubscriptionScripts = GetBoolProperty(properties, "ProduceSubscriptionScripts", true),
ProduceOutboxScripts = GetBoolProperty(properties, "ProduceOutboxScripts", true),
};
}

static bool GetBoolProperty(Dictionary<string, object?> properties, string key, bool defaultValue = false)
=> properties.GetValueOrDefault(key) as bool? ?? defaultValue;
Expand Down
38 changes: 34 additions & 4 deletions src/ScriptBuilder/ScriptGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class ScriptGenerator(string assemblyPath,
bool clean = true,
bool overwrite = true,
IReadOnlyList<BuildSqlDialect>? dialectOptions = null,
IReadOnlyList<string>? referencePaths = null,
Func<string, string>? promotionFinder = null,
Action<string, string>? logError = null)
{
Expand All @@ -24,6 +25,13 @@ public static void Generate(string assemblyPath, string targetDirectory,
writer.Generate();
}

public static void Generate(string assemblyPath, string targetDirectory, string[] referencePaths,
Action<string, string> logError, Func<string, string> promotionPathFinder)
{
var writer = new ScriptGenerator(assemblyPath, targetDirectory, referencePaths: referencePaths, promotionFinder: promotionPathFinder, logError: logError);
writer.Generate();
}

public void Generate()
{
if (clean)
Expand All @@ -39,11 +47,9 @@ public void Generate()
return;
}

var assemblyFiles = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll")
.Concat(Directory.GetFiles(assemblyFolderPath, "*.dll"))
.ToArray();
var assemblyFiles = CollectAssemblyFiles(assemblyFolderPath, referencePaths);
var resolver = new PathAssemblyResolver(assemblyFiles);
var metadataLoadContext = new MetadataLoadContext(resolver);
using var metadataLoadContext = new MetadataLoadContext(resolver);
var assembly = metadataLoadContext.LoadFromAssemblyPath(assemblyPath);

var settings = SettingsAttributeReader.Read(assembly);
Expand Down Expand Up @@ -148,4 +154,28 @@ void Promote(string replicationPath)

readonly string scriptBasePath = Path.Combine(destinationDirectory, "NServiceBus.Persistence.Sql");
readonly IReadOnlyList<BuildSqlDialect> dialectOptions = dialectOptions ?? [];

static IEnumerable<string> CollectAssemblyFiles(string assemblyFolderPath, IReadOnlyList<string>? referencePaths)
{
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var file in Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll").Where(f => seen.Add(Path.GetFileName(f))))
{
yield return file;
}

foreach (var file in Directory.EnumerateFiles(assemblyFolderPath, "*.dll", SearchOption.AllDirectories).Where(f => seen.Add(Path.GetFileName(f))))
{
yield return file;
}

if (referencePaths is null)
{
yield break;
}

foreach (var referencePath in referencePaths.Where(rp => File.Exists(rp) && seen.Add(Path.GetFileName(rp))))
{
yield return referencePath;
}
}
}
75 changes: 74 additions & 1 deletion src/ScriptBuilderTask.Tests/InnerTaskTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class InnerTaskTests
{
[Test]
public void IntegrationTest()
public void When_assembly_is_not_isolated_should_succeed_without_reference_paths()
{
var testDirectory = TestContext.CurrentContext.TestDirectory;
var temp = Path.Combine(testDirectory, "InnerTaskTemp");
Expand All @@ -26,6 +26,7 @@ public void IntegrationTest()
intermediateDirectory: intermediatePath,
projectDirectory: "TheProjectDir",
solutionDirectory: Path.Combine(temp, "PromotePath"),
referencePaths: [],
logError: (error, type) => throw new Exception(error));
innerTask.Execute();
var files = Directory.EnumerateFiles(temp, "*.*", SearchOption.AllDirectories)
Expand All @@ -36,4 +37,76 @@ public void IntegrationTest()

Approver.Verify(files);
}

[Test]
public void When_assembly_isolated_without_dependencies_should_fail_without_reference_paths()
{
var testDirectory = TestContext.CurrentContext.TestDirectory;
var temp = Path.Combine(testDirectory, "IsolatedAssemblyTest");
if (Directory.Exists(temp))
{
Directory.Delete(temp, true);
}

// Create an isolated folder with only the target assembly (no dependencies)
var isolatedAssemblyPath = Path.Combine(temp, "Isolated");
var intermediatePath = Path.Combine(temp, "IntermediatePath");
Directory.CreateDirectory(isolatedAssemblyPath);
Directory.CreateDirectory(intermediatePath);

var sourceAssembly = Path.Combine(testDirectory, "ScriptBuilderTask.Tests.Target.dll");
var isolatedAssembly = Path.Combine(isolatedAssemblyPath, "ScriptBuilderTask.Tests.Target.dll");
File.Copy(sourceAssembly, isolatedAssembly);

var innerTask = new InnerTask(
assemblyPath: isolatedAssembly,
intermediateDirectory: intermediatePath,
projectDirectory: "TheProjectDir",
solutionDirectory: Path.Combine(temp, "PromotePath"),
referencePaths: [], // Without reference paths, should fail because NServiceBus.Persistence.Sql.dll is not in the isolated folder
logError: static (_, _) => { });

Assert.That(() => innerTask.Execute(), Throws.TypeOf<FileNotFoundException>().And.Message.Contains("NServiceBus.Persistence.Sql"));
}

[Test]
public void When_assembly_isolated_without_dependencies_should_succeed_with_reference_paths()
{
var testDirectory = TestContext.CurrentContext.TestDirectory;
var temp = Path.Combine(testDirectory, "IsolatedAssemblyTestWithRefs");
if (Directory.Exists(temp))
{
Directory.Delete(temp, true);
}

// Create an isolated folder with only the target assembly (no dependencies)
var isolatedAssemblyPath = Path.Combine(temp, "Isolated");
var intermediatePath = Path.Combine(temp, "IntermediatePath");
Directory.CreateDirectory(isolatedAssemblyPath);
Directory.CreateDirectory(intermediatePath);

var sourceAssembly = Path.Combine(testDirectory, "ScriptBuilderTask.Tests.Target.dll");
var isolatedAssembly = Path.Combine(isolatedAssemblyPath, "ScriptBuilderTask.Tests.Target.dll");
File.Copy(sourceAssembly, isolatedAssembly);

// With reference paths pointing to the actual dependency location, should succeed
string[] referencePaths =
[
Path.Combine(testDirectory, "NServiceBus.Persistence.Sql.dll"),
Path.Combine(testDirectory, "NServiceBus.Persistence.Sql.dll") // deliberately adding duplicates
];

var innerTask = new InnerTask(
assemblyPath: isolatedAssembly,
intermediateDirectory: intermediatePath,
projectDirectory: "TheProjectDir",
solutionDirectory: Path.Combine(temp, "PromotePath"),
referencePaths: referencePaths,
logError: static (error, _) => throw new Exception(error));

Assert.DoesNotThrow(() => innerTask.Execute());

var files = Directory.EnumerateFiles(intermediatePath, "*.sql", SearchOption.AllDirectories);
Assert.That(files, Is.Not.Empty, "Should have generated SQL scripts");
}
}
27 changes: 3 additions & 24 deletions src/ScriptBuilderTask/InnerTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,9 @@
using System.IO;
using NServiceBus.Persistence.Sql;

class InnerTask
class InnerTask(string assemblyPath, string intermediateDirectory, string projectDirectory, string solutionDirectory, string[] referencePaths, Action<string, string> logError)
{
string assemblyPath;
string intermediateDirectory;
string projectDirectory;
string solutionDirectory;
Action<string, string> logError;

public InnerTask(string assemblyPath, string intermediateDirectory, string projectDirectory, string solutionDirectory, Action<string, string> logError)
{
this.assemblyPath = assemblyPath;
this.intermediateDirectory = intermediateDirectory;
this.projectDirectory = projectDirectory;
this.solutionDirectory = solutionDirectory;
this.logError = logError;
}

public void Execute()
{
ScriptGenerator.Generate(assemblyPath, intermediateDirectory, logError, FindPromotionPath);
}
public void Execute() => ScriptGenerator.Generate(assemblyPath, intermediateDirectory, referencePaths, logError, FindPromotionPath);

string FindPromotionPath(string promotionPath)
{
Expand Down Expand Up @@ -51,8 +33,5 @@ string FindPromotionPath(string promotionPath)
return promotionPath;
}

static string GetFullPathWithEndingSlashes(string input)
{
return input.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
}
static string GetFullPathWithEndingSlashes(string input) => input.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
}
3 changes: 2 additions & 1 deletion src/ScriptBuilderTask/NServiceBus.Persistence.Sql.targets
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
AssemblyPath="$(ProjectDir)@(IntermediateAssembly)"
IntermediateDirectory="$(ProjectDir)$(IntermediateOutputPath)"
ProjectDirectory="$(ProjectDir)"
SolutionDirectory="$(SqlPersistenceSolutionDir)" />
SolutionDirectory="$(SqlPersistenceSolutionDir)"
ReferencePath="@(ReferencePath)" />
</Target>

<Target Name="SqlPersistenceAddScriptsToContent" BeforeTargets="GetCopyToOutputDirectoryItems;GetCopyToPublishDirectoryItems" AfterTargets="SqlPersistenceScriptBuilder" Condition="'$(SqlPersistenceGenerateScripts)' != 'false' AND '$(DesignTimeBuild)' != 'true'">
Expand Down
13 changes: 7 additions & 6 deletions src/ScriptBuilderTask/SqlPersistenceScriptBuilderTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection;
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

Expand All @@ -22,7 +23,9 @@ public class SqlPersistenceScriptBuilderTask : Task

public string SolutionDirectory { get; set; }

static Version assemblyVersion = typeof(SqlPersistenceScriptBuilderTask).GetTypeInfo().Assembly.GetName().Version;
public ITaskItem[] ReferencePath { get; set; }

static readonly Version assemblyVersion = typeof(SqlPersistenceScriptBuilderTask).GetTypeInfo().Assembly.GetName().Version;

public override bool Execute()
{
Expand All @@ -34,11 +37,9 @@ public override bool Execute()
try
{
ValidateInputs();
var innerTask = new InnerTask(AssemblyPath, IntermediateDirectory, ProjectDirectory, SolutionDirectory,
logError: (error, file) =>
{
logger.LogError(error, file);
});
var referencePaths = ReferencePath?.Select(item => item.ItemSpec).ToArray() ?? [];
var innerTask = new InnerTask(AssemblyPath, IntermediateDirectory, ProjectDirectory, SolutionDirectory, referencePaths,
logError: (error, file) => logger.LogError(error, file));
innerTask.Execute();
}
catch (ErrorsException exception)
Expand Down