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
Original file line number Diff line number Diff line change
Expand Up @@ -2000,6 +2000,48 @@ face3d, still counts as pure.)

---

### `<pkg>: no licence file found, so this wheel would ship the library's object code with no notice`

**Cause:** a `build.sh` recipe's wheel is synthesised by forge, so nothing carries a
licence into it unless forge finds one. It looks for a **top-level** file in the source
or recipe directory whose name starts with `LICEN[CS]E` / `COPYING` / `COPYRIGHT` /
`NOTICE` (any case) and, finding none, **fails the build**. Every licence in this tree
requires its notice to accompany the binary, and a warning here proved worthless — it sat
unread in a thousand-line log on a job that exits 0, which is how every `flet-lib*` wheel
shipped with no notice at all for years while CI stayed green.

**You will almost always hit this on a VERSION BUMP**, not a new recipe: upstream renamed
or relocated its notice (`LICENSE` → `LICENSES/`, or into a subdirectory). That is exactly
the moment worth stopping, because the alternative is silently shipping a violation.

**Fix:** point at the real file — a path, or a list of them, resolved against the source
then the recipe directory:

```yaml
about:
license_file: docs/FTL.TXT # or: [COPYING, modules/oniguruma/COPYING]
```

Setting it **replaces** auto-discovery, which is also how you *exclude* a notice covering
something the wheel doesn't contain (libiconv's top-level `COPYING` is the GPL for the
`iconv` program build.sh deletes; only `COPYING.LIB` applies to the library). A recipe with
genuinely nothing to ship opts out explicitly, next to a comment saying why:

```yaml
about:
license_file: [] # deliberately none -- <reason>
```

Note an **unset** `license_file` still means "discover for me" — only an empty **list** is
the opt-out.

**Related:** `about.license_file` naming a file that isn't there raises too (a typo or a
moved file, not a preference). Changing licence metadata does not reach pypi.flet.dev until
the recipe's **build number is bumped** — see `forge-ci` § Deploying, "Bump before
republishing". Full authoring guidance in `new-mobile-recipe` § 3.5b.

---

## Diagnostic snippets

### Inspect a wheel's contents
Expand Down
24 changes: 24 additions & 0 deletions .claude/skills/native-recipe-bumps/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,30 @@ patch --dry-run -p1 --ignore-whitespace < /path/to/recipes/<pkg>/patches/mobile-

Render with both `sdk='iphoneos'` and `sdk='android'` whenever the file has SDK conditionals.

## Licences: the two things a bump can invalidate

A bump is the likeliest moment for both, and neither announces itself:

- **The licence FILE moves or is renamed.** forge auto-bundles a top-level
`LICEN[CS]E*`/`COPYING*`/`COPYRIGHT*`/`NOTICE*` from the source or recipe directory, and
**fails the build** when it finds none — so this one stops you, loudly, with the fix in
the error. Point `about.license_file` at the new location (a path or a list). Check it
cheaply before building:
```bash
tar tf downloads/<pkg>-<new>.tar.gz | awk -F/ 'NF==2' | grep -iE 'licen|copying|copyright|notice'
```
- **The licence EXPRESSION changes at a version boundary** — this one is silent, because
`about.license` is just a string forge copies into the metadata. Two real cases:
`libpng` is `Libpng` up to 1.6.35 and `libpng-2.0` after; `libtiff` 4.7.0's `LICENSE.md`
omits the second Berkeley grant covering `tif_lzw.c`, which upstream added in 4.7.1, so
the correct expression differs between those two releases. Re-read the licence text in
the archive you are now shipping from, not the one you shipped last time.

And whichever changed: **licence metadata does not reach pypi.flet.dev until the build
number is bumped** — an existing `<version>-<build>` 409-skips on re-publish. A version
bump usually moves the version anyway, so this mostly bites when you fix metadata *without*
a version change. See `forge-ci` § Deploying.

