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
33 changes: 14 additions & 19 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -308,29 +308,18 @@ jobs:
name: nuget-packages
path: artifacts

# The script asserts the result itself: require_windows_frameworks reads the lib/<tfm>/ folders
# out of every merged package and fails the step if any of them carries no Windows assets, so
# a pack that quietly emitted no -windows target framework cannot reach the upload below.
# Repeating that here as a shell one-liner was worse than nothing: with `-o pipefail`, which
# this shell sets, `unzip -l pkg | grep -q ...` reports failure whenever grep matches early
# enough to exit before unzip has finished writing, because unzip then dies of SIGPIPE and its
# 141 becomes the pipeline's status. That is a coin toss on the package's entry order, and it
# came up tails on a package that did have its Windows assets.
- name: Pack the Windows heads and merge them in
shell: bash
run: ./build/AddWindowsAssets.sh "${{ inputs.version }}"

# Asserted rather than assumed: the script merges whatever the pack produced, and a pack that
# quietly emitted no Windows target framework — because the workload was missing, or because
# the IsOSPlatform condition in OpenTok.Net.props stopped matching — would still exit 0 and
# upload the unchanged package.
- name: Check the Windows assets are actually in there
shell: bash
run: |
missing=0
for id in OpenTok.Net OpenTok.Net.Maui; do
pkg="$(ls artifacts/${id}.*.nupkg | grep -v symbols | head -1)"
if unzip -l "${pkg}" | grep -q 'lib/net9.0-windows'; then
echo " ok ${id}"
else
echo "::error::${pkg} has no lib/net9.0-windows*/ assets after the merge"
missing=1
fi
done
exit "${missing}"

- name: Upload packages
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -513,7 +502,12 @@ jobs:
#
# Unlike the Debug sample job, this cannot be fixed by installing both workloads: that job
# runs entirely on macOS, where both exist. This one deliberately builds Android on Linux.
# shell: bash on this step and the next, because the windows leg would otherwise get pwsh —
# which reads the line continuations below as an operator and fails to parse the command
# before ever running it. The two legs on macOS and Linux already had bash by default, which
# is why this only ever surfaced here.
- name: Build the sample in Release
shell: bash
run: |
dotnet build samples/OpenTok.Sample.Maui/OpenTok.Sample.Maui.csproj \
--configuration Release \
Expand All @@ -525,6 +519,7 @@ jobs:
# .app is assembled, and on Android r8 and the packaging step run before the APK is. A failure
# in any of them never gets far enough to produce one.
- name: Check the app was produced
shell: bash
run: |
case "${{ matrix.platform }}" in
ios)
Expand Down
79 changes: 73 additions & 6 deletions build/AddWindowsAssets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,39 @@ esac

PACKAGES=$(grep -v '^#' packages.tsv | grep -v '^[[:space:]]*$' | cut -f1)

# The lib/<tfm>/ folders a package carries for Windows, one per line, or nothing at all.
#
# Read from the zip rather than from the merge's own report: merge-packages.py prints the target
# frameworks it *added*, and a package can advertise a target framework in its nuspec while
# carrying no assets for it — which restores, and then fails to compile at whoever picked it.
windows_frameworks() {
python3 - "$1" <<'PY'
import sys
import zipfile

with zipfile.ZipFile(sys.argv[1]) as package:
frameworks = {
name.split("/")[1]
for name in package.namelist()
if name.startswith("lib/") and "-windows" in name.split("/")[1] and name.count("/") > 1
}

print("\n".join(sorted(frameworks)))
PY
}

# Fails unless the package carries Windows assets, naming it and listing what it does carry.
require_windows_frameworks() {
frameworks=$(windows_frameworks "$1" | tr '\n' ' ')

if [ -z "$(echo "$frameworks" | tr -d ' ')" ]; then
echo "error: $(basename "$1") carries no lib/<tfm>/ assets for Windows." >&2
exit 1
fi

echo " $(basename "$1"): $frameworks"
}

if [ -z "$PACKAGES" ]; then
echo "error: no packages found in build/packages.tsv" >&2
exit 1
Expand All @@ -74,14 +107,18 @@ fi

WIN1_DIR="$OUTPUT/.win9-pass"
WIN2_DIR="$OUTPUT/.win10-pass"
PRIMARY_DIR="$OUTPUT/.primary"
MERGED_DIR="$OUTPUT/.merged"

SDK10_DIR="$(mktemp -d)"
trap 'rm -rf "$SDK10_DIR" "$WIN1_DIR" "$WIN2_DIR" "$MERGED_DIR"' EXIT
trap 'rm -rf "$SDK10_DIR" "$WIN1_DIR" "$WIN2_DIR" "$PRIMARY_DIR" "$MERGED_DIR"' EXIT
cat > "$SDK10_DIR/global.json" <<EOF
{ "sdk": { "version": "$PASS2_SDK", "rollForward": "latestFeature" } }
EOF

# Every package this run merged into, by file name, to re-check once the loop is done.
MERGED_PACKAGES=""

