feat(qs): automate OSS string database builds#1327
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces build_oss_db.py to automate building OSS string databases from vcpkg static libraries using the jh tool, along with a default configuration file libraries.json and updated documentation in readme.md. The review feedback identifies critical bugs where the script attempts to parse the CSV output of the jh tool as JSONL in both Converter.parse and count_jsonl_rows. Additionally, the reviewer recommends skipping database writes for libraries that fail to build to prevent data loss, and checking if self.info_dir exists before calling iterdir() to avoid a potential crash.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Adds floss/qs/db/data/oss/build_oss_db.py that automates the vcpkg + jh pipeline for building OSS string databases: 1. install static libraries via vcpkg 2. extract string features (and function names) via jh 3. convert to JSONL and compress with gzip The converter deduplicates within each library (one metadata object per unique string) and emits each library as its own .jsonl.gz file, matching the schema expected by floss.qs.db.oss.OpenSourceStringDatabase. The script accepts a JSON config via --config or env vars and supports selecting individual libraries, controlling output directory, and continuing past per-library build failures.
592ba36 to
f63d643
Compare
Adds .github/workflows/build-oss-db.yml that runs the new build script on a bi-weekly cron (the 1st and 15th of each month at 00:00 UTC) and on manual dispatch. After each run, the workflow opens a PR against quantumstrand with the regenerated .jsonl.gz files if any of them changed. The workflow caches Cargo and vcpkg to keep build times reasonable, builds jh from the lancelot source, and runs the build script with --continue-on-error so a single failed library does not block the others. lancelot is pinned to williballenthin/lancelot HEAD.
f63d643 to
e797372
Compare
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
williballenthin
left a comment
There was a problem hiding this comment.
i think every 2 weeks is a reasonable starting point. if this is too noisy, we can reduce the frequency.
ultimately, i like that this demonstrates how to rebuild the databases, rather than having the databases be some magic artifact that can't be reproduced. i'm not convinced they need to be rebuilt so often, but i don't think it hurts.
| "boost-test", | ||
| "wolfssl", | ||
| "boost-wave", | ||
| "curl", |
There was a problem hiding this comment.
isn't zlib a dep of curl, so does that mean the zlib strings get filter as duplicate, rather than being assigned to zlib?
how do you handle this dependency issue?
There was a problem hiding this comment.
vcpkg builds curl and zlib as separate static .lib files so jh only sees zlib's symbols when processing zlib.lib directly. When processing libcurl.lib, it only extracts strings from curl's own compiled code. it doesn't re-extract zlib's symbols just because curl links against it.
you can also see a very minimal string overlap in my local fork: vee1e#9
367: ...zlib.jsonl.gz (149 entries, 8 removed as shared with other libraries)
There was a problem hiding this comment.
agree that when the dependency is via linking, then things may work out. but in some cases the dependency may be vendored, ie, source code copied into the project. in this case, i think the strings will be detected as duplicates.
to be clear, i believe this is a hard problem to handle generically. though maybe it can also be solved by a little research and hacks/configs/shorcuts.
There was a problem hiding this comment.
instead of removing duplicates altogether should we be upfront to the user and tell them exactly how commonly a string was found? maybe if its truly unique we can indicate it as a "pure" indicator tag, and with every subsequent instance of the string being found we can somehow show the library tag (#zlib etc) as "less" of a true oracle for its source.
There was a problem hiding this comment.
Keeping duplicates around may be the safer bet for now until we see more of this in practice or do more research into the overlaps.
| path: | | ||
| C:\vcpkg\installed | ||
| C:\vcpkg\downloads | ||
| key: ${{ runner.os }}-vcpkg-oss-db-${{ hashFiles('flare-floss/floss/qs/db/data/oss/build_oss_db.py') }} |
There was a problem hiding this comment.
seems like this keys the hash on the generation script, not the contents of the libraries. am i reading that right?
There was a problem hiding this comment.
i think what we'd want to use is a vcpkg lock file, if that exists
There was a problem hiding this comment.
seems like this keys the hash on the generation script, not the contents of the libraries. am i reading that right?
yup, not perfect ofc, i was thinking of using the libraries.json config file as the hash so it updates whenever we add/remove a lib to build
i think what we'd want to use is a vcpkg lock file, if that exists
the requirement for that is fulfilled by the libraries.json file already imo
I think we can drop C:\vcpkg\installed from the cache because vcpkg names files in downloads/ by their content hash (e.g. openssl-3.4.1.tar.gz with a sha512 in the filename). When vcpkg installs a package it checks downloads/ first, then if the tarball is already there and the hash matches, it skips the network fetch and uses it directly.
There was a problem hiding this comment.
ah ok so it's not a cache of the built artifacts, but of the source downloads.
i still think this cache key isn't right, and probably confusing as is (maybe just a comment is needed).
There was a problem hiding this comment.
i still think this cache key isn't right, and probably confusing as is (maybe just a comment is needed).
yeah we can add comments on why the decision was made. maybe we can forgo the cache entirely?
There was a problem hiding this comment.
i think that's fine. if we end up spending obviously a lot of computer resources maybe we can optimize some, but let's not do that prematurely.
| @@ -0,0 +1,73 @@ | |||
| { | |||
| "triplet": "x64-windows-static", | |||
| "compiler": "msvc143", | |||
There was a problem hiding this comment.
how is this chosen and when should this be updated?
There was a problem hiding this comment.
what effect, if any, does it have on the compilation?
There was a problem hiding this comment.
how is this chosen and when should this be updated?
taken directly from the existing qs documentation
flare-floss/floss/qs/db/data/oss/readme.md
Lines 48 to 52 in 0fbe890
what effect, if any, does it have on the compilation?
i tried the builds locally on macosx-arm64. no significant differences in string counts was observed
There was a problem hiding this comment.
i probably picked 143 at the time because it was probably the latest version or something. i assume there's not a major difference between versions, but we should document our expectations and assumptions. that's why i raise it here.
There was a problem hiding this comment.
What builds did you try?
Using raw strings on both I got this:
| Library | macOS | MinGW | In Both | mac Only | Win Only |
|---|---|---|---|---|---|
| binn | 212 | 401 | 3 | 209 | 398 |
| brotli | 2,768 | 3,281 | 1,087 | 1,681 | 2,194 |
| bzip2 | 571 | 632 | 63 | 508 | 569 |
| curl | 5,996 | 6,869 | 1,972 | 4,024 | 4,897 |
| flatcc | 751 | 1,085 | 87 | 664 | 998 |
| fmt | 719 | 1,246 | 59 | 660 | 1,187 |
| freetype | 4,938 | 5,985 | 954 | 3,984 | 5,031 |
| libarchive | 5,328 | 9,169 | 1,446 | 3,882 | 7,723 |
| libflac | 2,349 | 3,775 | 382 | 1,967 | 3,393 |
| libjpeg-turbo | 4,000 | 3,786 | 320 | 3,680 | 3,466 |
| liblzma | 1,182 | 1,653 | 159 | 1,023 | 1,494 |
| libogg | 226 | 269 | 81 | 145 | 188 |
| libpng | 1,909 | 2,111 | 436 | 1,473 | 1,675 |
| libtheora | 1,171 | 1,690 | 39 | 1,132 | 1,651 |
| libvorbis | 3,273 | 4,796 | 620 | 2,653 | 4,176 |
| libwebp | 3,185 | 4,803 | 117 | 3,068 | 4,686 |
| libxml2 | 8,506 | 9,200 | 3,874 | 4,632 | 5,326 |
| lz4 | 540 | 1,290 | 28 | 512 | 1,262 |
| lzo | 604 | 811 | 13 | 591 | 798 |
| mbedtls | 6,931 | 7,592 | 2,509 | 4,422 | 5,083 |
| openal-soft | 7,862 | 17,976 | 2,093 | 5,769 | 15,883 |
| openssl | 53,464 | 43,900 | 13,148 | 40,316 | 30,752 |
| opus | 2,729 | 3,224 | 581 | 2,148 | 2,643 |
| opusfile | 284 | 565 | 14 | 270 | 551 |
| parson | 245 | 343 | 5 | 240 | 338 |
| physfs | 1,202 | 1,588 | 76 | 1,126 | 1,512 |
| sqlite3 | 8,335 | 7,518 | 1,221 | 7,114 | 6,297 |
| tre | 330 | 595 | 34 | 296 | 561 |
| zlib | 674 | 828 | 52 | 622 | 776 |
| zstd | 2,157 | 5,250 | 133 | 2,024 | 5,117 |
=== binn macOS-only (arm64 machine code) ===
'\t@9j' ← random bytes, not text
' #[z9' ← ditto
'@9+\t@9,' ← ditto
'K)@)' ← ditto
'R)#@' ← ditto
'_AddValue' ← symbol name (mangled C function)
'_AdvanceDataPos' ← symbol name
'__.SYMDEF SORTED'← macOS BSD ar archive metadata
'#1/12 1782384003 501 20 644 57180 `'
← macOS BSD ar archive metadata
=== binn mingw-only (x86_64 machine code) ===
'\tw}A' ← random bytes
'([^_]A\A]A^A_' ← fragments of x86_64 instructions
'8[^_]A\A]A^A_' ← ditto
'AWAVAUATUWVSH' ← x86_64 function prologue bytes
'AVWVSH' ← ditto
'/ 1783197601 0 0 0 0 2520 `' ← GNU ar archive metadata
So there is a difference in strings but the total count remains similar. I didn't look at which strings overlapped before, hence the slightly wrong conclusion that there was no difference. But most of the lack of overlap can be attributed to:
- Random printable byte runs in compiled code that look like strings to
strings -n 4. Different ISAs produce different random byte runs, so the noise never overlaps. - Section names differ. Mach-O has __TEXT, __cstring etc. while COFF has .rdata, .pdata, .xdata
- Symbol name prefixes are different too. Mach-O adds a leading _ (_AddValue), COFF doesn't (AddValue)
It could be worth building another db set for different platforms if we want to cover all possible executables, but it'll increase build times and cost. I think its a fair point to debate imo
There was a problem hiding this comment.
The comment here was on the msvc version :) interesting additional information though. With our PE focus we should stick to Windows for now.
|
|
||
| The `build_oss_db.py` script automates the steps above and additionally | ||
| removes strings that appear in two or more libraries from every database. | ||
| A shared string is not a unique identifier for any specific library, so |
There was a problem hiding this comment.
as noted above: unless one library links or vendors another library.
| $ gzip -c zlib.jsonl > zlib.jsonl.gz | ||
| ``` | ||
|
|
||
| The `build_oss_db.py` script automates the steps above and additionally |
There was a problem hiding this comment.
i like that the logic lives in this script, and that the GH workflow is minimal. this reduces our dependence on GH, which i believe is prudent.
me and @mr-tz had a few back and forths with this. I ran a few tests on vcpkg versions and found out that ~biweekly is the best to capture around 5-8 update events because libraries update on different schedules. vcpkg update times (in days) that we found:
but I still do agree, i think we can get a better estimate after letting this cook for a while, so the frequency is volatile for now. we should also think about how many update events we should/need to capture — weekly would give us 3-4, monthly 10-15 from my tests. |
|
Ran the workflow on my own fork — 118ish minutes for the whole thing to complete |
mr-tz
left a comment
There was a problem hiding this comment.
Nice work. Further discussion in the threads Willi opened is needed.
|
|
||
| # Fallback library list when neither --libraries nor libraries.json is | ||
| # provided. The actual production list lives in libraries.json. | ||
| DEFAULT_LIBRARIES: List[str] = [ |
There was a problem hiding this comment.
Are these necessary or helpful here?
There was a problem hiding this comment.
yeah i don't think so. removing it.
imho a more interesting metric is how frequently does an important string get added to the db. project releases don't necessarily imply meaningful additions to the databases. likewise, there's probably some study that could be done about strings getting removed. and then, a scenario where strings are added and then removed without that middle release getting captured (such as a zlib version string or something). would a more comprehensive approach therefore be to build every release? what are the pros/cons there? we don't necessarily need to do all this research, just raising some ideas. |
yeah i did some preliminary research on this for a few libs. results are here:
the almost equal share of
yeah hence my point that we shouldn't wait too long to build because of the missing middle, and we shouldn't wait too little because of wasted build time for not a lot of new information being gained. |

Summary
Adds an automated pipeline for rebuilding the FLOSS Quantumstrand OSS string databases. The pipeline is implemented by a new Python script,
floss/qs/db/data/oss/build_oss_db.py, and a new GitHub Actions workflow,.github/workflows/build-oss-db.yml, that runs the script on a bi-weekly cron (the 1st and 15th of each month at 00:00 UTC) and on manual dispatch.What the script does
The script automates the existing vcpkg plus jh technique described in
floss/qs/db/data/oss/readme.md:Per library, it produces one
.jsonl.gzfile in the output directory, matching the schema consumed byfloss.qs.db.oss.OpenSourceStringDatabase.Two correctness fixes are baked into the converter:
metadata_by_stringsemantics.floss/qs/main.py:query_library_string_databasewould tag the same string as#<lib1>,#<lib2>, and so on, producing false-positive library attributions at query time.The script also merges into any
.jsonl.gzalready present in the output directory rather than replacing them. New entries win on string collisions, which is the right behavior when a library version changes, and libraries that are not in the current--librarieslist are still re-checked against the cross-library dedup and rewritten if the shared string set changes. This makes it safe to run the script locally with--libraries <one-lib>without losing the other databases.What the workflow does
.github/workflows/build-oss-db.yml:workflow_dispatch.cargo build --release -p lancelot-bin).--continue-on-errorso a single failed library does not block the others.quantumstrandwith the regenerated.jsonl.gzfiles if anything changed.The auto-update PR step is what makes this end-to-end. The script plus workflow is closed-loop: any change in upstream vcpkg package versions surfaces as a database diff, which becomes a small PR with metrics.
Todo
Errors in pipeline
ncurses,s2nandliboqsare only available on windows, i'm considering removing this one because its doubtful it'll appear in windows samplesclBLASis hitting a rust assertion errorCode review
scripts/and add copyright