Skip to content
Merged
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
63 changes: 61 additions & 2 deletions docs/guide/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ As of Wolverine 5.0, you now have the ability to better control the usage of the
code generation to potentially avoid unwanted usage:

<!-- snippet: sample_configuring_ServiceLocationPolicy -->
<a id='snippet-sample_configuring_servicelocationpolicy'></a>
<a id='snippet-sample_configuring_ServiceLocationPolicy'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
Expand All @@ -225,7 +225,7 @@ builder.UseWolverine(opts =>
opts.ServiceLocationPolicy = ServiceLocationPolicy.NotAllowed;
});
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/ServiceLocationUsage.cs#L11-L33' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_configuring_servicelocationpolicy' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/ServiceLocationUsage.cs#L11-L33' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_configuring_ServiceLocationPolicy' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

::: note
Expand Down Expand Up @@ -393,3 +393,62 @@ Which will use:

1. `TypeLoadMode.Dynamic` when the .NET environment is "Development" and dynamically generate types on the first usage
2. `TypeLoadMode.Static` for other .NET environments for optimized cold start times

## Customizing the Generated Code Output Path

By default, Wolverine writes generated code to `Internal/Generated` under your project's content root.
For Console applications or non-standard project structures, you may need to customize this path.

### Using CritterStackDefaults

You can configure the output path globally for all Critter Stack tools:

<!-- snippet: sample_configure_generated_code_output_path -->
<a id='snippet-sample_configure_generated_code_output_path'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.Services.CritterStackDefaults(opts =>
{
// Set a custom output path for generated code
opts.GeneratedCodeOutputPath = "/path/to/your/project/Internal/Generated";
});
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/CodegenUsage.cs#L89-L98' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_configure_generated_code_output_path' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

### Auto-Resolving Project Root for Console Apps

Console applications often have `ContentRootPath` pointing to the `bin` folder, which causes
generated code to be written to the wrong location. Enable automatic project root resolution:

<!-- snippet: sample_auto_resolve_project_root -->
<a id='snippet-sample_auto_resolve_project_root'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.Services.CritterStackDefaults(opts =>
{
// Automatically find the project root by looking for .csproj/.sln files
// Useful for Console apps where ContentRootPath defaults to bin folder
opts.AutoResolveProjectRoot = true;
});
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/CodegenUsage.cs#L103-L113' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_auto_resolve_project_root' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

### Direct Wolverine Configuration

You can also configure the path directly on Wolverine:

<!-- snippet: sample_direct_wolverine_output_path -->
<a id='snippet-sample_direct_wolverine_output_path'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
opts.CodeGeneration.GeneratedCodeOutputPath = "/path/to/output";
});
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/CodegenUsage.cs#L118-L126' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_direct_wolverine_output_path' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Note that explicit Wolverine configuration takes precedence over `CritterStackDefaults`.
42 changes: 42 additions & 0 deletions src/Samples/DocumentationSamples/CodegenUsage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,46 @@ public async Task use_optimized_workflow()

#endregion
}

public async Task configure_generated_code_output_path()
{
#region sample_configure_generated_code_output_path

var builder = Host.CreateApplicationBuilder();
builder.Services.CritterStackDefaults(opts =>
{
// Set a custom output path for generated code
opts.GeneratedCodeOutputPath = "/path/to/your/project/Internal/Generated";
});

#endregion
}

public async Task auto_resolve_project_root()
{
#region sample_auto_resolve_project_root

var builder = Host.CreateApplicationBuilder();
builder.Services.CritterStackDefaults(opts =>
{
// Automatically find the project root by looking for .csproj/.sln files
// Useful for Console apps where ContentRootPath defaults to bin folder
opts.AutoResolveProjectRoot = true;
});

#endregion
}

public async Task direct_wolverine_output_path()
{
#region sample_direct_wolverine_output_path

var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
opts.CodeGeneration.GeneratedCodeOutputPath = "/path/to/output";
});

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using JasperFx;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;

namespace CoreTests.Configuration;

public class generated_code_output_path_configuration
{
[Fact]
public void wolverine_options_should_use_jasperfx_generated_code_output_path()
{
var jasperfx = new JasperFxOptions
{
GeneratedCodeOutputPath = "/custom/path/Generated",
};

var wolverineOptions = new WolverineOptions();
wolverineOptions.ReadJasperFxOptions(jasperfx);

wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
.ShouldBe("/custom/path/Generated");
}

[Fact]
public void wolverine_options_should_not_override_explicit_generated_code_output_path()
{
var jasperfx = new JasperFxOptions
{
GeneratedCodeOutputPath = "/jasperfx/path",
};

var wolverineOptions = new WolverineOptions();
wolverineOptions.CodeGeneration.GeneratedCodeOutputPath = "/explicit/path";
wolverineOptions.ReadJasperFxOptions(jasperfx);

// Should keep explicit setting
wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
.ShouldBe("/explicit/path");
}

[Fact]
public async Task critter_stack_defaults_generated_code_output_path_flows_to_wolverine()
{
using var host = await Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.CritterStackDefaults(opts =>
{
opts.GeneratedCodeOutputPath = "/test/output/path";
});
})
.UseWolverine()
.StartAsync();