for package in $PACKAGES; do
project="$ROOT/src/$package/$package.csproj"

Expand All @@ -90,7 +127,7 @@ for package in $PACKAGES; do
exit 1
fi

rm -rf "$WIN1_DIR" "$WIN2_DIR" "$MERGED_DIR"
rm -rf "$WIN1_DIR" "$WIN2_DIR" "$PRIMARY_DIR" "$MERGED_DIR"

echo "==> packing $package windows heads ($PASS1_BAND band)"
dotnet pack "$project" \
Expand All @@ -108,13 +145,43 @@ for package in $PACKAGES; do
$VERSION_ARG \
-o "$WIN2_DIR")

# merge-packages.py merges every package it finds in the primary directory, so the primary
# here cannot be artifacts/ itself: that holds all the packages, while the Windows passes hold
# only the one just packed, and every other id would be reported as having no counterpart.
# Stage just this package's files — the pass directory names them, so no version is needed.
echo "==> merging windows target frameworks into $package"
mkdir -p "$PRIMARY_DIR"
nupkg=""
for asset in "$WIN1_DIR"/*.nupkg "$WIN1_DIR"/*.snupkg; do
[ -f "$asset" ] || continue
name=$(basename "$asset")
if [ ! -f "$OUTPUT/$name" ]; then
echo "error: $name is not in $OUTPUT — run build/BuildNugets.sh on macOS first and" >&2
echo " bring its artifacts/ here, at the same version." >&2
exit 1
fi
cp "$OUTPUT/$name" "$PRIMARY_DIR/$name"
case "$name" in *.nupkg) nupkg="$name" ;; esac
done

# Merged in two steps because merge-packages.py takes one additional directory at a time, and
# into a scratch directory because it will not read and write the same place.
echo "==> merging windows target frameworks into $package"
python3 "$ROOT/build/merge-packages.py" "$OUTPUT" "$WIN1_DIR" "$MERGED_DIR"
python3 "$ROOT/build/merge-packages.py" "$PRIMARY_DIR" "$WIN1_DIR" "$MERGED_DIR"
python3 "$ROOT/build/merge-packages.py" "$MERGED_DIR" "$WIN2_DIR" "$OUTPUT"

# Straight after the merge rather than only at the end, because the next package is packed
# against this one out of artifacts/ — so a package that came out of the merge without its
# Windows assets would otherwise be discovered later, having already been built against.
require_windows_frameworks "$OUTPUT/$nupkg"
MERGED_PACKAGES="$MERGED_PACKAGES $nupkg"
done

rm -rf "$WIN1_DIR" "$WIN2_DIR" "$MERGED_DIR"
rm -rf "$WIN1_DIR" "$WIN2_DIR" "$PRIMARY_DIR" "$MERGED_DIR"

echo "==> windows assets added to $OUTPUT"
# And again over the finished directory. Everything above packs and merges one package while the
# rest sit in the same directory, so "each package was right when it was merged" is not the same
# claim as "every package is still right now".
echo "==> windows assets in $OUTPUT"
for nupkg in $MERGED_PACKAGES; do
require_windows_frameworks "$OUTPUT/$nupkg"
done
26 changes: 26 additions & 0 deletions samples/OpenTok.Sample.Maui/OpenTok.Sample.Maui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,30 @@
<PackageReference Include="OpenTok.Net.Maui" Version="[$(OpenTokPackageVersion)]" />
</ItemGroup>

<!--
Works around https://github.com/dotnet/maui/issues/32683, the same way and for the same reason
as src/OpenTok.Net.Maui/OpenTok.Net.Maui.csproj — see the long version of this comment there.

In short: Microsoft.Maui.Core and Microsoft.Maui.Controls.Core ship a .pri naming
Microsoft.Maui/Handlers/HybridWebView/HybridWebView.js, neither package carries that file, and
Windows App SDK 1.8 expands a referenced package's .pri into copy-to-output items. A library
that has the target and an app that does not still fails, because the app does its own
expansion of the same MAUI packages.

Remove this once the MAUI packages stop naming files they do not ship.
-->
<Target Name="OpenTokDropMissingPriPayloadFiles"
AfterTargets="AddPriPayloadFilesToCopyToOutputDirectoryItems"
Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' ">
<ItemGroup>
<_OpenTokMissingPriPayloadFiles Include="@(_AllChildProjectItemsWithTargetPath)"
Condition=" !Exists('%(FullPath)') " />
<_AllChildProjectItemsWithTargetPath Remove="@(_OpenTokMissingPriPayloadFiles)" />
</ItemGroup>

<Message Importance="normal"
Condition=" '@(_OpenTokMissingPriPayloadFiles)' != '' "
Text="Dropped PRI payload files that no referenced package carries: @(_OpenTokMissingPriPayloadFiles->'%(Filename)%(Extension)')" />
</Target>

</Project>
2 changes: 1 addition & 1 deletion samples/OpenTok.Sample.Maui/Platforms/Windows/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
x:Class="OpenTok.Sample.Maui.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maui="using:Microsoft.Maui.MauiWinUIApplication">
xmlns:maui="using:Microsoft.Maui">
</maui:MauiWinUIApplication>
48 changes: 48 additions & 0 deletions src/OpenTok.Net.Maui/OpenTok.Net.Maui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,52 @@
<PackageReference Include="OpenTok.Net" Version="[$(OpenTokCorePackageVersion)]" />
</ItemGroup>

<!--
On Windows only, and for the reason spelled out on OpenTok.Net's own Windows references:
OpenTok.Client's targets arrive here through OpenTok.Net and add its x64 native payload as
@(Content), which MakePri would index into OpenTok.Net.Maui.pri — a package naming files it does
not carry, and an MSB3030 in the build of every app that consumes it. Nothing here needs the
payload: this is a library being packed, not an app being run.

Update rather than a second Include, so the reference above stays the single place the version
is stated; conditioned per target framework, because the android and ios heads genuinely do need
the build assets of the platform bindings underneath. PrivateAssets="none" keeps everything
flowing to consumers, where the payload does belong.
-->
<ItemGroup Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' ">
<PackageReference Update="OpenTok.Net" ExcludeAssets="build" PrivateAssets="none" />
</ItemGroup>

<!--
Works around https://github.com/dotnet/maui/issues/32683.

Microsoft.Maui.Core and Microsoft.Maui.Controls.Core ship a .pri that names
Microsoft.Maui/Handlers/HybridWebView/HybridWebView.js, and neither package carries that file.
Windows App SDK 1.8 expands a referenced package's .pri into copy-to-output items, so every
Windows build that resolves those packages now fails with MSB3030 for a file that has never
existed — verified against microsoft.maui.core 9.0.120, whose lib/net9.0-windows10.0.19041/
holds Microsoft.Maui.pri and no .js at all.

Dropping payload items whose file is not on disk rather than matching HybridWebView.js by name,
which is the workaround in that issue: the same expansion has been reported to invent
WebView2Loader.dll as well, and a name list is a list of the ones seen so far. Nothing is lost —
these items exist only to be copied, and a file that is not there cannot be.

Remove this target once the MAUI packages this builds against stop naming files they do not
ship; it is a no-op from then on.
-->
<Target Name="OpenTokDropMissingPriPayloadFiles"
AfterTargets="AddPriPayloadFilesToCopyToOutputDirectoryItems"
Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' ">
<ItemGroup>
<_OpenTokMissingPriPayloadFiles Include="@(_AllChildProjectItemsWithTargetPath)"
Condition=" !Exists('%(FullPath)') " />
<_AllChildProjectItemsWithTargetPath Remove="@(_OpenTokMissingPriPayloadFiles)" />
</ItemGroup>

<Message Importance="normal"
Condition=" '@(_OpenTokMissingPriPayloadFiles)' != '' "
Text="Dropped PRI payload files that no referenced package carries: @(_OpenTokMissingPriPayloadFiles->'%(Filename)%(Extension)')" />
</Target>

</Project>
26 changes: 24 additions & 2 deletions src/OpenTok.Net/OpenTok.Net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,30 @@
own reference list rather than arriving transitively.
-->
<ItemGroup Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows' ">
<PackageReference Include="OpenTok.Net.Win" Version="[$(OpenTokWinPackageVersion)]" />
<PackageReference Include="OpenTok.Client" Version="[$(OpenTokClientPackageVersion)]" />
<!--
ExcludeAssets="build" on both, PrivateAssets="none" on both. The pair is the same one
OpenTok.Net.Win uses on its own OpenTok.Client reference, for the same reason one level
further out — its comment is the long version of this one.

OpenTok.Client's build/OpenTok.Client.targets globs its package directory and adds
opentok.dll and the three capturers as @(Content). That is right for an application and wrong
for this: the Windows head is a WinUI library, so MakePri indexes every @(Content) item into
OpenTok.Net.pri under a folder named after the library. The .pri ships in lib/<tfm>/ and the
native DLLs deliberately do not, so a consumer — OpenTok.Net.Maui first, an app after it —
expands that .pri, resolves each recorded payload file beside it, and fails with MSB3030 for
lib/<tfm>/OpenTok.Net/opentok.dll and friends. Excluding the build assets leaves nothing to
index. Both references carry it because the targets reach this project by either path, and an
exclusion on one edge does nothing about the other.

PrivateAssets="none" is what still gets the payload into an app: NuGet's default keeps a
dependency's build assets private, and under it a consumer would restore OpenTok.Client,
never import its targets, and build an app with no opentok.dll in it. Flowing them costs
nothing here, because this package carries no copy of the payload.
-->
<PackageReference Include="OpenTok.Net.Win" Version="[$(OpenTokWinPackageVersion)]"
ExcludeAssets="build" PrivateAssets="none" />
<PackageReference Include="OpenTok.Client" Version="[$(OpenTokClientPackageVersion)]"
ExcludeAssets="build" PrivateAssets="none" />

<!--
Named here even though OpenTok.Net.Win already brings it. The Windows head compiles against
Expand Down
Loading