Skip to content
Open
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
182 changes: 181 additions & 1 deletion .github/workflows/csharp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,184 @@ jobs:
run: ./scripts/generate_bindings.ps1

- name: Run tests
run: dotnet test --logger "console;verbosity=minimal"
run: dotnet test Payjoin.Tests.csproj --logger "console;verbosity=minimal"

build-nuget-native:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, pack-nuget, and maybe smoke-nuget seem tightly coupled and I don't see a clear advantage of having them be independent jobs. Could they be consolidated such that:

  • The transient artifacts for each architecture aren't kept around. The only useful artifact is the final payjoin-csharp-nuget-package .nupkg file.
  • The os matrix is shared between all relevant steps, to prevent inconsistent matrices between steps on future updates.
    • Currently pack-nuget declares the matrix strategy but then uses a hardcoded loop for rid in linux-arm64 linux-x64 osx-arm64 osx-x64 win-arm64 win-x64; do that seems prone to error.

I also wonder if we could simplify the logic and not rely so much on github runners by using a cross-compiler like https://github.com/cross-rs/cross

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is particularly done in the recent commits. Now that cross-compile is done, can start thinking about consolidation

# Every RID cross-compiles from Linux: cargo-zigbuild links the Linux targets
# against a pinned glibc floor and the macOS targets against zig's bundled
# libSystem stubs; cargo-xwin links MSVC-ABI Windows targets against the
# Microsoft CRT/SDK. The per-RID smoke jobs verify each produced asset on
# real hardware.
name: "Build NuGet native asset"
runs-on: ubuntu-latest
env:
# Must match the dtolnay/rust-toolchain version exactly: the cross-target
# std components are installed for that toolchain, and a partial version
# here selects a different rustup toolchain entry without them (E0463).
RUSTUP_TOOLCHAIN: 1.85.0
defaults:
run:
working-directory: payjoin-ffi/csharp
strategy:
# Let every RID report its own result; a failure in one target should not
# cancel the signal from the others.
fail-fast: false
matrix:
include:
- rid: linux-arm64
target: aarch64-unknown-linux-gnu
- rid: linux-x64
target: x86_64-unknown-linux-gnu
- rid: osx-arm64
target: aarch64-apple-darwin
- rid: osx-x64
target: x86_64-apple-darwin
- rid: win-arm64
target: aarch64-pc-windows-msvc
- rid: win-x64
target: x86_64-pc-windows-msvc
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install Rust 1.85.0
uses: dtolnay/rust-toolchain@1.85.0
with:
targets: ${{ matrix.target }}

- name: Use cache
uses: Swatinem/rust-cache@v2

- name: Install cross toolchain
# Version- and hash-pinned: these tools produce the shipped binaries.
run: pip install --require-hashes -r scripts/cross-requirements.txt

- name: Install MSVC toolchain frontends (clang-cl, llvm-lib)
if: startsWith(matrix.rid, 'win-')
# cargo-xwin compiles C dependencies with clang-cl and archives them with
# llvm-lib. The runner image ships LLVM with versioned binary names only,
# so expose the unversioned names cc-rs looks for.
run: |
for tool in clang-cl llvm-lib; do
if command -v "$tool" >/dev/null; then
continue
fi
latest=$(ls -1 /usr/bin/"$tool"-* 2>/dev/null | sort -V | tail -n 1)
if [ -z "$latest" ]; then
sudo apt-get update -q
sudo apt-get install -y -q llvm clang
latest=$(ls -1 /usr/bin/"$tool"-* 2>/dev/null | sort -V | tail -n 1)
fi
if [ -z "$latest" ]; then
echo "$tool not found on the runner" >&2
exit 1
fi
sudo ln -s "$latest" /usr/local/bin/"$tool"
done
command -v clang-cl llvm-lib

- name: Build release native asset
run: |
PAYJOIN_FFI_CROSS=1 PAYJOIN_FFI_RID=${{ matrix.rid }} bash ./scripts/build_nuget_native.sh