## Build / debug loop

`forge` takes a *host* (top-level platform name like `iOS`/`android`, or a `platform:arch` / `platform:version:arch` triple) followed by one or more recipe names. There is no `build` subcommand.
Expand Down
82 changes: 82 additions & 0 deletions .claude/skills/new-mobile-recipe/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,88 @@ Renders the meta.yaml for all SDK contexts (iphoneos, iphonesimulator, android)
with a non-matching needle silently no-ops. Always `assert needle in text` before
replacing, or grep the output file for the new content after.

### 3.5b — Licences (`flet-lib*` / build.sh recipes only)

A **Python package** carries its own licence: the build backend copies upstream's METADATA
and licence files into the wheel, and forge preserves them. Nothing to do.

A **build.sh recipe's wheel is synthesised by forge**, so the licence only gets there
because the build does it. Since every licence in this tree requires its notice to
accompany the binary, forge now bundles one automatically: any top-level
`LICEN[CS]E*` / `COPYING*` / `COPYRIGHT*` in the **source** or the **recipe** directory is
copied into `.dist-info/licenses/` and listed as `License-File`. That covers almost every
upstream unchanged — so usually you write nothing.

Three cases need a line in `meta.yaml`:

- **The notice is not at the top level, or is named something else** — point at it:
```yaml
about:
license_file: docs/FTL.TXT # flet-libfreetype
```
An explicit `license_file` is resolved against the source dir, then the recipe dir, and
**replaces** auto-detection: use it when upstream ships several notices and only one
applies to what the wheel actually contains.
- **Machine-readable identifier**, so a licence scanner needn't unpack the wheel:
```yaml
about:
license: LGPL-2.1-or-later # emitted as License-Expression
```
Never guess this. Read the licence text in the archive we ship from, note whether it says
"or (at your option) any later version" (`-or-later`) or names one version (`-only`), and
omit the field when the answer is not clear. A wrong identifier in shipped metadata is
worse than none — the licence *file* is the authoritative artifact and ships either way.
- **`source: null` recipes** have no archive, so build.sh must stage the notice itself into
its working directory (which is the source root):
```bash
cp "$toolchain/NOTICE" ./LICENSE # flet-libcpp-shared, flet-libomp (android)
```
Taking it from the same NDK the binary was copied from means the two cannot drift.

**A recipe that ships different code per platform gets a different licence per platform.**
`about` is inside the Jinja-rendered meta.yaml like everything else, so gate it on `sdk`
rather than giving up and leaving it unset:

```yaml
about:
# {% if sdk == 'android' %}
license: Apache-2.0 WITH LLVM-exception
# {% else %}
license: BSD-3-Clause
# {% endif %}
```

That is `flet-libomp`: Android carries LLVM's `libomp.so` from the NDK, iOS carries the
serial stub build.sh generates, which is this repo's own code. `AND` would be wrong — no
single wheel contains both. The *file* side splits the same way for free, because a
source-dir file shadows a recipe-dir file of the same name: build.sh stages the NDK notice
as `LICENSE` on Android, and on iOS the recipe's own `LICENSE` is what remains.

**Finding no licence is a hard build error**, not a warning — a warning would sit unread in
a thousand-line log on a job that exits 0, which is exactly how every `flet-lib*` wheel came
to ship with no notice while CI stayed green. The error names the directories searched, the
name patterns tried, and the two fixes. You will most likely meet it on a **version bump**
where upstream renamed or relocated its notice; that is the moment you want stopping, and
the fix is one line. A recipe that genuinely has nothing to ship opts out explicitly, next
to a comment saying why:

```yaml
about:
license_file: [] # deliberately none -- <reason>
```

Note an unset `license_file` still means "discover for me" — only an **empty list** is the
opt-out. On success the log prints `Bundling licence file: …`.

Scope the expression to what the wheel *contains*: libiconv ships `COPYING` (GPL, for the
`iconv` program) beside `COPYING.LIB` (LGPL, for the library we actually build).

