Skip to content

Commit 8536664

Browse files
authored
Merge pull request #5 from henrikottesorensen/feature/runtime-package-selection-and-github-actions
Runtime package selection, win-arm64, and GitHub Actions CI
2 parents fb5cf47 + e368487 commit 8536664

43 files changed

Lines changed: 5249 additions & 181 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Keep the build context to just the sources the container needs.
2+
#
3+
# Copying host state in would make the container build non-reproducible, and worse, a native binary
4+
# left over from a host build could end up packed if a target inside the container failed to
5+
# rebuild it. The container fetches and unpacks the upstream release itself.
6+
.git
7+
.github
8+
.idea
9+
.vs
10+
**/bin
11+
**/obj
12+
upstream
13+
packages
14+
packages-docker
15+
runtime.*.liblouis/runtimes
16+
LibLouis.NET.Tables/tables

.github/workflows/build.yml

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
name: build
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ['v*']
7+
pull_request:
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
packages: write
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
17+
18+
env:
19+
DOTNET_NOLOGO: 'true'
20+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 'true'
21+
DOTNET_CLI_TELEMETRY_OPTOUT: 'true'
22+
23+
jobs:
24+
# Native binaries for the Linux and Windows RIDs, plus the runtime.liblouis metapackage.
25+
# Everything happens inside the container so a local ./build.sh produces identical packages.
26+
native-linux:
27+
name: native (linux + windows RIDs)
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- name: Cross-compile and pack
33+
env:
34+
DOCKER_BUILDKIT: '1'
35+
run: ./build.sh
36+
37+
- uses: actions/upload-artifact@v4
38+
with:
39+
name: packages-native-linux
40+
path: packages/*.nupkg
41+
if-no-files-found: error
42+
43+
# Native binaries for the macOS RIDs. Needs a macOS host, so it cannot go in the container.
44+
native-macos:
45+
name: native (macOS RIDs)
46+
runs-on: macos-latest
47+
steps:
48+
- uses: actions/checkout@v4
49+
50+
- uses: actions/setup-dotnet@v4
51+
with:
52+
dotnet-version: '8.0.x'
53+
54+
- name: Build and pack
55+
run: sh ./build/build_runtime_macos_packages.sh
56+
57+
- uses: actions/upload-artifact@v4
58+
with:
59+
name: packages-native-macos
60+
path: packages/*.nupkg
61+
if-no-files-found: error
62+
63+
# The end-to-end check that runtime package selection actually works.
64+
#
65+
# The test project reaches runtime.liblouis through a project reference to LibLouis.NET, so
66+
# restore pulls in all eight per-RID packages from the local feed. Each runner then has to resolve
67+
# the one native binary matching the RID it is running on before any P/Invoke can succeed. A
68+
# mistake in the package layout or in the metapackage dependencies shows up here as a failing
69+
# test rather than in a consumer's application.
70+
test:
71+
name: test (${{ matrix.os }})
72+
needs: [native-linux, native-macos]
73+
runs-on: ${{ matrix.os }}
74+
strategy:
75+
fail-fast: false
76+
matrix:
77+
os: [ubuntu-latest, windows-latest, macos-latest]
78+
steps:
79+
- uses: actions/checkout@v4
80+
81+
- uses: actions/setup-dotnet@v4
82+
with:
83+
dotnet-version: '8.0.x'
84+
85+
- name: Collect runtime packages into a local feed
86+
uses: actions/download-artifact@v4
87+
with:
88+
pattern: packages-native-*
89+
merge-multiple: true
90+
path: local-feed
91+
92+
# The da-dk test tables `include` general tables such as braille-patterns.cti that ship in the
93+
# upstream release, so they have to be staged into LibLouis.NET.Tables before the tests run.
94+
#
95+
# shell: bash rather than the default, so this also works on the Windows runner, where the
96+
# default shell is PowerShell and `sh` is not on PATH. GitHub maps bash to Git Bash.
97+
- name: Stage upstream tables
98+
shell: bash
99+
run: ./build/stage_tables.sh
100+
101+
- name: Test
102+
run: >
103+
dotnet test LibLouis.NET.Test/LibLouis.NET.Test.csproj
104+
--configuration Release
105+
-p:RestoreAdditionalProjectSources=${{ github.workspace }}/local-feed
106+
107+
# Managed packages. Split from native-linux because building LibLouis.NET resolves
108+
# runtime.liblouis, which depends on the macOS packages the container cannot produce.
109+
managed:
110+
name: managed packages
111+
needs: [native-linux, native-macos]
112+
runs-on: ubuntu-latest
113+
steps:
114+
- uses: actions/checkout@v4
115+
116+
- uses: actions/setup-dotnet@v4
117+
with:
118+
dotnet-version: '8.0.x'
119+
120+
- name: Collect runtime packages into a local feed
121+
uses: actions/download-artifact@v4
122+
with:
123+
pattern: packages-native-*
124+
merge-multiple: true
125+
path: local-feed
126+
127+
- name: Build and pack
128+
env:
129+
NUGET_LOCAL_FEED: ${{ github.workspace }}/local-feed
130+
# The test job already covers this, on three operating systems.
131+
SKIP_TESTS: '1'
132+
run: sh ./build/build_managed_packages.sh
133+
134+
- uses: actions/upload-artifact@v4
135+
with:
136+
name: packages-managed
137+
path: packages/*.nupkg
138+
if-no-files-found: error
139+
140+
# Publishing is gated on a tag, so every push and pull request gets built and tested without
141+
# anything reaching the feed.
142+
#
143+
# Note that the repository carries two independent version streams: the runtime packages are
144+
# versioned after the upstream liblouis release (Directory.Build.props), while LibLouis.NET has
145+
# its own version. A tag therefore does not correspond to a single package version, and
146+
# --skip-duplicate makes re-pushing an unchanged runtime package a no-op.
147+
publish:
148+
name: publish to GitHub Packages
149+
if: startsWith(github.ref, 'refs/tags/v')
150+
needs: [native-linux, native-macos, test, managed]
151+
runs-on: ubuntu-latest
152+
steps:
153+
- uses: actions/setup-dotnet@v4
154+
with:
155+
dotnet-version: '8.0.x'
156+
157+
- uses: actions/download-artifact@v4
158+
with:
159+
pattern: packages-*
160+
merge-multiple: true
161+
path: packages
162+
163+
- name: Push
164+
run: >
165+
dotnet nuget push "packages/*.nupkg"
166+
--source https://nuget.pkg.github.com/Notalib/index.json
167+
--api-key ${{ secrets.GITHUB_TOKEN }}
168+
--skip-duplicate

.gitignore

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
.vs
1+
.vs
2+
.idea
3+
.DS_Store
24
**/obj
35
**/bin
6+
7+
# Downloaded upstream liblouis release and everything derived from it.
48
upstream
59
packages
6-
runtime.liblouis/runtimes/win-x64/native/liblouis.dll
7-
runtime.liblouis/runtimes/win-x86/native/liblouis.dll
10+
packages-docker
811
LibLouis.NET.Tables/tables
12+
13+
# Native binaries staged into the per-RID runtime packages by the build scripts.
14+
runtime.*.liblouis/runtimes/
15+
16+
# Rider / ReSharper per-user settings
17+
*.DotSettings.user