- name: Upload NuGet native asset
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.rid }}
path: payjoin-ffi/csharp/artifacts/runtimes/${{ matrix.rid }}/native/*
if-no-files-found: error

pack-nuget:
name: "Pack C# NuGet package"
runs-on: ubuntu-latest
needs: build-nuget-native
env:
# Exact version so the packaged bindings build with the toolchain the
# action installs, not an implicitly-installed latest 1.85.x.
RUSTUP_TOOLCHAIN: 1.85.0
defaults:
run:
working-directory: payjoin-ffi/csharp
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install Rust 1.85.0
uses: dtolnay/rust-toolchain@1.85.0

- name: Use cache
uses: Swatinem/rust-cache@v2

- name: Install .NET 10 SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"

- name: Download native assets
uses: actions/download-artifact@v4
with:
path: payjoin-ffi/csharp/artifacts/downloaded

- name: Assemble RID assets
run: |
for rid in linux-arm64 linux-x64 osx-arm64 osx-x64 win-arm64 win-x64; do
mkdir -p "artifacts/runtimes/$rid/native"
cp "artifacts/downloaded/$rid"/* "artifacts/runtimes/$rid/native/"
done

- name: Generate production bindings
run: |
PAYJOIN_FFI_FEATURES= PAYJOIN_FFI_PROFILE=release bash ./scripts/generate_bindings.sh

- name: Pack NuGet package
run: dotnet pack Payjoin.csproj --configuration Release --output artifacts/packages

- name: Upload NuGet package
uses: actions/upload-artifact@v4
with:
name: payjoin-csharp-nuget-package
path: payjoin-ffi/csharp/artifacts/packages/*.nupkg
if-no-files-found: error

smoke-nuget:
name: "Smoke test C# NuGet package"
runs-on: ${{ matrix.os }}
needs: pack-nuget
defaults:
run:
working-directory: payjoin-ffi/csharp
strategy:
matrix:
include:
- os: ubuntu-24.04-arm
rid: linux-arm64
- os: ubuntu-latest
rid: linux-x64
- os: macos-latest
rid: osx-arm64
- os: macos-15-intel
rid: osx-x64
- os: windows-11-arm
rid: win-arm64
- os: windows-latest
rid: win-x64
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install .NET 10 SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"

- name: Download NuGet package
uses: actions/download-artifact@v4
with:
name: payjoin-csharp-nuget-package
path: payjoin-ffi/csharp/artifacts/packages

- name: Run package smoke test
shell: bash
# 'auto' derives the version from the packed artifact, so Payjoin.csproj
# stays the only place the package version is maintained.
run: bash ./scripts/smoke_nuget_package.sh artifacts/packages auto ${{ matrix.rid }}
1 change: 1 addition & 0 deletions payjoin-ffi/csharp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ src/*.cs
# Build outputs
bin/
obj/
artifacts/

# IDE
.vs/
Expand Down
7 changes: 7 additions & 0 deletions payjoin-ffi/csharp/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath>obj/$(MSBuildProjectName)/</BaseIntermediateOutputPath>
<BaseOutputPath>bin/$(MSBuildProjectName)/</BaseOutputPath>
<DefaultItemExcludes>$(DefaultItemExcludes);obj/**;bin/**;artifacts/**</DefaultItemExcludes>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion payjoin-ffi/csharp/Payjoin.Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Payjoin.Http
{
internal sealed class OhttpKeysClient : IDisposable
public sealed class OhttpKeysClient : IDisposable
{
private readonly HttpClient _client;
private readonly HttpClientHandler _handler;
Expand Down
6 changes: 6 additions & 0 deletions payjoin-ffi/csharp/Payjoin.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="xunit.v3" Version="3.2.2" />
<ProjectReference Include="Payjoin.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Remove="Payjoin.Http.cs" />
<Compile Remove="src/**/*.cs" />
</ItemGroup>