**Changing licence metadata on an already-published recipe does nothing until the build
number is bumped** — the re-publish 409-skips otherwise, so the fix never reaches
pypi.flet.dev. This is easy to miss precisely because the metadata edit needs no version
change. See `forge-ci` § Deploying, "Bump before republishing"; the error the *build* side
produces is catalogued in `forge-error-catalogue` § "no licence file found".

### 3.6 — Cross-cutting conventions from the ML wave (CMake-heavy recipes)

- **Version as a Jinja constant.** A bare `{% set version = "X.Y.Z" %}` as the first line, reused in `package.version` AND `source.url` — bumps become one-line edits. See `recipes/faiss-cpu/meta.yaml`; onnxruntime uses the same idiom.
Expand Down
5 changes: 4 additions & 1 deletion recipes/flet-libarrow/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package:
# not worth diverging from the flet-lib* convention to skip it.

build:
number: 1
number: 2

source:
# Apache Arrow C++ (curated source release; build dir = its cpp/). pyarrow
Expand All @@ -26,3 +26,6 @@ requirements:
# libarrow.so is C++; on Android it links libc++_shared.so.
- flet-libcpp-shared >=27.2.12479018
# {% endif %}

about:
license: Apache-2.0
5 changes: 5 additions & 0 deletions recipes/flet-libcpp-shared/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ export LIBC_SHARED_SO="$toolchain/sysroot/usr/lib/${HOST_TRIPLET}/libc++_shared.

mkdir -p $PREFIX/lib
cp $LIBC_SHARED_SO $PREFIX/lib

# This recipe has no upstream archive, so nothing carries a licence into the wheel on
# its own. libc++_shared.so is LLVM's, under Apache-2.0 WITH LLVM-exception; take the
# notice from the same NDK the .so was copied from so the two can never drift.
cp "$toolchain/NOTICE" ./NOTICE
5 changes: 4 additions & 1 deletion recipes/flet-libcpp-shared/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ package:
platforms: [android]

build:
number: 10
number: 11

# No upstream archive: build.sh copies libc++_shared.so out of the NDK toolchain.
source: null

about:
license: Apache-2.0 WITH LLVM-exception
7 changes: 5 additions & 2 deletions recipes/flet-libcrc32c/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ package:
version: '{{ version }}'

build:
number: 10
number: 11

source:
url: https://github.com/google/crc32c/archive/refs/tags/{{ version }}.tar.gz

requirements:
build:
- cmake
- cmake

about:
license: BSD-3-Clause
3 changes: 3 additions & 0 deletions recipes/flet-libcurl/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ requirements:

patches:
- config.patch

about:
license: curl
8 changes: 7 additions & 1 deletion recipes/flet-libfreetds/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ source:
url: https://www.freetds.org/files/stable/freetds-{{ version }}.tar.gz

build:
number: 1
number: 2
script_env:
# OpenSSL (for TLS) comes from the python-build support tree, surfaced by the
# `openssl` host requirement at {platlib}/opt (same as cryptography).
Expand All @@ -18,3 +18,9 @@ build:
requirements:
host:
- openssl ^3.0.12

about:
# COPYING_LIB.txt is the 1991 *Library* GPL v2, not 2.1. The plain-GPL
# notices cover src/pool and src/apps, which --disable-apps/server/pool
# excludes -- so GitHub's whole-repo answer of GPL-2.0 is wrong for this wheel.
license: LGPL-2.0-or-later
11 changes: 9 additions & 2 deletions recipes/flet-libfreetype/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package:
version: '{{ version }}'

build:
number: 10
number: 11

source:
url: https://downloads.sourceforge.net/project/freetype/freetype2/{{ version }}/freetype-{{ version }}.tar.gz
Expand All @@ -14,4 +14,11 @@ patches:
- config.patch