Directory.Build.props

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project>
2+
3+
<!--
4+
Single source of truth for the upstream liblouis release that this repository packages.
5+
6+
The shell scripts under build/ read these values back out of this file with sed, so keep
7+
each property on one line in the form <Name>value</Name>.
8+
-->
9+
<PropertyGroup>
10+
<LiblouisVersion>3.33.0</LiblouisVersion>
11+
<LiblouisTarballSha384>2906b0787781c195b4386ee52090d1038990db4134705ad207b2820e790d33d9384ddea1818e32762b8c1f4f9e380a77</LiblouisTarballSha384>
12+
</PropertyGroup>
13+
14+
<!--
15+
Version stamped on the native runtime packages. It tracks the upstream liblouis version so a
16+
consumer can tell at a glance which liblouis they are getting. Override it when a packaging-only
17+
fix has to ship without an upstream bump, e.g. -p:LiblouisPackageVersion=3.33.0.1
18+
-->
19+
<PropertyGroup>
20+
<LiblouisPackageVersion Condition="'$(LiblouisPackageVersion)' == ''">$(LiblouisVersion)</LiblouisPackageVersion>
21+
</PropertyGroup>
22+
23+
</Project>

Dockerfile

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,92 @@
1-
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build
1+
# The pinned platform below is deliberate, see the comment on the FROM line.
2+
# check=skip=FromPlatformFlagConstDisallowed
3+
4+
# Builds the native liblouis binaries for the Linux and Windows runtime identifiers, packs one
5+
# NuGet package per RID, and packs the runtime.liblouis metapackage.
6+
#
7+
# The macOS packages are not built here; they need a macOS host and are produced by the
8+
# native-macos CI job. The managed packages are not built here either, because building them
9+
# resolves runtime.liblouis, which depends on the macOS packages this container cannot produce.
10+
#
11+
# The gcc and llvm-mingw targets are separate stages on purpose. llvm-mingw also ships
12+
# i686-w64-mingw32-gcc and x86_64-w64-mingw32-gcc, so having it installed alongside the Ubuntu cross
13+
# compilers means it can displace them and silently change which toolchain builds win-x86 and
14+
# win-x64. Keeping it out of that stage entirely makes the mistake impossible rather than merely
15+
# documented. BuildKit also builds the independent stages concurrently, so the wall clock is the
16+
# slowest stage rather than the sum.
17+
#
18+
# --platform is pinned because the cross toolchain package names below only exist for amd64. On an
19+
# Apple Silicon machine this runs under emulation: slower, but it works.
20+
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-jammy AS base
221
LABEL org.opencontainers.image.source=https://github.com/Notalib/LibLouis.NET/
322

