feat(json-c): add json-c/json-c formula - #121
Conversation
Translate the Conan Center json-c recipe (snapshot ffe30df101afd4dc95aac2f14b25bf345e64d7be, version 0.18) into an LLAR Formula serving upstream tag json-c-0.18-20240915. The build uses the CMake helper with the library-only configuration the Conan recipe selects for its defaults: static archive with -fPIC, BUILD_TESTING and BUILD_APPS off, and a normalized lib/ install layout. Consumer metadata is derived from the installed json-c.pc pkg-config file (static query, so the private -lm dependency is reported). onTest compiles and runs the Conan test_package consumer against the installed CMake package in a build tree independent of the onBuild scratch tree so it also passes on a cache hit. No dependencies: the upstream source and the Conan recipe declare none. Co-authored-by: fennoai[bot] <231223108+fennoai[bot]@users.noreply.github.com> Co-authored-by: MeteorsLiu <17515813+MeteorsLiu@users.noreply.github.com>
There was a problem hiding this comment.
FennoAI Review — json-c/json-c Formula
Overall this is a well-structured Formula that follows the LLAR conventions closely: id, versions.json.path, and the directory json-c/json-c all agree; the filename stem Jsonc is a valid Go identifier; fromVer is a non-empty string literal aligned with the directory; c.configure/c.build/c.install are called without result assignment or ! as required; os.mkdirAll/os.writeFile use the ! failure form; and the capout + lastErr + ctx.setMetadata metadata block matches the reference. The cmake defines and the -lm/pkg-config comments were verified accurate against the json-c-0.18-20240915 source (json-c.pc.in Libs.private: @LIBS@, upstream DISABLE_STATIC_FPIC branch). Performance and security passes found no issues.
Two low-severity findings on the onTest consumer block are left inline. Neither is a correctness bug; both are about the temp-checkout convention and comment accuracy.
Reviewers should still confirm BUILD_STATIC_LIBS, BUILD_APPS, and DISABLE_STATIC_FPIC exist in the CMake of this exact upstream tag, and that llar test passes after both a fresh build and a cache hit.
| onTest ctx => { | ||
| installDir := ctx.outputDir | ||
|
|
||
| // Build the consumer in a tree independent of the onBuild scratch tree so |
There was a problem hiding this comment.
onTest synthesizes consumer.c and CMakeLists.txt and writes them into ctx.SourceDir (testDir := ctx.SourceDir + "/_llar_consumer"), mutating the temporary upstream checkout. The reference's "Installed-Output Tests" section says test source files should come from the fresh ctx.SourceDir checkout or from files shipped in the Formula module (read via ctx.Proj.readFile); the example treats ctx.SourceDir + "/consumer" as a pre-existing subdir, not a place to generate files. Consider shipping consumer.c/CMakeLists.txt in the Formula module (read with ctx.Proj.readFile) or writing them to a scratch dir outside the source checkout.
Also, the comment's stated reason is inaccurate: the separate tree is not what makes the test "run on a cache hit." On a cache hit LLAR creates a fresh ctx.SourceDir and runs onTest against the persisted ctx.outputDir, so onBuild's _build never exists there regardless. A build tree separate from _build is the right convention, but its real purpose is avoiding collision with onBuild's tree during a cache-miss build-then-test run — not enabling cache-hit tests. Suggest rewording to attribute it to that (or to convention).
|
|
||
| id "json-c/json-c" | ||
|
|
||
| fromVer "json-c-0.18-20240915" |
There was a problem hiding this comment.
@fennoai check this comparator, does this formula need a custom comparator?
There was a problem hiding this comment.
Comparator check — no custom comparator needed
Short answer: no. json-c/json-c is fine on LLAR's default GNU-style numeric-segment comparator. You do not need a Jsonc_cmp.gox.
Why. Per the formula reference, without a _cmp.gox LLAR compares version strings with its GNU-style comparator (numeric runs compared numerically, non-numeric runs lexically), and you should "add one comparator only when actual upstream tags require a different ordering." json-c's upstream tags all share one shape:
json-c-0.15-20200726
json-c-0.16-20220414
json-c-0.17-20230812
json-c-0.18-20240915 <- this Formula's fromVer
json-c-0.19-20260627 <- current latest
Format is json-c-<MAJOR>.<MINOR>-<YYYYMMDD>. Under GNU comparison:
- The shared
json-c-prefix and the0.segment tie. - The minor segment decides:
18 < 19numerically → correct ordering. - The
-YYYYMMDDsuffix only ever acts as a monotonically-increasing tie-breaker (dates rise with releases), so it never contradicts the version ordering.
So the ordering the engine produces already matches upstream release order, and the single fromVer "json-c-0.18-20240915" threshold selects exactly the intended range (0.18 and newer). Your own validation confirms this end-to-end: llar test --verbose ./json-c/json-c --os linux --arch amd64 resolved to the latest tag json-c-0.19-20260627 and this Formula served it, which only works if 0.18-... is correctly ordered not newer than 0.19-....
When you would need one. A custom _cmp.gox (compareVer(a, b) => semver.Compare(...) or a hand-rolled rule) is only warranted if upstream tags mixed orderings the GNU comparator gets wrong — e.g. non-zero-padded segments that sort lexically wrong, pre-release/rc suffixes, or a numbering scheme change. json-c has none of those, and semver.Compare would actually be wrong here since these tags are not valid Go semver strings. The reference also warns to keep exactly one comparator per module, so adding an unnecessary one is a net negative.
Recommendation: leave it as-is.
There was a problem hiding this comment.
@fennoai test it with newer version, i need a verification report.
There was a problem hiding this comment.
Verification report — newer version (json-c-0.19-20260627) ✅
Ran the real goplus/llar toolchain against this Formula, installed exactly as CI does, targeting the newer upstream tag. The fromVer "json-c-0.18-20240915" threshold correctly served the newer 0.19 source, and every run passed (exit 0).
Environment
| Component | Version |
|---|---|
| llar | built from goplus/llar@main — go install -ldflags="-checklinkname=0" ./cmd/llar (verbatim CI step) |
| go | 1.24.0 (auto-selected from .llar-source/go.mod) |
| cmake | 3.22.1 |
| gcc | 11.4.0 |
| pkg-config | 0.29.2 |
| host | linux / amd64 |
Commands & results
| # | Command | Resolved source | Result |
|---|---|---|---|
| 1 | llar test --verbose ./json-c/json-c --os linux --arch amd64 (exact CI command) |
json-c-0.19-20260627 |
✅ exit 0 — fresh build + install + consumer run |
| 2 | Re-run of #1 (cache-hit path) | json-c-0.19-20260627 |
✅ exit 0 — onBuild skipped, onTest rebuilt & ran the consumer |
| 3 | llar test -v ./json-c/json-c@json-c-0.19-20260627 --os linux --arch amd64 (explicit newer selector) |
json-c-0.19-20260627 |
✅ exit 0 |
What was verified
- Comparator/selection works with the newer tag. With no version pinned (run feat: add LLAR formula SKILL.md #1), the selector resolved the latest upstream tag
json-c-0.19-20260627and served it from the0.18Formula — confirming the default GNU comparator orders0.18-… < 0.19-…correctly, end-to-end. Explicitly pinning@json-c-0.19-20260627(run build(zlib): migrate from cmake to autotools build system #3) selects the same Formula and passes. - Build. Static archive built and linked cleanly:
[100%] Linking C static library libjson-c.a→Built target json-c, installed underdebugconfiguration. - Installed tree (
…/json-c@json-c-0.19-20260627-amd64-linux):lib/libjson-c.ainclude/json-c/*.h(18 headers)lib/cmake/json-c/(json-c-config.cmake,json-c-targets.cmake,json-c-targets-debug.cmake)lib/pkgconfig/json-c.pc→Version: 0.19
- Metadata.
pkg-config --staticcorrectly surfaces the private math dep:-I…/include -I…/include/json-c -L…/lib -lm -ljson-c - Consumer /
onTest. Thetest_package-style consumer (find_package(json-c REQUIRED CONFIG)+json-c::json-c) built and ran, printing the expected:{ "Categories": [ "c", "c++" ] } - Cache-hit correctness. Run feat: add zlib formula (madler/zlib) #2 exercised the cache-hit path —
onBuildwas skipped, yetonTestindependently reconfigured, rebuilt, and ran the consumer in its own tree (_llar_consumer), still printing the expected JSON. This validates the "test valid on a cache hit" design note.
Notes
- Only the linux/amd64 host was exercised here (the sandbox platform). CI additionally covers
linux-arm64,darwin-amd64, anddarwin-arm64; those were not run in this environment. - Confirms the earlier comparator finding: no custom
_cmp.goxis needed — the newer version resolves and builds correctly under the default comparator.
Conclusion: the Formula builds, installs, and passes its consumer test against the newer json-c-0.19-20260627 source, on both fresh-build and cache-hit paths. No changes required.
There was a problem hiding this comment.
@fennoai i see conan support version from 0.14 to 0.19, why don't we support it? https://github.com/conan-io/conan-center-index/raw/refs/heads/master/recipes/json-c/all/conanfile.py
There was a problem hiding this comment.
Why this Formula starts at 0.18 (and what wider support would take)
Good question. Two separate reasons — one is a deliberate scoping choice, the other is real technical work the current single Formula does not yet do. Also a small correction on the range.
1. Conan's tracked range is 0.14–0.18, not 0.19
The recipe you linked is the source tree, but Conan Center only packages the versions in config.yml:
versions:
"0.18": {folder: all}
"0.17": {folder: all}
"0.16": {folder: all}
"0.15": {folder: all}
"0.14": {folder: all}So Conan itself tops out at 0.18. 0.19 exists upstream (json-c released json-c-0.19-20260627) and this Formula already serves it via the 0.18 fromVer — verified in the previous run — but Conan has no 0.19 recipe entry yet.
2. fromVer is a lower bound, so older requests currently get no Formula
Per the formula reference: "For a requested version, LLAR … selects the greatest remaining fromVer not newer than the request. A request older than every fromVer has no Formula."
With only fromVer "json-c-0.18-20240915", anything below 0.18 resolves to nothing. Demonstrated live just now:
$ llar test -v ./json-c/json-c@json-c-0.16-20220414 --os linux --arch amd64
Error: failed to load modules: no formula found for json-c/json-c
So today the Formula intentionally claims only 0.18+ — matching the PR's stated boundary: "Only the exact 0.18 tag is claimed as verified … no unverified version range is asserted."
3. Older versions need different build logic — you can't just lower fromVer
This is the real reason it isn't a one-line change. The Conan recipe branches its build on the version in three places, so 0.14–0.16 do not build the same way 0.18 does:
# generate()
if Version(self.version) >= "0.15":
tc.variables["BUILD_STATIC_LIBS"] = not self.options.shared
tc.variables["DISABLE_STATIC_FPIC"] = not self.options.get_safe("fPIC", True)
tc.cache_variables["BUILD_TESTING"] = False
if Version(self.version) < "0.17":
# relocatable shared libs on macOS + CMake 4 support
tc.cache_variables["CMAKE_POLICY_DEFAULT_CMP0042"] = "NEW"
tc.cache_variables["CMAKE_POLICY_VERSION_MINIMUM"] = "3.5"
else:
tc.cache_variables["BUILD_APPS"] = False
# package_info()
if Version(self.version) >= "0.17" and self.settings.os in ["Linux", "FreeBSD", "Neutrino"]:
self.cpp_info.system_libs.extend(["m"])Translated to what each range needs:
| Requested version | BUILD_STATIC_LIBS/DISABLE_STATIC_FPIC |
BUILD_APPS=OFF |
CMP0042 + CMAKE_POLICY_VERSION_MINIMUM=3.5 |
system_libs |
|---|---|---|---|---|
0.14 |
❌ not set (no static/shared split yet) | ❌ | ✅ needed | none |
0.15–0.16 |
✅ | ❌ | ✅ needed | none |
0.17–0.19 (this Formula) |
✅ | ✅ | ❌ | -lm on Linux/FreeBSD |
Our current onBuild hardcodes the >= 0.17 column: it sets BUILD_APPS=OFF, omits the older CMake-policy shims, and its metadata derives -lm from the installed .pc. If we just lowered fromVer to 0.14:
0.14would likely fail configure (it predates theBUILD_STATIC_LIBSsplit, and withoutCMAKE_POLICY_VERSION_MINIMUM=3.5it can break under CMake ≥ 4).0.15/0.16would miss theCMP0042/policy-minimum shims and could fail on newer CMake / macOS.-lmwould still appear viapkg-config --staticon all of them, which is harmless, but the build differences above are not.
How to actually support the older range (if you want it)
fromVer describes a range with a single build recipe, so a version-conditional recipe needs multiple Formulas, one per boundary the build logic changes at:
json-c/json-c/json-c-0.17-.../→fromVer "json-c-0.17-..."(essentially today's recipe; serves0.17–0.19)json-c/json-c/json-c-0.15-.../→fromVer "json-c-0.15-..."with the< 0.17policy shims and noBUILD_APPS/-lmjson-c/json-c/json-c-0.14-.../→fromVer "json-c-0.14-..."without the static/shared split
LLAR then picks the greatest fromVer not newer than the request, so each older tag lands on the Formula whose flags match. Each new Formula must be built + llar test-verified against its own tags before its range is claimed — which is exactly why this PR scoped to the verified 0.18+ recipe rather than asserting an unverified 0.14+ range.
Recommendation
Keep this PR as the 0.17+ recipe (it's verified and correct). If broader coverage is wanted, add the 0.15 and 0.14 Formulas as a follow-up, each with its version-appropriate flags and its own verification run. I can draft those if you'd like — just say the word and I'll open the work.
There was a problem hiding this comment.
0.17 support added ✅ — pushed c25bd0e
Lowered the Formula's fromVer boundary to json-c-0.17-20230812 and verified the widened range end-to-end. Because 0.17–0.19 share the same Conan build branch (the >= 0.17 column: BUILD_STATIC_LIBS split, BUILD_APPS=OFF, and system_libs=["m"] on Linux/FreeBSD), no onBuild change was needed — only the threshold and the aligned directory name.
Change
- json-c/json-c/json-c-0.18-20240915/Jsonc_llar.gox (renamed)
+ json-c/json-c/json-c-0.17-20230812/Jsonc_llar.gox
- fromVer "json-c-0.18-20240915"
+ fromVer "json-c-0.17-20230812"One file changed, 1 insertion / 1 deletion (plus the directory rename). versions.json is unchanged (deps is empty, so there's no per-version key to add).
Verification (goplus/llar@main, go install, linux/amd64)
| Requested | Resolves to this Formula? | Result |
|---|---|---|
json-c-0.17-20230812 (new lower bound) |
✅ | ✅ exit 0 — fresh build, pkg-config Version: 0.17, consumer printed { "Categories": [ "c", "c++" ] }, flags … -lm -ljson-c |
json-c-0.18-20240915 |
✅ | ✅ exit 0 — still builds |
unpinned → json-c-0.19-20260627 (exact CI command) |
✅ | ✅ exit 0 — still builds |
json-c-0.16-20220414 |
❌ (below 0.17) |
no formula found — correct; 0.16 needs the different < 0.17 build branch and is intentionally still out of range |
So the Formula now serves 0.17 → 0.19 from one recipe, and the 0.16/0.14–0.15 gap remains correctly unclaimed (those need their own version-conditional Formulas, per the earlier analysis).
Notes
- Verified on linux/amd64 only (sandbox platform). CI additionally covers
linux-arm64,darwin-amd64,darwin-arm64, which were not run here. - Commit authored as you (
MeteorsLiu), committed byFennoAI, pushed tofennoai/issue-39-1785407810(1a5354a..c25bd0e).
Extend the json-c/json-c Formula's served range down to json-c-0.17. 0.17 shares the same Conan build branch as 0.18/0.19 (BUILD_STATIC_LIBS split, BUILD_APPS=OFF, and system_libs=[m] on Linux/FreeBSD, all gated on >= 0.17), so no onBuild change is required — only the fromVer threshold and the aligned directory name. Verified with the local goplus/llar CLI (llar test --verbose, linux/amd64): - json-c-0.17-20230812: fresh build + consumer run, pkg-config Version 0.17. - json-c-0.18-20240915: still resolves to this Formula and builds. - unpinned (latest json-c-0.19-20260627): still resolves and builds. - json-c-0.16-20220414: correctly still has no Formula (below 0.17).
Closes #39.
Translates the Conan Center
json-crecipe (snapshotffe30df101afd4dc95aac2f14b25bf345e64d7be, version0.18) into an idiomatic LLAR Formula.Module
json-c/json-c/versions.json— pathjson-c/json-c, no dependency fallbacks (json-c has none).json-c/json-c/json-c-0.18-20240915/Jsonc_llar.gox—fromVer "json-c-0.18-20240915", the exact upstream tag Conan records for0.18.Translation notes
requirements/tool_requires/test_requires, and the upstream0.18source confirms json-c is self-contained, so there is noonRequireanddepsis empty.>= 0.17:BUILD_STATIC_LIBS=ON/BUILD_SHARED_LIBS=OFF(Conan defaultshared=False),DISABLE_STATIC_FPIC=OFF(Conan defaultfPIC=True),BUILD_TESTING=OFF,BUILD_APPS=OFF.CMAKE_INSTALL_LIBDIR=libnormalizes the layout so the installedjson-c.pcand the dependency search paths resolve withoutlib64handling.json-c.pcviapkg-config --cflags --libs --static json-c, so the consumer flags include the private-lmjson-c uses on UNIX — matching the Conanpackage_info(system_libs = ["m"]on Linux/FreeBSD for>= 0.17).onTestmirrors the Conantest_package: it writes a small consumer that includes<json-c/json.h>, links the installedjson-c::json-cCMake target, builds it in a tree separate from theonBuildscratch tree, and runs it. This keeps the test valid on a cache hit (whereonBuildis skipped).fromVerboundary. Only the exact0.18tag is claimed as verified per the issue's instruction; no unverified version range is asserted.Validation
Built and installed with the local
goplus/llarCLI (installed exactly as CI does). All passing (exit 0):llar test -v ./json-c/json-c@json-c-0.18-20240915— fresh build + consumer run.onTest(onBuild skipped) still passes.llar test ./json-c/json-c@json-c-0.18-20240915 --os linux --arch amd64— explicit host matrix.llar test --verbose ./json-c/json-c --os linux --arch amd64— the exact CI command; resolves to the latest tag (json-c-0.19-20260627), whichfromVeralso serves, and builds/tests cleanly.The installed tree contains
lib/libjson-c.a,include/json-c/*.h,lib/pkgconfig/json-c.pc, andlib/cmake/json-c/, and the consumer prints{ "Categories": [ "c", "c++" ] }.