Skip to content
Closed
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Pick a license variant, then reference the platform you build for.
| `GPLv2` | GPL-2.0 |
| `GPLv3` | GPL-3.0 |

Every platform package carries upstream's `THIRD-PARTY-NOTICES.txt` at its root: the effective
licence, FFmpeg's build and source details, and every bundled library's licence text. NuGet does
not copy it into your build or publish output; distributing it with your app is up to you.

```
DevEnvy.FFmpeg.Binaries path helper, no binaries
DevEnvy.FFmpeg.Binaries.<variant>.Runtime.<rid> one platform's binaries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

## Revisions during implementation

The sections below describe the approved design. Eight things changed while building it, each
because something was measured or reviewed rather than assumed. Where a section further down
The sections below describe the approved design. Nine things changed while building it or
since, each because something was measured, reviewed or changed upstream rather than assumed. Where a section further down
contradicts this list, this list is current.

0. **Final package shape (supersedes items 1, 2 and 8 below).** One package per
Expand Down Expand Up @@ -37,8 +37,9 @@ contradicts this list, this list is current.
loaded resolver builds versioned Unix names (`libavcodec.so.61`) and cannot load Android's
unversioned `.so` files or iOS frameworks, so depending on it would advertise mobile bindings
that do not work. The helper is now pure path resolution; consumers add a binding themselves.
4. **`legal/` is packed at the package root, never under `runtimes/**/native/`.** Anything below
`native/` is a native asset NuGet copies into every consumer's build and publish output.
4. **The licence notices are packed at the package root, never under `runtimes/**/native/`.**
Anything below `native/` is a native asset NuGet copies into every consumer's build and
publish output. (Originally `legal/`; see item 9 for the single file that replaced it.)
5. **`release.yml` is not tag-triggered.** The original design had it triggered by a tag *and*
creating that tag, which cannot work. It now runs on a merge to `main` or by hand, and is
idempotent: a series is released when its computed version has no `v*` tag yet.
Expand All @@ -51,6 +52,13 @@ contradicts this list, this list is current.
the bit and `read` does not, so shared libraries were never affected.
8. **Package count is 57, not 53** — the four `.Rid` metas, plus the helper, 4 metas, 44 runtime
and 4 Apple packages.
9. **`legal/` became one `THIRD-PARTY-NOTICES.txt`.** Upstream moved its archives to the
`ffmpeg-build` engine, which drops the `legal/` directory and instead puts a single
`THIRD-PARTY-NOTICES.txt` at every archive's root: the effective licence, FFmpeg's build and
source details, and every bundled library's licence text. The iOS bundle carries one too, at
the root beside the `.xcframework`s, covering all three slices. Every platform and Apple
package ships that file at its package root; a pack whose archive lacks it fails rather than
shipping binaries without their licence texts.

One review finding was investigated and **not** acted on. Codex flagged that `linux-musl-x64`
falls back to `linux-x64` in the RID graph, so the all-platform meta can have NuGet select glibc
Expand Down Expand Up @@ -133,6 +141,7 @@ never downloads.

All twelve carry a `legal/` directory holding the cell's license notice plus every bundled
dependency's license. It is shipped in every package — it is the LGPL compliance payload.
(Superseded by `THIRD-PARTY-NOTICES.txt` at the archive root; see revision 9.)

Since upstream release `9.0.1.5`, MoltenVK is statically linked into the iOS libraries, so
every iOS cell now contains exactly the same six xcframeworks. The staging code still
Expand Down Expand Up @@ -399,7 +408,7 @@ Every download is verified against the release's `SHA256SUMS` before extraction.
- no symlinked or duplicate libraries survived staging (the size regression this guards against
is roughly 3x)
- no `include/` directory in any package
- `legal/` present in every platform package
- `THIRD-PARTY-NOTICES.txt` at the root of every platform package, and nowhere else
- `libc++_shared.so` present in both Android packages
- every runtime package's payload sits under `runtimes/{rid}/native/`
- each meta's dependency groups match the TFM table above
Expand Down
4 changes: 2 additions & 2 deletions scripts/audit-upstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,13 @@ def audit(root: Path, name: str):
def add(kind, detail, path=''):
f['issues'].append({'kind': kind, 'detail': detail, 'file': path})

entries = [p for p in root.rglob('*') if not p.is_dir() and 'legal' not in p.parts]
entries = [p for p in root.rglob('*') if not p.is_dir() and p.name != 'THIRD-PARTY-NOTICES.txt']
real = [p for p in entries if not p.is_symlink()]
links = [p for p in entries if p.is_symlink()]
f['files'] = len(entries)
f['real'] = len(real)
f['symlinks'] = len(links)
f['has_legal'] = (root / 'legal').is_dir() or any('legal' in p.parts for p in root.rglob('*'))
f['has_notices'] = (root / 'THIRD-PARTY-NOTICES.txt').is_file()

# Byte-identical real files: upstream symlinks these on Linux, so any platform
# that ships them as copies is inconsistent and inflates the download.
Expand Down
29 changes: 20 additions & 9 deletions scripts/pack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ DOWNLOAD_DIR="${REPO_ROOT}/.downloads/${UPSTREAM_TAG}"
STAGING_DIR="${REPO_ROOT}/staging"
NUSPEC_DIR="${REPO_ROOT}/.nuspec-gen"
STUB_CSPROJ="${REPO_ROOT}/src/Packaging/Packaging.csproj"
NOTICES="THIRD-PARTY-NOTICES.txt" # at the root of every upstream archive

# Upstream artifact name -> .NET RID. Upstream calls 32-bit ARM "armhf"; .NET calls it linux-arm.
declare -A RID_FOR_ARTIFACT=(
Expand Down Expand Up @@ -266,15 +267,19 @@ if [[ "${PHASE}" == "all" || "${PHASE}" == "base" ]]; then
*) normalize_desktop "${work}" ;;
esac

# legal/ is packed at the package root, never under runtimes/{rid}/native/.
# Anything beneath native/ is a native asset to NuGet, so license text left
# there is copied into every consumer's build and publish output, and the
# per-dependency notices collide across packages on a flattened publish.
if [[ -d "${work}/legal" ]]; then
mkdir -p "${stage}/legal"
mv "${work}/legal"/* "${stage}/legal/"
rmdir "${work}/legal"
# Upstream puts one THIRD-PARTY-NOTICES.txt at the archive root: the
# effective licence, FFmpeg's build and source details, and every bundled
# library's licence text. It is packed at the package root, never under
# runtimes/{rid}/native/. Anything beneath native/ is a native asset to
# NuGet, so a notices file left there is copied into every consumer's
# build and publish output and collides across packages on a flattened
# publish. A missing file fails the pack: shipping binaries without their
# licence texts is not a smaller package, it is a non-compliant one.
if [[ ! -f "${work}/${NOTICES}" ]]; then
echo "ERROR: ${tarball} has no ${NOTICES}" >&2
exit 1
fi
mv "${work}/${NOTICES}" "${stage}/${NOTICES}"

echo " staged ${PACKAGE_CELL[${cell}]}/${rid}"
done
Expand Down Expand Up @@ -310,6 +315,10 @@ if [[ "${PHASE}" == "all" || "${PHASE}" == "base" ]]; then
unpacked="${STAGING_DIR}/.apple-${PACKAGE_CELL[${cell}]}"
rm -rf "${unpacked}"; mkdir -p "${unpacked}"
tar -xzf "${DOWNLOAD_DIR}/${tarball}" -C "${unpacked}"
if [[ ! -f "${unpacked}/${NOTICES}" ]]; then
echo "ERROR: ${tarball} has no ${NOTICES}" >&2
exit 1
fi

for rid in "${!APPLE_SLICE[@]}"; do
slice="${APPLE_SLICE[${rid}]}"
Expand All @@ -328,7 +337,9 @@ if [[ "${PHASE}" == "all" || "${PHASE}" == "base" ]]; then
exit 1
fi

[[ -d "${unpacked}/legal" ]] && cp -R "${unpacked}/legal" "${stage}/"
# The bundle's one notices file covers all three slices, so every
# Apple package carries it whole.
cp "${unpacked}/${NOTICES}" "${stage}/${NOTICES}"
echo " staged ${PACKAGE_CELL[${cell}]}/${rid} (${found} frameworks from the ${slice} slice)"
done
rm -rf "${unpacked}"
Expand Down
15 changes: 11 additions & 4 deletions scripts/verify-packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
_spec.loader.exec_module(_audit)
_pe, _SYSTEM_DLL = _audit.pe, _audit.SYSTEM_DLL

# Upstream's single notices file: the effective licence, FFmpeg's build and
# source details, and every bundled library's licence text.
NOTICES = "THIRD-PARTY-NOTICES.txt"

directory = pathlib.Path(sys.argv[1])
packages = sorted(directory.glob("*.nupkg"))
if not packages:
Expand Down Expand Up @@ -127,15 +131,18 @@ def doubled(path):
check(not bad, f"{name}: doubled path segments (PackagePath/%(RecursiveDir) bug): {bad[:2]}")

# Anything under runtimes/**/native is a native asset NuGet copies into the
# consumer's output. Licence text belongs at the package root instead.
check(not any("native/" in n and "/legal/" in n for n in names),
f"{name}: legal/ is nested under native/ and would be copied into consumer output")
# consumer's output, and frameworks/ is linked into the app. Licence text
# belongs at the package root instead, once.
misplaced = [n for n in names
if n.rsplit("/", 1)[-1] == NOTICES and n != NOTICES]
check(not misplaced,
f"{name}: {NOTICES} outside the package root would be copied into consumer output: {misplaced[:2]}")

# These are file containers. A stray compiled assembly means IncludeBuildOutput leaked.
if payload:
check(not any(n.startswith("lib/") and n.endswith(".dll") for n in names),
f"{name}: contains a lib/*.dll — IncludeBuildOutput leaked into a native package")
check(any(n.startswith("legal/") for n in names), f"{name}: ships no legal/ notices")
check(NOTICES in names, f"{name}: ships no {NOTICES} at the package root")
check(bool(payload), f"{name}: has no native payload at all")

if payload and "frameworks/" not in "".join(payload):
Expand Down
3 changes: 2 additions & 1 deletion src/Packaging/Apple.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
directory portion would drop the bundle name from %(RecursiveDir) and
flatten all six frameworks on top of each other. -->
<None Include="$(StagingDir)frameworks/**" PackagePath="frameworks/" Pack="true" />
<None Include="$(StagingDir)legal/**" PackagePath="legal/" Pack="true" />
<!-- At the package root, as in Runtime.csproj: never copied into a consumer's app. -->
<None Include="$(StagingDir)THIRD-PARTY-NOTICES.txt" PackagePath="" Pack="true" />
<None Include="$(TargetsFile)" PackagePath="build/$(PackageId).targets" Pack="true" />
<None Include="$(TargetsFile)" PackagePath="buildTransitive/$(PackageId).targets" Pack="true" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Packaging/Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<None Include="$(StagingDir)native/**" PackagePath="runtimes/$(Rid)/native/" Pack="true" />
<!-- Notices go at the package root. Anything under runtimes/**/native is a
native asset to NuGet and would be copied into every consumer's output. -->
<None Include="$(StagingDir)legal/**" PackagePath="legal/" Pack="true" />
<None Include="$(StagingDir)THIRD-PARTY-NOTICES.txt" PackagePath="" Pack="true" />
<None Include="$(TargetsFile)" PackagePath="build/$(PackageId).targets" Pack="true" />
<None Include="$(TargetsFile)" PackagePath="buildTransitive/$(PackageId).targets" Pack="true" />
</ItemGroup>
Expand Down
Loading