var wolverineOptions = host.Services.GetRequiredService<WolverineOptions>();
wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
.ShouldBe("/test/output/path");
}

[Fact]
public async Task explicit_wolverine_path_takes_precedence_over_critter_stack_defaults()
{
using var host = await Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.CritterStackDefaults(opts =>
{
opts.GeneratedCodeOutputPath = "/jasperfx/path";
});
})
.UseWolverine(opts =>
{
opts.CodeGeneration.GeneratedCodeOutputPath = "/explicit/wolverine/path";
})
.StartAsync();

var wolverineOptions = host.Services.GetRequiredService<WolverineOptions>();
wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
.ShouldBe("/explicit/wolverine/path");
}

[Fact]
public void wolverine_options_should_not_copy_null_generated_code_output_path()
{
var jasperfx = new JasperFxOptions
{
// GeneratedCodeOutputPath is null by default
};

var wolverineOptions = new WolverineOptions();
var defaultPath = wolverineOptions.CodeGeneration.GeneratedCodeOutputPath;
wolverineOptions.ReadJasperFxOptions(jasperfx);

// Should keep the default path when JasperFxOptions has null
wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
.ShouldBe(defaultPath);
}
}
47 changes: 34 additions & 13 deletions src/Wolverine/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,43 @@ internal static IServiceCollection AddWolverine(this IServiceCollection services
var environment = s.GetService<IHostEnvironment>();
var directory = environment?.ContentRootPath ?? AppContext.BaseDirectory;

#if DEBUG
if (directory.EndsWith("Debug", StringComparison.OrdinalIgnoreCase))
{
directory = directory.ParentDirectory()!.ParentDirectory();
}
else if (directory.ParentDirectory()!.EndsWith("Debug", StringComparison.OrdinalIgnoreCase))
// Don't correct for the path if it's already been set (from JasperFxOptions or user)
if (options.CodeGeneration.GeneratedCodeOutputPath == "Internal/Generated")
{
directory = directory.ParentDirectory()!.ParentDirectory()!.ParentDirectory();
}
#if DEBUG
// In DEBUG builds, try to resolve project root like JasperFx does during codegen
if (jasperfx.AutoResolveProjectRoot)
{
var resolvedRoot = JasperFxOptions.ResolveProjectRoot(directory);
if (resolvedRoot != null)
{
directory = resolvedRoot;
}
}
else
{
// Legacy behavior for backward compatibility when AutoResolveProjectRoot is false
if (directory.EndsWith("Debug", StringComparison.OrdinalIgnoreCase))
{
directory = directory.ParentDirectory()!.ParentDirectory();
}
else if (directory.ParentDirectory()!.EndsWith("Debug", StringComparison.OrdinalIgnoreCase))
{
directory = directory.ParentDirectory()!.ParentDirectory()!.ParentDirectory();
}
}
#endif

// Don't correct for the path if it's already been set
if (options.CodeGeneration.GeneratedCodeOutputPath == "Internal/Generated")
{
options.CodeGeneration.GeneratedCodeOutputPath =
directory!.AppendPath("Internal", "Generated");
// Use JasperFxOptions path if set, otherwise use the resolved directory
if (jasperfx.GeneratedCodeOutputPath != null)
{
options.CodeGeneration.GeneratedCodeOutputPath = jasperfx.GeneratedCodeOutputPath;
}
else
{
options.CodeGeneration.GeneratedCodeOutputPath =
directory!.AppendPath("Internal", "Generated");
}
}

return options;
Expand Down
6 changes: 6 additions & 0 deletions src/Wolverine/WolverineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,12 @@ internal void ReadJasperFxOptions(JasperFxOptions jasperfx)
{
_autoBuildMessageStorageOnStartup = jasperfx.ActiveProfile.ResourceAutoCreate;
}

// Propagate GeneratedCodeOutputPath from JasperFxOptions if not explicitly set
if (CodeGeneration.GeneratedCodeOutputPath == "Internal/Generated" && jasperfx.GeneratedCodeOutputPath != null)
{
CodeGeneration.GeneratedCodeOutputPath = jasperfx.GeneratedCodeOutputPath;
}
}

public void RegisterMessageType(Type messageType, string messageAlias)
Expand Down
Loading