Skip to content

Commit a530674

Browse files
committed
feat: linux-musl runtime support
pact-reference has introduced musl and arm64 based ffi libraries for linux - pact-foundation/pact-reference#416 Tracking Issue - pact-foundation/roadmap#30 fixes #498 fixes #496 fixes #500 fixes #374 fixes #387 Linux glibc based hosts take precedence, so if any error occurs during musl detection. I do not anticipate breaking changes for users - Docs - [Uses MSBuild Exec task](https://learn.microsoft.com/en-us/visualstudio/msbuild/exec-task?view=vs-2022) - MSBuild Blog Posts - [Cross-Platform Build Events in .NET Core using MSBuild](https://jeremybytes.blogspot.com/2020/05/cross-platform-build-events-in-net-core.html) - [MSBuild 101: Using the exit code from a command](https://www.creepingcoder.com/2020/06/01/msbuild-101-using-the-exit-code-from-a-command/) - Stack OverFlow - [Set PropertyGroup property to Exec output](https://stackoverflow.com/questions/76583824/set-propertygroup-property-to-exec-output) - .NET runtime musl detection code - https://github.com/dotnet/runtime/blob/a50ba0669353893ca8ade8568b0a7d210b5a425f/src/mono/llvm/llvm-init.proj\#L7 - https://github.com/dotnet/runtime/blob/a50ba0669353893ca8ade8568b0a7d210b5a425f/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs\#L78t musl detection will run if - if linux - if /lib/ld-musl-(x86_64|aarch64).so.1 exists - if ldd bin/sh | grep musl is true (musl lib is loaded, rather than glibc) will continue on error, reverting back to glibc based libaries. should work for multiple musl based distroes if - /lib/ld-musl-(x86_64|aarch64).so.1 exists - ldd is available (available by default in alpine images) Tested on Alpine ARM64 / AMD64.
1 parent 63a9d90 commit a530674

File tree

4 files changed

+152
-62
lines changed

4 files changed

+152
-62
lines changed

.github/workflows/ci.yml

