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
195 changes: 168 additions & 27 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,174 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net

name: Publish

on: [workflow_dispatch]
on:
workflow_dispatch:

jobs:
build:
permissions:
# Settings > Actions > General must allow GitHub Actions to create pull requests.
contents: write
pull-requests: write

runs-on: ubuntu-latest
concurrency:
group: publish
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup .NET
id: setup-dotnet
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
10.0.x
- name: Select .NET 10 SDK
run: dotnet new globaljson --sdk-version "${{ steps.setup-dotnet.outputs.dotnet-version }}" --roll-forward latestPatch --force
- name: Show .NET SDK
run: dotnet --version
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --configuration Release --no-build --verbosity normal
- name: Publish
run: dotnet nuget push "**/bin/Release/*.nupkg" --source "https://api.nuget.org/v3/index.json" --api-key "${{ secrets.NUGET_API_KEY }}"
- name: Require the default branch
shell: bash
run: |
if [[ "${GITHUB_REF_NAME}" != "${{ github.event.repository.default_branch }}" ]]; then
echo "Releases must run from ${{ github.event.repository.default_branch }}, not ${GITHUB_REF_NAME}." >&2
exit 1
fi

- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup .NET
id: setup-dotnet
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
10.0.x

- name: Select .NET 10 SDK
run: dotnet new globaljson --sdk-version "${{ steps.setup-dotnet.outputs.dotnet-version }}" --roll-forward latestPatch --force

- name: Show .NET SDK
run: dotnet --version

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --configuration Release --no-restore -p:PackageOutputPath="$GITHUB_WORKSPACE/artifacts/packages"

- name: Test
run: dotnet test --configuration Release --no-build --verbosity normal

- name: Test release automation
shell: pwsh
run: ./build/tests/Test-ReleaseAutomation.ps1

- name: Validate release artifacts
id: package
shell: pwsh
run: >-
./build/Get-ReleaseArtifacts.ps1
-SearchRoot artifacts/packages
-StagingDirectory "artifacts/release-${{ github.run_id }}"
-GitHubOutput $env:GITHUB_OUTPUT

- name: Validate release metadata
shell: bash
env:
VERSION: ${{ steps.package.outputs.version }}
PRERELEASE: ${{ steps.package.outputs.prerelease }}
run: |
if [[ -z "$VERSION" ]]; then
echo "Release version output is empty." >&2
exit 1
fi

if [[ "$PRERELEASE" == "true" && "$VERSION" != *-* ]]; then
echo "Version $VERSION is marked prerelease but has no prerelease suffix." >&2
exit 1
fi

if [[ "$PRERELEASE" != "true" && "$VERSION" == *-* ]]; then
echo "Version $VERSION has a prerelease suffix but is not marked prerelease." >&2
exit 1
fi

echo "Validated release tag/version metadata for $VERSION."

- name: Publish packages
shell: bash
run: |
for package in "${{ steps.package.outputs.directory }}"/*.nupkg "${{ steps.package.outputs.directory }}"/*.snupkg; do
dotnet nuget push "$package" \
--source "https://api.nuget.org/v3/index.json" \
--api-key "${{ secrets.NUGET_API_KEY }}" \
--skip-duplicate
done

- name: Create or verify GitHub release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.package.outputs.version }}
PRERELEASE: ${{ steps.package.outputs.prerelease }}
ARTIFACT_DIRECTORY: ${{ steps.package.outputs.directory }}
run: |
if gh release view "$VERSION" >/dev/null 2>&1; then
git fetch --force origin "refs/tags/$VERSION:refs/tags/$VERSION"
target=$(git rev-list -n 1 "$VERSION")
if [[ "$target" != "$GITHUB_SHA" ]]; then
echo "Release $VERSION already exists for $target, not $GITHUB_SHA." >&2
exit 1
fi
gh release upload "$VERSION" "$ARTIFACT_DIRECTORY"/* --clobber
else
options=(--target "$GITHUB_SHA" --title "$VERSION" --generate-notes)
if [[ "$PRERELEASE" == "true" ]]; then
options+=(--prerelease)
fi
gh release create "$VERSION" "$ARTIFACT_DIRECTORY"/* "${options[@]}"
fi

- name: Promote public API baselines
if: steps.package.outputs.prerelease != 'true'
shell: pwsh
run: ./build/Promote-PublicApi.ps1

# Pull requests opened by GITHUB_TOKEN do not start another workflow run,
# so validate the exact promoted tree before committing it.
- name: Validate promoted baselines
if: steps.package.outputs.prerelease != 'true'
run: dotnet build --configuration Release --no-restore

- name: Open public API promotion pull request
if: steps.package.outputs.prerelease != 'true'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.package.outputs.version }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
branch="automation/public-api-$VERSION"
if git diff --quiet -- '*/PublicAPI.Shipped.txt' '*/PublicAPI.Unshipped.txt'; then
echo "Public API baselines are already promoted."
exit 0
fi