4-
RUN apt update && \
5-
apt upgrade -y && \
6-
# List of gcc compilers is sadly platform specific, this one works for amd64.
7-
apt install -y build-essential build-essential gcc-aarch64-linux-gnu gcc-mingw-w64-i686 gcc-mingw-w64-x86-64 gcc-i686-linux-gnu unzip m4 less
23+
RUN apt-get update && \
24+
apt-get upgrade -y && \
25+
apt-get install -y --no-install-recommends \
26+
build-essential \
27+
ca-certificates \
28+
curl \
29+
m4 \
30+
xz-utils \
31+
&& rm -rf /var/lib/apt/lists/*
832

33+
ENV PACKAGE_OUTPUT_DIR=/packages
934
WORKDIR /source
10-
ADD . /source
1135

12-
RUN sh ./build.sh
1336

37+
# The five targets Ubuntu has cross compilers for.
38+
FROM base AS gcc-targets
39+
40+
RUN apt-get update && \
41+
apt-get install -y --no-install-recommends \
42+
gcc-i686-linux-gnu \
43+
gcc-aarch64-linux-gnu \
44+
gcc-mingw-w64-i686 \
45+
gcc-mingw-w64-x86-64 \
46+
# The cross gcc packages only Recommend their target libc, so with
47+
# --no-install-recommends they install a compiler that cannot link. Name them explicitly
48+
# rather than dropping the flag, so the requirement is visible.
49+
libc6-dev-i386-cross \
50+
libc6-dev-arm64-cross \
51+
&& rm -rf /var/lib/apt/lists/*
52+
53+
COPY . /source
54+
RUN sh ./build/build_runtime_packages.sh gcc
55+
56+
57+
# win-arm64. Ubuntu has no aarch64 mingw-w64 cross compiler, so this stage uses the prebuilt
58+
# llvm-mingw toolchain, pinned by digest: an unpinned toolchain would silently change what the
59+
# published binaries were built with. Bump both values together when moving to a newer release.
60+
FROM base AS llvm-targets
61+
62+
ARG LLVM_MINGW_VERSION=20260616
63+
ARG LLVM_MINGW_SHA256=534b92e067b22a6b4441f48ae9240a3341b17825d04d577eab0cf85c44b4deda
64+
RUN set -eu; \
65+
archive="llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-ubuntu-22.04-x86_64.tar.xz"; \
66+
curl -fL -o "/tmp/$archive" \
67+
"https://github.com/mstorsjo/llvm-mingw/releases/download/${LLVM_MINGW_VERSION}/$archive"; \
68+
echo "${LLVM_MINGW_SHA256} /tmp/$archive" | sha256sum --check; \
69+
mkdir -p /opt/llvm-mingw; \
70+
tar xf "/tmp/$archive" -C /opt/llvm-mingw --strip-components=1; \
71+
rm "/tmp/$archive"
72+
73+
# The build script prepends this to PATH for the targets that need it. Left off PATH here so there
74+
# is exactly one mechanism selecting the toolchain, in the script, where it is visible.
75+
ENV LLVM_MINGW_BIN=/opt/llvm-mingw/bin
76+
77+
COPY . /source
78+
RUN sh ./build/build_runtime_packages.sh llvm
79+
80+
81+
# The metapackage is pure metadata and needs no toolchain at all.
82+
FROM base AS metapackage
83+
84+
COPY . /source
85+
RUN sh ./build/build_metapackage.sh
86+
87+
88+
# `docker build --output=packages .` exports just the .nupkg files into ./packages.
1489
FROM scratch
15-
COPY --from=build /packages/* /
90+
COPY --from=gcc-targets /packages/* /
91+
COPY --from=llvm-targets /packages/* /
92+
COPY --from=metapackage /packages/* /

0 commit comments

Comments
 (0)