Compare commits

...
Author SHA1 Message Date
b26807f5aa
docs(packaging): add the 3.0.0 release entry
All checks were successful
Flatpak / flatpak (push) Successful in 12m53s
C++ exceptions in wasm builds, scripts, dependency notes, the browser
runtime's exit and park, and mingw found where it is; the fixes since
2.2.0; and the two changes that need a look when upgrading: the wasm
sysroot and engine requirements, and siblings inheriting the loading
project's args. A major, because a wasm project.cpp with
-fno-exceptions or an older sysroot, and a project.cpp whose siblings
relied on getting no args, build differently or not at all on this
release.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 06:54:33 +01:00
1d52350b31
feat(ci): render the release notes as Markdown with headings and lists
The release page got the newest <release> block as the plain text of
its paragraphs, which fit two paragraphs of prose and nothing more.
From 3.0.0 the notes group the changes under New, Fixes and Breaking,
each in descending order of importance, and AppStream's description
markup has lists but no headings to carry that.

The metainfo stays the one source of the notes, since the Flatpak
installs it and the tag check reads it. A category is written there as
a paragraph that introduces a list, <p>New</p><ul>...</ul>, which is
how AppStream notes lead into a list anyway, and the CI step renders
such a paragraph as a Markdown heading over its items. A paragraph on
its own stays a paragraph, so the older entries render as before. The
step parses the XML with ElementTree instead of two regexes: entities
are decoded, <em> and <code> get their Markdown spelling, and a code
span keeps text like lib/<triple>/eh from being read as an HTML tag by
the forge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 06:54:33 +01:00
2 changed files with 59 additions and 6 deletions

View file

@ -185,13 +185,38 @@ jobs:
- name: Release notes
if: github.ref_type == 'tag'
run: |
# The newest <release> block's description, as plain text.
# The newest <release> block's description as Markdown, for the
# release page. AppStream has no headings, so a paragraph that
# introduces a list is one: <p>New</p><ul>...</ul> comes out as
# "## New" over its items, the shape the notes take since 3.0.0.
# A paragraph on its own stays a paragraph, as the older entries
# are written. <em> and <code> get their Markdown spelling; a code
# span also keeps text such as lib/<triple>/eh from being read as
# an HTML tag by the forge.
python3 - > release-notes.md <<'EOF'
import re, html
text = open("packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml").read()
block = re.search(r"<release [^>]*>(.*?)</release>", text, re.S).group(1)
paras = re.findall(r"<p>(.*?)</p>", block, re.S)
print("\n\n".join(html.unescape(" ".join(p.split())) for p in paras))
import xml.etree.ElementTree as ET
desc = ET.parse("packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml").getroot().find("releases/release/description")
def text(el):
parts = [el.text or ""]
for child in el:
inner = text(child)
parts.append({"em": f"*{inner}*", "code": f"`{inner}`"}.get(child.tag, inner))
parts.append(child.tail or "")
return " ".join("".join(parts).split())
blocks = list(desc)
out = []
for i, el in enumerate(blocks):
heads_list = i + 1 < len(blocks) and blocks[i + 1].tag in ("ul", "ol")
if el.tag == "p":
out.append(f"## {text(el)}" if heads_list else text(el))
elif el.tag == "ul":
out.append("\n".join(f"- {text(li)}" for li in el))
elif el.tag == "ol":
out.append("\n".join(f"{n}. {text(li)}" for n, li in enumerate(el, 1)))
print("\n\n".join(out))
print("\nInstall:\n\n flatpak remote-add --if-not-exists catcrafts https://flatpak.catcrafts.net/catcrafts.flatpakrepo\n flatpak install catcrafts net.catcrafts.CrafterBuild\n")
EOF
- name: Publish release

View file