+45-1
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,52 @@ jobs:
7272
name: nupkgs
7373
path: ./dist/*.*
7474

75+
build-dotnet-containers:
76+
runs-on: ubuntu-latest
77+
name: ${{ matrix.arch }}-${{ matrix.distro }}-build-dotnet-container
78+
strategy:
79+
fail-fast: false
80+
matrix:
81+
arch:
82+
- amd64
83+
# - arm64
84+
distro:
85+
- "mcr.microsoft.com/dotnet/sdk:8.0"
86+
- "mcr.microsoft.com/dotnet/sdk:8.0-alpine3.20"
87+
- "mcr.microsoft.com/dotnet/sdk:8.0-alpine3.19"
88+
89+
steps:
90+
- uses: actions/checkout@v4
91+
92+
- name: Docker dependencies
93+
id: docker_commands
94+
shell: bash
95+
run: |
96+
if [[ ${{ matrix.distro }} == *"alpine"* ]]; then
97+
echo "deps=apk add --no-cache curl bash gzip && " >> "$GITHUB_OUTPUT"
98+
else
99+
echo "deps=" >> "$GITHUB_OUTPUT"
100+
fi
101+
102+
- name: Restore, Build & Test
103+
run: |
104+
docker run \
105+
--rm \
106+
-v $(pwd):/${{ github.workspace }} \
107+
-w ${{ github.workspace }} \
108+
--platform linux/${{ matrix.arch }} \
109+
--entrypoint /bin/sh \
110+
${{ matrix.distro }} \
111+
-c '${{ steps.docker_commands.outputs.deps }} \
112+
build/download-native-libs.sh && \
113+
dotnet restore && dotnet build --no-restore && \
114+
dotnet test --no-build --verbosity normal'
115+
75116
release:
76-
needs: build-dotnet
117+
needs: [
118+
build-dotnet,
119+
build-dotnet-containers
120+
]
77121
if: github.ref_type == 'tag'
78122
runs-on: ubuntu-latest
79123
steps:

README.md

+10-10
Original file line numberDiff line numberDiff line change
@@ -232,16 +232,16 @@ For writing messaging pacts instead of requests/response pacts, see the [messagi
232232

233233
Due to using a shared native library instead of C# for the main Pact logic only certain OSs are supported:
234234

235-
| OS | Arch | Support |
236-
| ------------ | ------------ | -------------------------------------------------------------------|
237-
| Windows | x86 | ❌ No |
238-
| Windows | x64 | ✔️ Yes |
239-
| Linux (libc) | ARM64 | ✔️ Yes |
240-
| Linux (libc) | x64 | ✔️ Yes |
241-
| Linux (libc) | x86 | ❌ No |
242-
| Linux (musl) | Any |[No](https://github.com/pact-foundation/pact-net/issues/374) |
243-
| OSX | x64 | ✔️ Yes |
244-
| OSX | ARM64 (M1/M2)| ✔️ Yes |
235+
| OS | Arch | Support |
236+
| ------------ | ----------- | -------------------------------------------------------------------|
237+
| Windows | x86 | ❌ No |
238+
| Windows | x64 | ✔️ Yes |
239+
| Linux (libc) | ARM | ✔️ Yes |
240+
| Linux (libc) | x86 | ❌ No |
241+
| Linux (libc) | x64 | ✔️ Yes |
242+
| Linux (musl) | Any | ✔️ Yes |
243+
| OSX | x64 | ✔️ Yes |
244+
| OSX | ARM (M1/M2) | ✔️ Yes |
245245

246246
### Pact Specification
247247

build/download-native-libs.sh

+19-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,23 @@ download_native() {
4646
if [[ "$OSTYPE" == "darwin"* ]]; then
4747
shasum -a 256 --check --quiet "$src_sha"
4848
else
49-
sha256sum --check --quiet "$src_sha"
49+
if [[ "$OSTYPE" == "linux"* ]]; then
50+
if ldd /bin/ls >/dev/null 2>&1; then
51+
ldd_output=$(ldd /bin/ls)
52+
case "$ldd_output" in
53+
*musl*)
54+
sha256sum -c -s "$src_sha"
55+
;;
56+
*)
57+
sha256sum --check --quiet "$src_sha"
58+
;;
59+
esac
60+
else
61+
sha256sum --check --quiet "$src_sha"
62+
fi
63+
else
64+
sha256sum --check --quiet "$src_sha"
65+
fi
5066
fi
5167

5268
echo -e "${GREEN}OK${CLEAR}"
@@ -67,5 +83,7 @@ download_native "libpact_ffi" "linux" "x86_64" "so"
6783
download_native "libpact_ffi" "linux" "aarch64" "so"
6884
download_native "libpact_ffi" "macos" "x86_64" "dylib"
6985
download_native "libpact_ffi" "macos" "aarch64" "dylib"
86+
download_native "libpact_ffi" "linux" "x86_64-musl" "so"
87+
download_native "libpact_ffi" "linux" "aarch64-musl" "so"
7088

7189
echo "Successfully downloaded FFI libraries"

src/PactNet/PactNet.csproj

+78-50
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="build_libs">
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
@@ -9,56 +9,84 @@
99
</PropertyGroup>
1010

1111
<Import Project="../NuGet.targets" />
12+
<Target Name="build_libs">
13+
<!-- musl detection notes -->
14+
<!-- Main fallback behaviour is to default to glibc flavour, ensuring miminal impact on existing supported targets -->
15+
<!-- ContinueOnError True and Fallback to IsLinuxX64 / IsLinuxArm64 -->
16+
<!-- 1. Check host is Linux - IsLinux -->
17+
<!-- 2. Check if supported arch specific musl lib exists - IsLinuxMuslX64LibFound/IsLinuxMuslArm64LibFound -->
18+
<!-- 3. Check if musl is the loaded libc -->
19+
<!-- 3a. glibc hosts could have musl cross libs installed, in the standard musl location -->
20+
<!-- 3b. use ldd on a well known binary such as /bin/sh and grep for musl -->
21+
<!-- 3c. note ldd may not be available on all musl targets -->
22+
<PropertyGroup>
23+
<IsLinux>False</IsLinux>
24+
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'True'">True</IsLinux>
25+
<IsLinuxMuslX64LibFound Condition="$([System.IO.File]::Exists('/lib/ld-musl-x86_64.so.1')) == 'True'">True</IsLinuxMuslX64LibFound>
26+
<IsLinuxMuslArm64LibFound Condition="$([System.IO.File]::Exists('/lib/ld-musl-aarch64.so.1')) == 'True'">True</IsLinuxMuslArm64LibFound>
27+
</PropertyGroup>
28+
<!-- only run this check if linux and the musl shared libs were found -->
29+
<Exec Command="ldd /bin/sh | grep musl" ConsoleToMSBuild="true" StandardOutputImportance="low" ContinueOnError="true" Condition="$(IsLinux) == 'True' And ($(IsLinuxMuslX64LibFound) == 'True' Or $(IsLinuxMuslArm64LibFound) == 'True')">
30+
<Output TaskParameter="ExitCode" PropertyName="IsLinuxMuslLoaded"/>
31+
</Exec>
32+
<PropertyGroup>
33+
<IsWindows>False</IsWindows>
34+
<IsOSX>False</IsOSX>
35+
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'True'">True</IsWindows>
36+
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'True'">True</IsOSX>
37+
<IsArm64>False</IsArm64>
38+
<IsArm64 Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">True</IsArm64>
39+
<IsLinuxX64 Condition="'$(IsLinux)' == 'True' And '$(IsArm64)' == 'False'">True</IsLinuxX64>
40+
<IsLinuxArm64 Condition="'$(IsLinux)' == 'True' And '$(IsArm64)' == 'True'">True</IsLinuxArm64>
41+
<IsLinuxMuslX64 Condition="'$(IsLinux)' == 'True' And '$(IsLinuxMuslLoaded)' == '0' And '$(IsArm64)' == 'False'">True</IsLinuxMuslX64>
42+
<IsLinuxMuslArm64 Condition="'$(IsLinux)' == 'True' And '$(IsLinuxMuslLoaded)' == '0' And '$(IsArm64)' == 'True'">True</IsLinuxMuslArm64>
43+
</PropertyGroup>
1244

13-
<PropertyGroup>
14-
<IsWindows>False</IsWindows>
15-
<IsLinux>False</IsLinux>
16-
<IsOSX>False</IsOSX>
17-
<IsArm64>False</IsArm64>
18-
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'True'">True</IsWindows>
19-
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'True'">True</IsLinux>
20-
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'True'">True</IsOSX>
21-
<IsArm64 Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">True</IsArm64>
22-
</PropertyGroup>
23-
24-
<ItemGroup>
25-
<Content Include="$(MSBuildProjectDirectory)\..\..\build\windows\x86_64\pact_ffi.dll">
26-
<Link>pact_ffi.dll</Link>
27-
<PackagePath>runtimes/win-x64/native</PackagePath>
28-
<Pack>true</Pack>
29-
<CopyToOutputDirectory Condition="'$(IsWindows)'">PreserveNewest</CopyToOutputDirectory>
30-
<Visible>false</Visible>
31-
</Content>
32-
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\x86_64\libpact_ffi.so">
33-
<Link>libpact_ffi.so</Link>
34-
<PackagePath>runtimes/linux-x64/native</PackagePath>
35-
<Pack>true</Pack>
36-
<CopyToOutputDirectory Condition="'$(IsLinux)' And '$(IsArm64)' == 'False'">PreserveNewest</CopyToOutputDirectory>
37-
<Visible>false</Visible>
38-
</Content>
39-
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\aarch64\libpact_ffi.so">
40-
<Link>libpact_ffi.so</Link>
41-
<PackagePath>runtimes/linux-arm64/native</PackagePath>
42-
<Pack>true</Pack>
43-
<CopyToOutputDirectory Condition="'$(IsLinux)' And '$(IsArm64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
44-
<Visible>false</Visible>
45-
</Content>
46-
<Content Include="$(MSBuildProjectDirectory)\..\..\build\macos\x86_64\libpact_ffi.dylib">
47-
<Link>libpact_ffi.dylib</Link>
48-
<PackagePath>runtimes/osx-x64/native</PackagePath>
49-
<Pack>true</Pack>
50-
<CopyToOutputDirectory Condition="'$(IsOSX)' == 'True' And '$(IsArm64)' == 'False'">PreserveNewest</CopyToOutputDirectory>
51-
<Visible>false</Visible>
52-
</Content>
53-
<Content Include="$(MSBuildProjectDirectory)\..\..\build\macos\aarch64\libpact_ffi.dylib">
54-
<Link>libpact_ffi.dylib</Link>
55-
<PackagePath>runtimes/osx-arm64/native</PackagePath>
56-
<Pack>true</Pack>
57-
<CopyToOutputDirectory Condition="'$(IsOSX)' == 'True' And '$(IsArm64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
58-
<Visible>false</Visible>
59-
</Content>
60-
</ItemGroup>
61-
45+
<ItemGroup>
46+
<Content Include="$(MSBuildProjectDirectory)\..\..\build\windows\x86_64\pact_ffi.dll">
47+
<Link>pact_ffi.dll</Link>
48+
<PackagePath>runtimes/win-x64/native</PackagePath>
49+
<Pack>true</Pack>
50+
<CopyToOutputDirectory Condition="'$(IsWindows)'">PreserveNewest</CopyToOutputDirectory>
51+
<Visible>false</Visible>
52+
</Content>
53+
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\x86_64\libpact_ffi.so">
54+
<Link>libpact_ffi.so</Link>
55+
<PackagePath>runtimes/linux-x64/native</PackagePath>
56+
<Pack>true</Pack>
57+
<CopyToOutputDirectory Condition="'$(IsLinuxX64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
58+
<Visible>false</Visible>
59+
</Content>
60+
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\x86_64-musl\libpact_ffi.so">
61+
<Link>libpact_ffi.so</Link>
62+
<PackagePath>runtimes/linux-x64-musl/native</PackagePath>
63+
<Pack>true</Pack>
64+
<CopyToOutputDirectory Condition="'$(IsLinuxMuslX64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
65+
<Visible>false</Visible>
66+
</Content>
67+
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\aarch64-musl\libpact_ffi.so">
68+
<Link>libpact_ffi.so</Link>
69+
<PackagePath>runtimes/linux-arm64-musl/native</PackagePath>
70+
<Pack>true</Pack>
71+
<CopyToOutputDirectory Condition="'$(IsLinuxMuslArm64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
72+
<Visible>false</Visible>
73+
</Content>
74+
<Content Include="$(MSBuildProjectDirectory)\..\..\build\macos\x86_64\libpact_ffi.dylib">
75+
<Link>libpact_ffi.dylib</Link>
76+
<PackagePath>runtimes/osx-x64/native</PackagePath>
77+
<Pack>true</Pack>
78+
<CopyToOutputDirectory Condition="'$(IsOSX)' == 'True' And '$(IsArm64)' == 'False'">PreserveNewest</CopyToOutputDirectory>
79+
<Visible>false</Visible>
80+
</Content>
81+
<Content Include="$(MSBuildProjectDirectory)\..\..\build\macos\aarch64\libpact_ffi.dylib">
82+
<Link>libpact_ffi.dylib</Link>
83+
<PackagePath>runtimes/osx-arm64/native</PackagePath>
84+
<Pack>true</Pack>
85+
<CopyToOutputDirectory Condition="'$(IsOSX)' == 'True' And '$(IsArm64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
86+
<Visible>false</Visible>
87+
</Content>
88+
</ItemGroup>
89+
</Target>
6290
<ItemGroup>
6391
<Content Include="$(MSBuildProjectDirectory)\..\..\build\PactNet.targets">
6492
<PackagePath>build/net462/</PackagePath>

0 commit comments

Comments
 (0)