<!-- Include native library - copied to output directory for testing -->
Expand Down
63 changes: 63 additions & 0 deletions payjoin-ffi/csharp/Payjoin.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>true</IsPackable>
<PackageId>Payjoin</PackageId>
<Version>0.24.0-preview.1</Version>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest version on master is 1.0.0-rc.4. FWIW for the npm and pub.dev releases I am sticking with 0.1.x versioning while we're stabilizing the publishing process.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, will match those here too

<Title>Payjoin</Title>
<Authors>Payjoin Dev Kit Contributors</Authors>
<Description>C# bindings for payjoin-ffi, generated from rust-payjoin via UniFFI.</Description>
<PackageTags>bitcoin;payjoin;bip78;ffi;csharp;dotnet</PackageTags>
<PackageProjectUrl>https://github.com/payjoin/rust-payjoin</PackageProjectUrl>
<RepositoryUrl>https://github.com/payjoin/rust-payjoin</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT OR Apache-2.0</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<IncludeBuildOutput>false</IncludeBuildOutput>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<!-- Accepted warnings from UniFFI-generated code: CS0108 (generated exception
Message types hide Exception.Message) and CS0659 (generated classes override
Equals without GetHashCode). Regenerate rather than hand-edit src/payjoin.cs.
NU5131 fires because the package ships a ref/net10.0 assembly (the intentional
native-asset layout: compile against ref/, run from runtimes/) and csproj-based
pack cannot emit the nuspec references group the analysis asks for; consumption
is covered by the per-RID smoke tests. Keep every other package-analysis warning
visible. -->
<NoWarn>$(NoWarn);CS0108;CS0659;NU5131</NoWarn>
</PropertyGroup>

<ItemGroup>
<Compile Include="src/**/*.cs" />
<Compile Include="Payjoin.Http.cs" />
<None Include="README.md" Pack="true" PackagePath="/" />
<None Include="$(TargetPath)" Pack="true" PackagePath="ref/$(TargetFramework)/" />
<None Include="$(TargetPath)" Pack="true" PackagePath="runtimes/any/lib/$(TargetFramework)/" />
<None Include="artifacts/runtimes/linux-arm64/native/*" Pack="true" PackagePath="runtimes/linux-arm64/native/" />
<None Include="artifacts/runtimes/linux-x64/native/*" Pack="true" PackagePath="runtimes/linux-x64/native/" />
<None Include="artifacts/runtimes/osx-arm64/native/*" Pack="true" PackagePath="runtimes/osx-arm64/native/" />
<None Include="artifacts/runtimes/osx-x64/native/*" Pack="true" PackagePath="runtimes/osx-x64/native/" />
<None Include="artifacts/runtimes/win-arm64/native/*" Pack="true" PackagePath="runtimes/win-arm64/native/" />
<None Include="artifacts/runtimes/win-x64/native/*" Pack="true" PackagePath="runtimes/win-x64/native/" />
</ItemGroup>

<Target Name="ValidatePayjoinNativeAssets" BeforeTargets="Pack">
<ItemGroup>
<PayjoinNativeAsset Include="artifacts/runtimes/**/native/*" />
</ItemGroup>
<Error
Condition="'@(PayjoinNativeAsset)' == ''"
Text="No RID-specific native assets found. Run scripts/build_nuget_native.sh or scripts/build_nuget_native.ps1 before packing." />
<!-- BitcoindEnv is exported by payjoin-ffi's test_utils module, which only
exists under the _test-utils feature, so its presence in the generated
bindings means they were not generated for production. -->
<Error
Condition="Exists('src/payjoin.cs') and $([System.IO.File]::ReadAllText('src/payjoin.cs').Contains('BitcoindEnv'))"
Text="Generated bindings include _test-utils. Regenerate production bindings before packing (bash: PAYJOIN_FFI_FEATURES= ./scripts/generate_bindings.sh; PowerShell: ./scripts/generate_bindings.ps1 -ProductionBindings)." />
</Target>

</Project>
28 changes: 0 additions & 28 deletions payjoin-ffi/csharp/Payjoin.nuspec

This file was deleted.

Loading
Loading