@ -25,6 +25,34 @@
</provides>
<content_rating type="oars-1.1"/>
<releases>
<release version="3.0.0" date="2026-10-31">
<description>
<p>New</p>
<ul>
<li>C++ exceptions work in WebAssembly builds. Every <code>wasm32-wasip1</code> unit and the std module are compiled with WebAssembly exception handling in its standardized form (exnref), against the exception-enabled libc++ the sysroot ships under <code>lib/&lt;triple&gt;/eh/</code>, and <code>-lunwind</code> and <code>-lsetjmp</code> are linked automatically, so throw and catch, libc++'s own throws, setjmp and longjmp all work. The Flatpak's WASI sysroot is built with those libraries. A page from <code>EnableWasiBrowserRuntime</code> probes for the feature and names what an older browser lacks instead of failing with a CompileError.</li>
<li>Scripts. A project.cpp can push a name and a C++ callback onto <code>cfg.scripts</code>; <code>crafter-build run &lt;name&gt; [args...]</code> runs it with everything after the name handed over untouched, a bare <code>run</code> lists the scripts, and the callback's return value is the exit status. <code>ScriptContext</code> gives it the project, the project directory, the arguments and <code>Run(command)</code>, which runs a shell command with the terminal attached. <code>Crafter::ShellQuote</code> is exported. Only the scripts of the project crafter-build was pointed at can be run, never a dependency's.</li>
<li>Dependency notes for agents. A library ships an <code>AGENTS.consumer.md</code> beside its project.cpp; <code>crafter-build</code> and <code>crafter-build test</code> keep its dependencies' notes in a marked section of a consumer's <code>AGENTS.md</code>, when the consumer has one, and never create the file. <code>cfg.dependencyNotes</code> picks the dependencies: <code>Transitive</code> (the default), <code>Direct</code> or <code>None</code>, which also removes the section.</li>
<li>In a page from <code>EnableWasiBrowserRuntime</code>, <code>exit</code> and <code>_Exit</code> end the program without running any of its code on the way out, no destructor and no <code>catch (...)</code>, with the status in <code>window.crafter_wasi.exitCode</code>. A program that hands the thread to the browser for good and goes on serving calls imports <code>crafter_runtime.park</code> instead, which never returns and leaves main's locals valid for the calls that follow.</li>
<li>The mingw target is found where it is: by the <code>x86_64-w64-mingw32-gcc</code> on PATH, through <code>CRAFTER_MINGW_DIR</code> naming another prefix, and inside the Flatpak through the host's /usr when it has mingw-w64. Its std module comes from the toolchain's own libstdc++.modules.json, and a toolchain that is not there is a build error saying where it looked, not std::terminate.</li>
</ul>
<p>Fixes</p>
<ul>
<li>A program that holds two builds of one module is refused before anything compiles, with both paths and what tells the builds apart. A sibling project that two paths loaded with different arg lists used to be built twice, every consumer took whichever build clang found first, and the program built cleanly and then read garbage or crashed (#41).</li>
<li>An external dependency shared by the configurations of one build is fetched and built once per build pass instead of once per configuration, and each clone's fetch and build hold a file lock. The tests of a project that copies its externalDependencies into each test pulled the same clone side by side and broke its FETCH_HEAD, which failed every test (#34); they now wait for one fetch.</li>
<li>Incremental builds pick up more of what changed. A shader rebuilds when a file it includes or one of its settings (stage, entry point, defines, include directories) changes, not only its own source. An executable relinks when its link command changed, such as a new link flag or a dependency's library that moved to another cache directory, not only when an object is newer.</li>
<li>Submodules are checked out at the revisions the fetched commit records, after every clone, switch, checkout and pull, so a pinned commit builds against its own submodules and not the default branch's.</li>
<li>A project that loads the same sibling as one of its <code>#pragma crafter dependency</code> tools, with the same args, got the tool's copy back: host tooling with -march=native and -fPIC, whose BMI clang then refused. Host tooling and everything it loads are now kept apart in the project cache.</li>
<li><code>crafter-build format clean</code> formats the rules matching "clean"; it used to delete bin/ and build/ and exit 0.</li>
<li><code>--</code> ends crafter-build's options, as the help has advertised: every later word is an operand of the verb. It used to be forwarded as a project arg and change the variant hash, and the words after it were still read as options.</li>
<li><code>ProjectDependency()</code> works from a static initialiser of a project file; it used to find nothing there and terminate the process.</li>
</ul>
<p>Breaking</p>
<ul>
<li>A wasm build needs a sysroot that ships the exception-enabled libc++ and libunwind (wasi-sdk 33 or later; the Flatpak's does), and stops with one line saying so without one. It refuses <code>-fno-exceptions</code>, <code>-flto</code> in compileFlags or linkFlags, and shared libraries, each with a message that says why; there is no switch back to building without exceptions. The result runs on engines with exnref: Chrome 137, Firefox 131, Safari 18.4 and wasmtime 47 or newer (the bundled one qualifies; an older wasmtime needs <code>-W exceptions=y</code>). An exception that escapes main ends the program as an uncaught WebAssembly exception without calling std::terminate, so catch it in main to report it. The flags are part of the variant hash, so the first wasm build after updating starts in a fresh directory.</li>
<li>A sibling loaded through <code>GitProject</code> or <code>LocalProject</code> without <code>.args</code> now receives the args its loading project.cpp was given, so <code>--target=</code>, <code>--march=</code>, <code>--debug</code> and the project's own flags reach every sibling down the chain; releases up to 2.2.0 forwarded nothing. Set <code>.args = {}</code> to keep the old behaviour, and give every sibling the same list: a project that two paths load with different lists now stops the build. The default is read when the spec is made, so make it inside <code>CrafterBuildProject</code>; a spec made at namespace scope forwards nothing. A <code>#pragma crafter dependency</code> still gets none of the project's args.</li>
</ul>
</description>
</release>
<release version="2.2.0" date="2026-10-16">
<description>
<p>A Shader entry can carry defines, spelled like the compiler's -D (NAME is defined to 1, NAME=VALUE to VALUE), and a variant that names its output, so one source compiles into several outputs: ui.comp.glsl with variant sets becomes ui.comp.sets.spv. A configuration in which two entries share an output name is refused with both named, as are a malformed define and a variant with a path separator.</p>