existing=$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // empty')
if [[ -n "$existing" ]]; then
echo "Promotion pull request already exists: $existing"
exit 0
fi

if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
echo "Remote branch $branch already exists without an open pull request." >&2
exit 1
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "$branch"
git add -- '*/PublicAPI.Shipped.txt' '*/PublicAPI.Unshipped.txt'
git commit -m "Mark public APIs shipped in $VERSION"
git push origin "HEAD:refs/heads/$branch"
gh pr create \
--base "$DEFAULT_BRANCH" \
--head "$branch" \
--title "Mark public APIs shipped in $VERSION" \
--body "Promotes the public API baselines after the [$VERSION release]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$VERSION)."

- name: Skip baseline promotion for prerelease
if: steps.package.outputs.prerelease == 'true'
run: echo "Prerelease detected; skipping public API baseline promotion."
5 changes: 5 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<ItemGroup Condition="'$(IsPackable)' == 'true'">
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="5.6.0" PrivateAssets="all" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#nullable enable
System.Threading.Tasks.Flow.IDefaultTaskFlowFactory
System.Threading.Tasks.Flow.IDefaultTaskFlowFactory.Create(System.Threading.Tasks.Flow.TaskFlowOptions! options) -> System.Threading.Tasks.Flow.ITaskFlow!
System.Threading.Tasks.Flow.INamedConfigureTaskFlowChain
System.Threading.Tasks.Flow.INamedConfigureTaskFlowChain.ConfigureChain(System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler) -> System.Threading.Tasks.Flow.ITaskScheduler!
System.Threading.Tasks.Flow.INamedConfigureTaskFlowChain.Name.get -> string!
System.Threading.Tasks.Flow.INamedConfigureTaskFlowOptions
System.Threading.Tasks.Flow.INamedConfigureTaskFlowOptions.Configure() -> System.Threading.Tasks.Flow.TaskFlowOptions!
System.Threading.Tasks.Flow.INamedConfigureTaskFlowOptions.Name.get -> string!
System.Threading.Tasks.Flow.INamedTaskFlowFactory
System.Threading.Tasks.Flow.INamedTaskFlowFactory.Create(System.Threading.Tasks.Flow.TaskFlowOptions! options) -> System.Threading.Tasks.Flow.ITaskFlow!
System.Threading.Tasks.Flow.INamedTaskFlowFactory.Name.get -> string!
System.Threading.Tasks.Flow.ITaskFlowFactory
System.Threading.Tasks.Flow.ITaskFlowFactory.CreateTaskFlow(string? name = null) -> System.Threading.Tasks.Flow.ITaskFlow!
System.Threading.Tasks.Flow.ServiceCollectionExtensions
System.Threading.Tasks.Flow.TaskFlowFactory
System.Threading.Tasks.Flow.TaskFlowFactory.CreateTaskFlow(string? name = null) -> System.Threading.Tasks.Flow.ITaskFlow!
System.Threading.Tasks.Flow.TaskFlowFactory.TaskFlowFactory(System.Collections.Generic.IEnumerable<System.Threading.Tasks.Flow.INamedTaskFlowFactory!>! namedTaskFlowFactories, System.Collections.Generic.IEnumerable<System.Threading.Tasks.Flow.INamedConfigureTaskFlowChain!>! namedConfigureTaskFlowChains, System.Collections.Generic.IEnumerable<System.Threading.Tasks.Flow.INamedConfigureTaskFlowOptions!>! namedConfigureTaskFlowOptions, System.Threading.Tasks.Flow.IDefaultTaskFlowFactory! defaultTaskFlowFactory) -> void
static System.Threading.Tasks.Flow.ServiceCollectionExtensions.AddTaskFlow(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
static System.Threading.Tasks.Flow.ServiceCollectionExtensions.AddTaskFlow(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! name, System.Threading.Tasks.Flow.TaskFlowOptions! options) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
static System.Threading.Tasks.Flow.ServiceCollectionExtensions.AddTaskFlow(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string? name, System.Func<System.IServiceProvider!, System.Threading.Tasks.Flow.TaskFlowOptions!, System.Threading.Tasks.Flow.ITaskFlow!>? baseTaskFlowFactory = null, System.Func<System.IServiceProvider!, System.Threading.Tasks.Flow.TaskFlowOptions!>? configureOptions = null, System.Func<System.Threading.Tasks.Flow.ITaskScheduler!, System.IServiceProvider!, System.Threading.Tasks.Flow.ITaskScheduler!>? configureSchedulerChain = null) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
static System.Threading.Tasks.Flow.ServiceCollectionExtensions.AddTaskFlow(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Threading.Tasks.Flow.TaskFlowOptions! options) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
17 changes: 17 additions & 0 deletions TaskFlow.Extensions.Microsoft.Logging/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable
System.Threading.Tasks.Flow.LoggingTaskSchedulerExtensions
System.Threading.Tasks.Flow.TaskFlowLoggingOptions
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.CancellationRequestedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.CancellationRequestedLogLevel.set -> void
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.EnqueuedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.EnqueuedLogLevel.set -> void
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.FailedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.FailedLogLevel.set -> void
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.FinishedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.FinishedLogLevel.set -> void
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.StartedLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.StartedLogLevel.set -> void
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.SucceededLogLevel.get -> Microsoft.Extensions.Logging.LogLevel
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.SucceededLogLevel.set -> void
System.Threading.Tasks.Flow.TaskFlowLoggingOptions.TaskFlowLoggingOptions() -> void
static System.Threading.Tasks.Flow.LoggingTaskSchedulerExtensions.WithLogging(this System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler, Microsoft.Extensions.Logging.ILogger! logger, System.Action<System.Threading.Tasks.Flow.TaskFlowLoggingOptions!>? configure = null) -> System.Threading.Tasks.Flow.ITaskScheduler!
3 changes: 3 additions & 0 deletions TaskFlow.Extensions.Time/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#nullable enable
System.Threading.Tasks.Flow.ThrottlingTaskSchedulerExtensions
static System.Threading.Tasks.Flow.ThrottlingTaskSchedulerExtensions.WithDebounce(this System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler, System.TimeSpan interval, System.TimeProvider? timeProvider = null) -> System.Threading.Tasks.Flow.ITaskScheduler!
3 changes: 3 additions & 0 deletions TaskFlow.Extensions.Time/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#nullable enable
*REMOVED*static System.Threading.Tasks.Flow.ThrottlingTaskSchedulerExtensions.WithDebounce(this System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler, System.TimeSpan interval, System.TimeProvider? timeProvider = null) -> System.Threading.Tasks.Flow.ITaskScheduler!
static System.Threading.Tasks.Flow.ThrottlingTaskSchedulerExtensions.WithThrottle(this System.Threading.Tasks.Flow.ITaskScheduler! taskScheduler, System.TimeSpan interval, System.TimeProvider? timeProvider = null) -> System.Threading.Tasks.Flow.ITaskScheduler!
1 change: 1 addition & 0 deletions TaskFlow.Extensions.Time/TaskFlow.Extensions.Time.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>$(DefineConstants);TASKFLOW_EXTENSIONS_TIME</DefineConstants>
<RootNamespace>System.Threading.Tasks.Flow</RootNamespace>
<PackageId>TaskFlow.Extensions.Time</PackageId>
<Description>Leading-edge admission throttling for TaskFlow on netstandard2.0 with TimeProvider-based deterministic timing.</Description>
Expand Down
37 changes: 30 additions & 7 deletions TaskFlow/DedicatedThreadTaskFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@ public sealed class DedicatedThreadTaskFlow : ThreadTaskFlow
private readonly Thread _thread;

/// <summary>
/// Initializes a new instance of the <see cref="DedicatedThreadTaskFlow"/> class with default options and an optional name.
/// Initializes a new instance of the <see cref="DedicatedThreadTaskFlow"/> class with default options and the default thread name.
/// </summary>
/// <param name="name">Optional name for the dedicated thread. If null or empty, uses the class name.</param>
/// <remarks>
/// <para>
/// This constructor uses the default options from <see cref="TaskFlowOptions.Default"/>.
Expand All @@ -56,17 +55,41 @@ public sealed class DedicatedThreadTaskFlow : ThreadTaskFlow
/// The task flow immediately creates and starts a dedicated background thread for processing tasks.
/// </para>
/// </remarks>
public DedicatedThreadTaskFlow(string? name = default)
public DedicatedThreadTaskFlow()
: this(TaskFlowOptions.Default, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DedicatedThreadTaskFlow"/> class with default options and the specified thread name.
/// </summary>
/// <param name="name">The dedicated thread name. If <c>null</c> or empty, the class name is used.</param>
/// <remarks>
/// The task flow immediately creates and starts a dedicated background thread using the default options from
/// <see cref="TaskFlowOptions.Default"/>.
/// </remarks>
public DedicatedThreadTaskFlow(string? name)
: this(TaskFlowOptions.Default, name)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DedicatedThreadTaskFlow"/> class with the specified options and an optional name.
/// Initializes a new instance of the <see cref="DedicatedThreadTaskFlow"/> class with the specified options and the default thread name.
/// </summary>
/// <param name="options">The options that configure the behavior of this task flow.</param>
/// <exception cref="ArgumentNullException"><paramref name="options"/> is <c>null</c>.</exception>
/// <remarks>The task flow immediately creates and starts a dedicated background thread.</remarks>
public DedicatedThreadTaskFlow(TaskFlowOptions options)
: this(options, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DedicatedThreadTaskFlow"/> class with the specified options and thread name.
/// </summary>
/// <param name="options">The options that configure the behavior of this task flow.</param>
/// <param name="name">Optional name for the dedicated thread. If null or empty, uses the class name.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="options"/> is null.</exception>
/// <param name="name">The dedicated thread name. If <c>null</c> or empty, the class name is used.</param>
/// <exception cref="ArgumentNullException"><paramref name="options"/> is <c>null</c>.</exception>
/// <remarks>
/// <para>
/// The task flow immediately creates and starts a dedicated background thread for processing tasks.
Expand All @@ -75,7 +98,7 @@ public DedicatedThreadTaskFlow(string? name = default)
/// The thread name is useful for debugging and thread identification in thread dumps or profiling tools.
/// </para>
/// </remarks>
public DedicatedThreadTaskFlow(TaskFlowOptions options, string? name = default)
public DedicatedThreadTaskFlow(TaskFlowOptions options, string? name)
: base(options)
{
_thread = new Thread(ThreadStart)
Expand Down
Loading
Loading