Skip to content

Commit

Permalink
feat: linux arm64 support & musl detection
Browse files Browse the repository at this point in the history
## Rationale

pact-reference has introduced musl and arm64 based ffi libraries for linux

- pact-foundation/pact-reference#416

Tracking Issue

- pact-foundation/roadmap#30

## Issues Resolved

fixes pact-foundation#498
fixes pact-foundation#496
fixes pact-foundation#500
fixes pact-foundation#374
fixes pact-foundation#387

## Backwards Compatibility

Linux glibc based hosts take precedence, so if any error occurs during musl
detection. I do not anticipate breaking changes for users

## Implementation notes

### .NET notes

- 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

### Conditions for execution

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.

### Supported musl targets

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.

## Caveats

- [.NET does not run under QEMU](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md#qemu) affecting the ability to test multi-arch from a single system
- .NET restore can take a long time when running under containers.
  - [Workaround](NuGet/Home#13062 (comment)): Set `DOTNET_NUGET_SIGNATURE_VERIFICATION` to `false`

## Compatibility

### Operating System

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

| OS           | Arch        | Support                                                            |
| ------------ | ----------- | -------------------------------------------------------------------|
| Windows      | x86         | ❌ No                                                              |
| Windows      | x64         | ✔️ Yes                                                              |
| Linux (libc) | x86         | ❌ No                                                              |
| Linux (libc) | x64         | ✔️ Yes                                                              |
| Linux (musl) | x64         | ✔️ Yes (Tier 2)*                                                    |
| Linux (libc) | ARM         | ✔️ Yes (Tier 3)*                                                    |
| Linux (musl) | ARM         | ✔️ Yes (Tier 3)*                                                    |
| OSX          | x64         | ✔️ Yes                                                              |
| OSX          | ARM (M1/M2) | ✔️ Yes                                                              |

#### Support

- Tier 1
  - Established
  - Full CI/CD support.
  - Users should not encounter issues
  - Full reproducible examples running in CI, should be provided by users raising issues
  - If using musl targets, users should attempt the same test on a libc target (such as debian)
- Tier 2
  - Recently introduced
  - Full CI/CD support.
  - Users may encounter issues
  - Full reproducible examples running in CI, should be provided by users raising issues
  - If using musl targets, users should attempt the same test on a libc target (such as debian)
- Tier 3
  - Recently introduced, No/limited CI/CD support.
  - Users may encounter issues
  - Full reproducible examples which can be run by maintainers locally, should be provided by users raising issues
  • Loading branch information
YOU54F committed May 29, 2024
1 parent 09ee818 commit 5b38f3c
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 66 deletions.
25 changes: 25 additions & 0 deletions .cirrus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
BUILD_TEST_TASK_TEMPLATE: &BUILD_TEST_TASK_TEMPLATE
arch_check_script: |
uname -m
ffi_download_script: |
chmod +x build/download-native-libs.sh
build/download-native-libs.sh
restore_script: |
dotnet restore
build_script: |
dotnet build --no-restore
test_script: |
dotnet test --no-build --verbosity normal
pack_script: |
dotnet pack --verbosity normal -c Release --no-restore --include-source --version-suffix alpha.123 -o ./dist
linux_arm64_task:
arm_container:
image: mcr.microsoft.com/dotnet/sdk:8.0
<< : *BUILD_TEST_TASK_TEMPLATE

linux_arm64_alpine_task:
arm_container:
image: mcr.microsoft.com/dotnet/sdk:8.0-alpine
setup_alpine_script: apk add curl bash gzip
<< : *BUILD_TEST_TASK_TEMPLATE
55 changes: 52 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ env:

jobs:
build-dotnet:
name: "Build and Test (dotnet)"
name: ${{ matrix.arch }}-${{ matrix.os }}-build-dotnet
strategy:
fail-fast: false
matrix:
os:
- windows-latest
Expand Down Expand Up @@ -43,8 +44,8 @@ jobs:
build/windows
- name: Pull interop dependencies
run: bash -c "build/download-native-libs.sh"
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
run: bash -c "build/download-native-libs.sh"

- name: Restore
run: dotnet restore
Expand All @@ -66,8 +67,56 @@ jobs:
name: nupkgs
path: ./dist/*.*

build-dotnet-containers:
runs-on: ubuntu-latest
name: ${{ matrix.arch }}-${{ matrix.distro }}-build-dotnet-container
strategy:
fail-fast: false
matrix:
arch:
- amd64
# - arm64
distro:
- "mcr.microsoft.com/dotnet/sdk:8.0"
- "mcr.microsoft.com/dotnet/sdk:8.0-alpine"

steps:
- uses: actions/checkout@v4
- name: Set up QEMU
if: matrix.arch != 'amd64'
uses: docker/setup-qemu-action@v1
with:
platforms: ${{ matrix.arch }}

- name: Docker dependencies
id: docker_commands
shell: bash
run: |
if [[ ${{ matrix.distro }} == *"alpine"* ]]; then
echo "deps=apk add --no-cache curl bash gzip && " >> "$GITHUB_OUTPUT"
else
echo "deps=" >> "$GITHUB_OUTPUT"
fi
- name: Restore, Build & Test
run: |
docker run \
--rm \
-v $(pwd):/${{ github.workspace }} \
-w ${{ github.workspace }} \
--platform linux/${{ matrix.arch }} \
--entrypoint /bin/sh \
${{ matrix.distro }} \
-c '${{ steps.docker_commands.outputs.deps }} \
build/download-native-libs.sh && \
dotnet restore && dotnet build --no-restore && \
dotnet test --no-build --verbosity normal'
release:
needs: build-dotnet
needs: [
build-dotnet,
build-dotnet-containers
]
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
steps:
Expand Down
34 changes: 27 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,34 @@ Due to using a shared native library instead of C# for the main Pact logic only
| OS | Arch | Support |
| ------------ | ----------- | -------------------------------------------------------------------|
| Windows | x86 | ❌ No |
| Windows | x64 | ✔️ Yes |
| Linux (libc) | ARM | ❌ No |
| Windows | x64 | ✔️ Yes |
| Linux (libc) | x86 | ❌ No |
| Linux (libc) | x64 | ✔️ Yes |
| Linux (musl) | Any |[No](https://github.com/pact-foundation/pact-net/issues/374) |
| OSX | x64 | ✔️ Yes |
| OSX | ARM (M1/M2) | ✔️ Yes |

| Linux (libc) | x64 | ✔️ Yes |
| Linux (musl) | x64 | ✔️ Yes (Tier 2)* |
| Linux (libc) | ARM | ✔️ Yes (Tier 3)* |
| Linux (musl) | ARM | ✔️ Yes (Tier 3)* |
| OSX | x64 | ✔️ Yes |
| OSX | ARM (M1/M2) | ✔️ Yes |

#### Support

- Tier 1
- Established
- Full CI/CD support.
- Users should not encounter issues
- Full reproducible examples running in CI, should be provided by users raising issues
- If using musl targets, users should attempt the same test on a libc target (such as debian)
- Tier 2
- Recently introduced
- Full CI/CD support.
- Users may encounter issues
- Full reproducible examples running in CI, should be provided by users raising issues
- If using musl targets, users should attempt the same test on a libc target (such as debian)
- Tier 3
- Recently introduced, No/limited CI/CD support.
- Users may encounter issues
- Full reproducible examples which can be run by maintainers locally, should be provided by users raising issues

### Pact Specification

| Version | Status | [Spec] Compatibility | Install |
Expand Down
27 changes: 23 additions & 4 deletions build/download-native-libs.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail

FFI_VERSION="0.4.16"
FFI_VERSION="0.4.17"
FFI_BASE_URL="https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v$FFI_VERSION"

GREEN="\e[32m"
Expand Down Expand Up @@ -42,11 +42,27 @@ download_native() {

if [[ "$OSTYPE" == "darwin"* ]]; then
# OSX requires an empty arg passed to -i, but this doesn't work on Lin/Win
sed -Ei '' "s|../release_artifacts/.+$|$path/$dest_file|" "$path/$dest_file.sha256"
sed -Ei '' "s|\*(.*)$|\*$path/$dest_file|" "$path/$dest_file.sha256"
shasum -a 256 --check --quiet "$path/$dest_file.sha256"
else
sed -Ei "s|../release_artifacts/.+$|$path/$dest_file|" "$path/$dest_file.sha256"
sha256sum --check --quiet "$path/$dest_file.sha256"
sed -Ei "s|\*(.*)$|\*$path/$dest_file|" "$path/$dest_file.sha256"
if [[ "$OSTYPE" == "linux"* ]]; then
if ldd /bin/ls >/dev/null 2>&1; then
ldd_output=$(ldd /bin/ls)
case "$ldd_output" in
*musl*)
sha256sum -c -s "$path/$dest_file.sha256"
;;
*)
sha256sum --check --quiet "$path/$dest_file.sha256"
;;
esac
else
sha256sum --check --quiet "$path/$dest_file.sha256"
fi
else
sha256sum --check --quiet "$path/$dest_file.sha256"
fi
fi

rm "$path/$dest_file.sha256"
Expand All @@ -60,5 +76,8 @@ download_native() {

download_native "pact_ffi" "windows" "x86_64" "dll"
download_native "libpact_ffi" "linux" "x86_64" "so"
download_native "libpact_ffi" "linux" "aarch64" "so"
download_native "libpact_ffi" "linux" "x86_64-musl" "so"
download_native "libpact_ffi" "linux" "aarch64-musl" "so"
download_native "libpact_ffi" "osx" "x86_64" "dylib"
download_native "libpact_ffi" "osx" "aarch64-apple-darwin" "dylib"
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
],
"metadata": {
"pactRust": {
"ffi": "0.4.16",
"ffi": "0.4.17",
"models": "1.1.19"
},
"pactSpecification": {
Expand Down
128 changes: 85 additions & 43 deletions src/PactNet/PactNet.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="build_libs">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
Expand All @@ -9,49 +9,91 @@
</PropertyGroup>

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

<PropertyGroup>
<IsWindows>False</IsWindows>
<IsLinux>False</IsLinux>
<IsOSX>False</IsOSX>
<IsArm64>False</IsArm64>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'True'">True</IsWindows>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'True'">True</IsLinux>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'True'">True</IsOSX>
<IsArm64 Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">True</IsArm64>
</PropertyGroup>

<ItemGroup>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\windows\x86_64\pact_ffi.dll">
<Link>pact_ffi.dll</Link>
<PackagePath>runtimes/win-x64/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsWindows)'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\x86_64\libpact_ffi.so">
<Link>libpact_ffi.so</Link>
<PackagePath>runtimes/linux-x64/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsLinux)'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\osx\x86_64\libpact_ffi.dylib">
<Link>libpact_ffi.dylib</Link>
<PackagePath>runtimes/osx-x64/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsOSX)' == 'True' And '$(IsArm64)' == 'False'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\osx\aarch64-apple-darwin\libpact_ffi.dylib">
<Link>libpact_ffi.dylib</Link>
<PackagePath>runtimes/osx-arm64/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsOSX)' == 'True' And '$(IsArm64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
</ItemGroup>

<ItemGroup>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\windows\x86_64\pact_ffi.dll">
<Link>pact_ffi.dll</Link>
<PackagePath>runtimes/win-x64/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsWindows)'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\x86_64\libpact_ffi.so">
<Link>libpact_ffi.so</Link>
<PackagePath>runtimes/linux-x64/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsLinuxX64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\aarch64\libpact_ffi.so">
<Link>libpact_ffi.so</Link>
<PackagePath>runtimes/linux-arm64/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsLinuxArm64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\x86_64-musl\libpact_ffi.so">
<Link>libpact_ffi.so</Link>
<PackagePath>runtimes/linux-x64-musl/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsLinuxMuslX64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\linux\aarch64-musl\libpact_ffi.so">
<Link>libpact_ffi.so</Link>
<PackagePath>runtimes/linux-arm64-musl/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsLinuxMuslArm64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\osx\x86_64\libpact_ffi.dylib">
<Link>libpact_ffi.dylib</Link>
<PackagePath>runtimes/osx-x64/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsOSX)' == 'True' And '$(IsArm64)' == 'False'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\osx\aarch64-apple-darwin\libpact_ffi.dylib">
<Link>libpact_ffi.dylib</Link>
<PackagePath>runtimes/osx-arm64/native</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory Condition="'$(IsOSX)' == 'True' And '$(IsArm64)' == 'True'">PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
</ItemGroup>
</Target>
<ItemGroup>
<Content Include="$(MSBuildProjectDirectory)\..\..\build\PactNet.targets">
<PackagePath>build/net462/</PackagePath>
Expand Down
2 changes: 1 addition & 1 deletion tests/PactNet.Tests/data/v2-consumer-integration.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
],
"metadata": {
"pactRust": {
"ffi": "0.4.16",
"ffi": "0.4.17",
"models": "1.1.19"
},
"pactSpecification": {
Expand Down
2 changes: 1 addition & 1 deletion tests/PactNet.Tests/data/v3-consumer-integration.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
],
"metadata": {
"pactRust": {
"ffi": "0.4.16",
"ffi": "0.4.17",
"models": "1.1.19"
},
"pactSpecification": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"language": "C#"
},
"pactRust": {
"ffi": "0.4.16",
"ffi": "0.4.17",
"models": "1.1.19"
},
"pactSpecification": {
Expand Down
2 changes: 1 addition & 1 deletion tests/PactNet.Tests/data/v3-message-integration.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
],
"metadata": {
"pactRust": {
"ffi": "0.4.16",
"ffi": "0.4.17",
"models": "1.1.19"
},
"pactSpecification": {
Expand Down
2 changes: 1 addition & 1 deletion tests/PactNet.Tests/data/v3-server-integration.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
],
"metadata": {
"pactRust": {
"ffi": "0.4.16",
"ffi": "0.4.17",
"models": "1.1.19"
},
"pactSpecification": {
Expand Down
2 changes: 1 addition & 1 deletion tests/PactNet.Tests/data/v4-combined-integration.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
"language": "C#"
},
"pactRust": {
"ffi": "0.4.16",
"ffi": "0.4.17",
"models": "1.1.19"
},
"pactSpecification": {
Expand Down
Loading

0 comments on commit 5b38f3c

Please sign in to comment.