about:
license_file: docs/FTL.TXT
# LICENSE.TXT states the dual arm and is what a reader needs first; both
# arm texts ship beside it. MIT covers the bdf/pcf/fthash code compiled in.
# Parentheses are load-bearing: SPDX binds AND tighter than OR.
license_file:
- LICENSE.TXT
- docs/FTL.TXT
- docs/GPLv2.TXT
license: (FTL OR GPL-2.0-or-later) AND MIT
8 changes: 7 additions & 1 deletion recipes/flet-libgdal/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ source:
url: https://github.com/OSGeo/gdal/releases/download/v{{ version }}/gdal-{{ version }}.tar.gz

build:
number: 1
number: 2

requirements:
build:
Expand All @@ -18,3 +18,9 @@ requirements:

patches:
- mobile.patch

# No `about.license`, deliberately: the core is MIT but the wheel is not MIT-only.
# GDAL_BUILD_OPTIONAL_DRIVERS=OFF does not disable GTiff, and the cross build
# forces internal libtiff/libgeotiff/libjpeg/zlib/json-c/qhull/LercLib, which
# LICENSE.TXT does not enumerate. Compose it from a real build's CMake configure
# summary before setting one. LICENSE.TXT ships either way.
7 changes: 6 additions & 1 deletion recipes/flet-libgeos/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# No `about.license`, deliberately: the source headers name no LGPL version
# ("See the COPYING file"), so LGPL-2.1 section 13 lets a recipient choose any
# FSF version -- broader than -or-later, and positively not -only. Neither
# standard identifier is faithful, and src/deps/ryu (Apache-2.0 OR BSL-1.0) is
# linked in besides. The COPYING file ships either way.
{% set version = "3.13.1" %}

package:
name: flet-libgeos
version: '{{ version }}'

build:
number: 1
number: 2

source:
url: http://download.osgeo.org/geos/geos-{{ version }}.tar.bz2
Expand Down
9 changes: 8 additions & 1 deletion recipes/flet-libiconv/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ package:
platforms: [android]

build:
number: 1
number: 2

source:
url: https://ftp.gnu.org/gnu/libiconv/libiconv-{{ version }}.tar.gz

about:
# Pinned deliberately: the top-level COPYING is GPL-3.0 covering the `iconv`
# program and docs, which build.sh deletes. Only COPYING.LIB applies to the
# library this wheel ships, and auto-discovery would bundle both.
license_file: COPYING.LIB
license: LGPL-2.1-or-later
8 changes: 6 additions & 2 deletions recipes/flet-libjpeg/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ source:
url: https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{{ version }}/libjpeg-turbo-{{ version }}.tar.gz

build:
number: 10
number: 11

requirements:
build:
- cmake
- cmake

about:
# TurboJPEG is BSD-3-Clause and the SIMD sources are zlib-licensed; both build.
license: IJG AND BSD-3-Clause AND Zlib
11 changes: 9 additions & 2 deletions recipes/flet-libjq/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ source:
url: https://github.com/jqlang/jq/releases/download/jq-{{ version }}/jq-{{ version }}.tar.gz

build:
number: 11
number: 12

requirements:
build:
- cmake

patches:
- config.patch
- config.patch

about:
# oniguruma is bundled (--with-oniguruma=builtin), so its notice ships too.
license_file:
- COPYING
- modules/oniguruma/COPYING
license: MIT AND BSD-2-Clause AND ICU AND dtoa
5 changes: 4 additions & 1 deletion recipes/flet-libmagic/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ package:
version: '{{ version }}'

build:
number: 1
number: 2

source:
# The `file` project ships a release tarball with a pre-generated ./configure
# (autotools), so no autoreconf is needed (unlike flet-libzbar).
url: https://astron.com/pub/file/file-{{ version }}.tar.gz

about:
license: BSD-2-Clause-Darwin AND BSD-2-Clause
Loading
Loading