public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/package-notes] f44: Version 0.17
@ 2026-06-02 16:28
0 siblings, 0 replies; only message in thread
From: @ 2026-06-02 16:28 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/package-notes
Branch : f44
Commit : 9909a5098bbe9e4e017fa7c50b2309ff47905934
Author : Zbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Date : 2026-02-06T12:07:17+01:00
Stats : +416/-22 in 7 file(s)
URL : https://src.fedoraproject.org/rpms/package-notes/c/9909a5098bbe9e4e017fa7c50b2309ff47905934?branch=f44
Log:
Version 0.17
- This is a major version upgrade that allows automatic generation of
Requires/Recommends/Suggests from package dlopen notes. See the new
macro file for details of use.
- The new fileattr plugin is in the package-notes subpackage, so that
needs to be installed to activate the feature.
---
diff --git a/0001-Fix-type-in-group_by_feature.patch b/0001-Fix-type-in-group_by_feature.patch
new file mode 100644
index 0000000..5721460
--- /dev/null
+++ b/0001-Fix-type-in-group_by_feature.patch
@@ -0,0 +1,23 @@
+From a2f9622f3f21b80b3cfbf9ce76fbc2dc721ecf13 Mon Sep 17 00:00:00 2001
+From: Arthur Petitpierre <bartaba@smallstone.org>
+Date: Wed, 7 Jan 2026 17:48:14 +0700
+Subject: [PATCH 1/6] Fix type in group_by_feature
+
+In group_by_feature, fix typo "recommened" -> "recommended".
+---
+ dlopen-notes.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/dlopen-notes.py b/dlopen-notes.py
+index 3d5b651fd9..e55800c98d 100755
+--- a/dlopen-notes.py
++++ b/dlopen-notes.py
+@@ -98,7 +98,7 @@ def group_by_feature(elffiles):
+ # ]
+ for elffiles in elffiles:
+ for note in elffiles.notes():
+- prio = Priority[note.get('priority', 'recommened')]
++ prio = Priority[note.get('priority', 'recommended')]
+ feature_name = note['feature']
+
+ try:
diff --git a/0001-rpm-use-a-r-.-guard-around-package-notes-LDFLAGS.patch b/0001-rpm-use-a-r-.-guard-around-package-notes-LDFLAGS.patch
deleted file mode 100644
index 6fca390..0000000
--- a/0001-rpm-use-a-r-.-guard-around-package-notes-LDFLAGS.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-From e52dfeba8ba36fc765fbe0ff0d4c93ed9aac493b Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20B=C3=A9rat?= <fberat@redhat.com>
-Date: Tue, 27 Jan 2026 11:31:16 +0100
-Subject: [PATCH] rpm: use a '%{!r: ... }' guard around package notes LDFLAGS
-
-...to prevent double inclusion when performing a relocatable link ('-r').
-Relates to https://bugzilla.redhat.com/show_bug.cgi?id=2362272.
----
- rpm/redhat-package-notes.in | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/rpm/redhat-package-notes.in b/rpm/redhat-package-notes.in
-index 3a19b1be8a..1297ae5261 100644
---- a/rpm/redhat-package-notes.in
-+++ b/rpm/redhat-package-notes.in
-@@ -1,2 +1,2 @@
- *link:
--+ --package-metadata={\"type\":\"rpm\",\"name\":\"%:getenv(RPM_PACKAGE_NAME \",\"version\":\"%:getenv(RPM_PACKAGE_VERSION -%:getenv(RPM_PACKAGE_RELEASE \",\"architecture\":\"%:getenv(RPM_ARCH \",\"osCpe\":\"@OSCPE@\"}))))
-++ %{!r:--package-metadata={\"type\":\"rpm\",\"name\":\"%:getenv(RPM_PACKAGE_NAME \",\"version\":\"%:getenv(RPM_PACKAGE_VERSION -%:getenv(RPM_PACKAGE_RELEASE \",\"architecture\":\"%:getenv(RPM_ARCH \",\"osCpe\":\"@OSCPE@\"}))))}
diff --git a/0002-dlopen-notes-group-two-helper-functions-together.patch b/0002-dlopen-notes-group-two-helper-functions-together.patch
new file mode 100644
index 0000000..99f7bef
--- /dev/null
+++ b/0002-dlopen-notes-group-two-helper-functions-together.patch
@@ -0,0 +1,37 @@
+From 2ad3aca11e60a9f50574a65df2232dfb58597f2b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Mon, 12 Jan 2026 13:57:06 +0100
+Subject: [PATCH 2/6] dlopen-notes: group two helper functions together
+
+---
+ dlopen-notes.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/dlopen-notes.py b/dlopen-notes.py
+index e55800c98d..f103611c00 100755
+--- a/dlopen-notes.py
++++ b/dlopen-notes.py
+@@ -19,6 +19,11 @@ try:
+ except ImportError:
+ print_json = print
+
++def dictify(f):
++ def wrap(*args, **kwargs):
++ return dict(f(*args, **kwargs))
++ return functools.update_wrapper(wrap, f)
++
+ def listify(f):
+ def wrap(*args, **kwargs):
+ return list(f(*args, **kwargs))
+@@ -63,11 +68,6 @@ class ELFFileReader:
+
+ yield from j
+
+-def dictify(f):
+- def wrap(*args, **kwargs):
+- return dict(f(*args, **kwargs))
+- return functools.update_wrapper(wrap, f)
+-
+ @dictify
+ def group_by_soname(elffiles):
+ for elffile in elffiles:
diff --git a/0003-rpm-add-fileattr-multifile-generator.patch b/0003-rpm-add-fileattr-multifile-generator.patch
new file mode 100644
index 0000000..23ddbd1
--- /dev/null
+++ b/0003-rpm-add-fileattr-multifile-generator.patch
@@ -0,0 +1,270 @@
+From d6e833810909a4278f59c19dbc748424ca5e153f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Tue, 13 Jan 2026 14:14:22 +0100
+Subject: [PATCH 3/6] rpm: add fileattr multifile generator
+
+This allows dlopen notes to be turned into appropriate dependencies
+automatically. The dlopen_notes.attr file needs to be installed into
+%{_fileattrsdir}.
+
+By default, dependencies are generated for all files that have package
+notes. I think this is a reasonable default because it makes the whole
+feature easier to discover. In more realistic cases, esp. with
+multiple subpackages, it's likely that the packager may need to
+configure the distribution of dependencies between subpackages.
+
+One shortcoming of the scheme is that everything is per file, so it's
+not possible to say that dependencies generated from a feature should
+be assigned to a different subpackage. This is how the feature is
+designed in rpm.
+
+The opt-out mechanism is a bit clunky. The first option I considered
+was to tell the user to undefine
+%__dlopen_notes_requires/recommends/suggests, but that requires three
+lines of boilerplate. And might not be forwards-compatible if we add
+new features in the future. The second option would be to tell the
+user to define __dlopen_notes_requires/recommends/suggests_opts to
+%nil. But that has similar problems. I think it's nice to have an
+obvious oneliner to handle this. Unfortunately, when I tried to use
+ %__dlopen_notes_requires %{?_dlopen_notes_generator:%{_dlopen_notes_generator} ...}
+ %__dlopen_notes_recommends %{?_dlopen_notes_generator:%{_dlopen_notes_generator} ...}
+ %__dlopen_notes_suggests %{?_dlopen_notes_generator:%{_dlopen_notes_generator} ...}
+in the .attr file, when the package has %undefine _dlopen_notes_generator,
+we still end up with the macro being expanded. Maybe I misunderstood
+the macro expansion logic. The approach with 'true' is clunky, but
+it works fine.
+
+Thanks to Neal Gompa for the suggestion to use this protocol.
+
+The new interface is new, independent of the existing options
+--feature, --rpm-recommends, --rpm-requires that were previously added
+to support rpms. Unfortunately, with the fileattr protocol, the
+old way to specify information is not useful. Instead of trying
+to shoehorn the new metadata into existing options, I think it's
+easier to add a new set with clear semantics.
+---
+ README.md | 13 ++++++
+ dlopen-notes.py | 96 +++++++++++++++++++++++++++++++++++++++++--
+ rpm/dlopen_notes.attr | 44 ++++++++++++++++++++
+ 3 files changed, 150 insertions(+), 3 deletions(-)
+ create mode 100644 rpm/dlopen_notes.attr
+
+diff --git a/README.md b/README.md
+index 8b27e19de3..f7fa518630 100644
+--- a/README.md
++++ b/README.md
+@@ -97,6 +97,19 @@ $ dlopen-notes /usr/lib64/systemd/libsystemd-shared-257.so
+ ...
+ ```
+
++### Using the rpm fileattr generator
++
++The tool that processes package notes can be hooked into the rpm build process
++to automatically generate virtual `Requires`, `Recommends`, and `Suggests` dependencies.
++
++The rpm file attribute mechanism is described in
++[rpm-dependency-generators.7](https://rpm-software-management.github.io/rpm/man/rpm-dependency-generators.7).
++
++This tool implements the 'multifile' protocol:
++it reads the list of files on stdin and outputs a list of virtual dependencies.
++
++See the `rpm/dlopen_notes.attr` file for invocation details and options.
++
+ ## Requirements
+ * binutils (>= 2.39)
+ * mold (>= 1.3.0)
+diff --git a/dlopen-notes.py b/dlopen-notes.py
+index f103611c00..4fa30d8e89 100755
+--- a/dlopen-notes.py
++++ b/dlopen-notes.py
+@@ -7,6 +7,7 @@ Read .note.dlopen notes from ELF files and report the contents.
+
+ import argparse
+ import enum
++import fnmatch
+ import functools
+ import json
+ import sys
+@@ -84,6 +85,16 @@ class Priority(enum.Enum):
+ def __lt__(self, other):
+ return self.value < other.value
+
++ def rpm_name(self):
++ if self == self.__class__.suggested:
++ return 'Suggests'
++ if self == self.__class__.recommended:
++ return 'Recommends'
++ if self == self.__class__.required:
++ return 'Requires'
++ raise ValueError
++
++
+ def group_by_feature(elffiles):
+ features = {}
+
+@@ -143,6 +154,52 @@ def generate_rpm(elffiles, stanza, filter):
+ soname = next(iter(note['soname'])) # we take the first — most recommended — soname
+ yield f"{stanza}: {soname}{suffix}"
+
++def rpm_fileattr_generator(args):
++ if args.rpm_features is not None:
++ if not any(fnmatch.fnmatch(args.subpackage, pattern[0])
++ for pattern in args.rpm_features):
++ # Current subpackage is not listed, nothing to do.
++ # Consume all input as required by the protocol.
++ sys.stdin.read()
++ return
++
++ for file in sys.stdin:
++ file = file.strip()
++ if not file:
++ continue # ignore empty lines
++
++ elffile = ELFFileReader(file)
++ suffix = '()(64bit)' if elffile.elffile.elfclass == 64 else ''
++
++ first = True
++
++ for note in elffile.notes():
++ # Feature name is optional. Allow this to be matched
++ # by the empty string ('') or a wildcard glob ('*').
++ feature = note.get('feature', '')
++
++ if args.rpm_features is not None:
++ for package_pattern,feature_pattern in args.rpm_features:
++ if (fnmatch.fnmatch(args.subpackage, package_pattern) and
++ fnmatch.fnmatch(feature, feature_pattern)):
++ break
++ else:
++ # not matched
++ continue
++ else:
++ # if no mapping, print all features at the suggested level
++ level = Priority[note.get('priority', 'recommended')].rpm_name()
++ if level != args.rpm_fileattr:
++ continue
++
++ if first:
++ print(f';{file}')
++ first = False
++
++ soname = next(iter(note['soname'])) # we take the first — most recommended — soname
++ print(f'{soname}{suffix}')
++
++
+ def make_parser():
+ p = argparse.ArgumentParser(
+ description=__doc__,
+@@ -187,10 +244,28 @@ def make_parser():
+ metavar='FEATURE1,FEATURE2',
+ help='Generate rpm Recommends for listed features',
+ )
++ p.add_argument(
++ '--rpm-fileattr',
++ metavar='TYPE',
++ help='Run as rpm fileattr generator for TYPE dependencies',
++ )
++ p.add_argument(
++ '--subpackage',
++ metavar='NAME',
++ default='',
++ help='Current subpackage NAME',
++ )
++ p.add_argument(
++ '--rpm-features',
++ metavar='SUBPACKAGE:FEATURE,SUBPACKAGE:FEATURE',
++ type=lambda s: [x.split(':', maxsplit=1) for x in s.split(',')],
++ action='extend',
++ help='Specify subpackage:feature mapping',
++ )
+ p.add_argument(
+ 'filenames',
+- nargs='+',
+- metavar='filename',
++ nargs='*',
++ metavar='FILENAME',
+ help='Library file to extract notes from',
+ )
+ p.add_argument(
+@@ -207,15 +282,30 @@ def parse_args():
+ and not args.sonames
+ and args.features is None
+ and args.rpm_requires is None
+- and args.rpm_recommends is None):
++ and args.rpm_recommends is None
++ and args.rpm_fileattr is None):
+ # Make --raw the default if no action is specified.
+ args.raw = True
+
++ if args.rpm_fileattr is not None:
++ if (args.filenames
++ or args.raw
++ or args.features is not None
++ or args.rpm_requires
++ or args.rpm_recommends):
++ raise ValueError('--rpm-generate cannot be combined with most options')
++
++ if args.rpm_fileattr is None and not args.filenames:
++ raise ValueError('At least one positional FILENAME parameter is required')
++
+ return args
+
+ if __name__ == '__main__':
+ args = parse_args()
+
++ if args.rpm_fileattr is not None:
++ sys.exit(rpm_fileattr_generator(args))
++
+ elffiles = [ELFFileReader(filename) for filename in args.filenames]
+ features = group_by_feature(elffiles)
+
+diff --git a/rpm/dlopen_notes.attr b/rpm/dlopen_notes.attr
+new file mode 100644
+index 0000000000..abe3e8547c
+--- /dev/null
++++ b/rpm/dlopen_notes.attr
+@@ -0,0 +1,44 @@
++# SPDX-License-Identifier: MIT-0
++#
++# This file is part of the package-notes package.
++#
++#
++# The spec file for a package can specify which features are listed
++# and at which level, using
++# '--rpm-features=SUBPACKAGE1:FEATURE1,SUBPACKAGE2:FEATURE2' option in
++# the '__dlopen_notes_TYPE_opts' macro, where TYPE is one of
++# 'requires', 'recommends', or 'suggests'. The macro should be declared
++# in the spec file using:
++# %define __dlopen_notes_TYPE_opts SUBPACKAGE:FEATURE…
++# e.g.
++# %define __dlopen_notes_recommends_opts *:zstd
++#
++# The option accepts multiple comma-separated pairs, and can also be
++# specified multiple times. Both the subpackage name and feature name
++# can be a glob. If configuration is omitted, the priority recommended
++# in the notes is used.
++#
++# The '--subpackage=SUBPACKAGE' option (inserted below) tells the generator
++# which subpackage is being processed.
++#
++# For example, for a package using compression libraries, we can say
++# that the 'package-libs' subpackage shall carry 'Requires' on all the
++# libraries needed for the 'zstd' feature, all subpackages shall carry
++# 'Recommends' on all the libraries needed for the 'gzip' feature, and
++# the 'package' subpackage shall carry 'Suggests' for any feature
++# matching 'lzma' or 'bzip*'.
++#
++# %define __dlopen_notes_requires_opts --rpm-features=package-libs:zstd
++# %define __dlopen_notes_recommends_opts --rpm-features=*:gzip
++# %define __dlopen_notes_suggests_opts --rpm-features=package:lzma,package:bzip*
++#
++# To opt out, undefine the %_dlopen_notes_generator macro:
++# %undefine _dlopen_notes_generator
++
++%_dlopen_notes_generator %{_bindir}/dlopen-notes
++
++%__dlopen_notes_requires %{!?_dlopen_notes_generator:true }%{_dlopen_notes_generator} --subpackage='%{name}' --rpm-fileattr=Requires
++%__dlopen_notes_recommends %{!?_dlopen_notes_generator:true }%{_dlopen_notes_generator} --subpackage='%{name}' --rpm-fileattr=Recommends
++%__dlopen_notes_suggests %{!?_dlopen_notes_generator:true }%{_dlopen_notes_generator} --subpackage='%{name}' --rpm-fileattr=Suggests
++%__dlopen_notes_protocol multifile
++%__dlopen_notes_magic ^.*ELF (32|64)-bit.*$
diff --git a/0004-fakelib-add-test-for-the-new-fileattr-plugin.patch b/0004-fakelib-add-test-for-the-new-fileattr-plugin.patch
new file mode 100644
index 0000000..8b19a33
--- /dev/null
+++ b/0004-fakelib-add-test-for-the-new-fileattr-plugin.patch
@@ -0,0 +1,57 @@
+From 9f6142b43b730d2ff51cf2b355050548f5f8fc9e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Tue, 13 Jan 2026 09:53:07 +0100
+Subject: [PATCH 4/6] fakelib: add test for the new fileattr plugin
+
+This is a package that "builds" by copying two files: a systemd
+library that is known to use dlopen notes and another glibc library
+that doesn't have them. It can be built with 'rpmbuild' or 'fedpkg local'.
+For example:
+ (cd fakelib && fedpkg local && echo 'Requires:' && rpm -qpv --requires x86_64/fakelib-0-1.fc44.x86_64.rpm && echo 'Recommends:' && rpm -qpv --recommends x86_64/fakelib-0-1.fc44.x86_64.rpm && echo 'Suggests:' && rpm -qpv --suggests x86_64/fakelib-0-1.fc44.x86_64.rpm)
+---
+ fakelib/fakelib.spec | 36 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+ create mode 100644 fakelib/fakelib.spec
+
+diff --git a/fakelib/fakelib.spec b/fakelib/fakelib.spec
+new file mode 100644
+index 0000000000..40714cbeb1
+--- /dev/null
++++ b/fakelib/fakelib.spec
+@@ -0,0 +1,36 @@
++# SPDX-License-Identifier: MIT-0
++#
++# This file is part of the package-notes package.
++
++Name: fakelib
++Version: 0
++Release: %autorelease
++Summary: %{name}
++
++License: None
++
++%define __dlopen_notes_requires_opts --rpm-features=fakelib:gcrypt,fakelib:lz4
++%define __dlopen_notes_recommends_opts --rpm-features=*:zstd
++%define __dlopen_notes_suggests_opts --rpm-features=fakelib:lzm[abc]
++
++#undefine _dlopen_notes_generator
++
++%description
++%{summary}.
++
++%prep
++
++%build
++
++%install
++install -Dt %{buildroot}/usr/lib64/ /usr/lib64/libsystemd.so.0
++ln -s libsystemd.so.0 %{buildroot}/usr/lib64/libsystemd.so
++install -Dt %{buildroot}/usr/lib64/ /usr/lib64/libmvec.so.1
++
++%files
++/usr/lib64/libsystemd.so.0
++/usr/lib64/libsystemd.so
++/usr/lib64/libmvec.so.1
++
++%changelog
++%autochangelog
diff --git a/0005-rpm-use-a-r-.-guard-around-package-notes-LDFLAGS.patch b/0005-rpm-use-a-r-.-guard-around-package-notes-LDFLAGS.patch
new file mode 100644
index 0000000..be26cf0
--- /dev/null
+++ b/0005-rpm-use-a-r-.-guard-around-package-notes-LDFLAGS.patch
@@ -0,0 +1,20 @@
+From e52dfeba8ba36fc765fbe0ff0d4c93ed9aac493b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20B=C3=A9rat?= <fberat@redhat.com>
+Date: Tue, 27 Jan 2026 11:31:16 +0100
+Subject: [PATCH 5/6] rpm: use a '%{!r: ... }' guard around package notes
+ LDFLAGS
+
+...to prevent double inclusion when performing a relocatable link ('-r').
+Relates to https://bugzilla.redhat.com/show_bug.cgi?id=2362272.
+---
+ rpm/redhat-package-notes.in | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/rpm/redhat-package-notes.in b/rpm/redhat-package-notes.in
+index 3a19b1be8a..1297ae5261 100644
+--- a/rpm/redhat-package-notes.in
++++ b/rpm/redhat-package-notes.in
+@@ -1,2 +1,2 @@
+ *link:
+-+ --package-metadata={\"type\":\"rpm\",\"name\":\"%:getenv(RPM_PACKAGE_NAME \",\"version\":\"%:getenv(RPM_PACKAGE_VERSION -%:getenv(RPM_PACKAGE_RELEASE \",\"architecture\":\"%:getenv(RPM_ARCH \",\"osCpe\":\"@OSCPE@\"}))))
+++ %{!r:--package-metadata={\"type\":\"rpm\",\"name\":\"%:getenv(RPM_PACKAGE_NAME \",\"version\":\"%:getenv(RPM_PACKAGE_VERSION -%:getenv(RPM_PACKAGE_RELEASE \",\"architecture\":\"%:getenv(RPM_ARCH \",\"osCpe\":\"@OSCPE@\"}))))}
diff --git a/package-notes.spec b/package-notes.spec
index 5a549ce..c040d89 100644
--- a/package-notes.spec
+++ b/package-notes.spec
@@ -1,5 +1,5 @@
Name: package-notes
-Version: 0.16
+Version: 0.17
Release: %autorelease
Summary: ELF Package and Dlopen Notes
License: 0BSD
@@ -9,7 +9,11 @@ Source: https://github.com/systemd/package-notes/archive/v%{version_no_t
BuildArch: noarch
Requires: python3dist(pyelftools)
-Patch: 0001-rpm-use-a-r-.-guard-around-package-notes-LDFLAGS.patch
+Patch: 0001-Fix-type-in-group_by_feature.patch
+Patch: 0002-dlopen-notes-group-two-helper-functions-together.patch
+Patch: 0003-rpm-add-fileattr-multifile-generator.patch
+Patch: 0004-fakelib-add-test-for-the-new-fileattr-plugin.patch
+Patch: 0005-rpm-use-a-r-.-guard-around-package-notes-LDFLAGS.patch
%description
This package provides rpm macros to generate an '.note.package' ELF note in
@@ -25,6 +29,7 @@ See https://systemd.io/ELF_DLOPEN_METADATA/ for the overview and details.
%files
%{_bindir}/dlopen-notes
+%{_fileattrsdir}/dlopen_notes.attr
%{_mandir}/man1/dlopen-notes.1*
%package srpm-macros
@@ -54,7 +59,8 @@ sed "s|@OSCPE@|$(cat /usr/lib/system-release-cpe)|" rpm/redhat-package-notes.in
%make_install
install -Dt %{buildroot}%{_rpmconfigdir}/redhat/ rpm/redhat-package-notes
-install -m0644 -Dt %{buildroot}%{_rpmmacrodir}/ rpm/macros.package-notes-srpm
+install -m0644 -Dt %{buildroot}%{_rpmmacrodir}/ rpm/macros.package-notes-srpm
+install -m0644 -Dt %{buildroot}%{_fileattrsdir}/ rpm/dlopen_notes.attr
%changelog
%autochangelog
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-06-02 16:28 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-02 16:28 [rpms/package-notes] f44: Version 0.17
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox