Hey everyone,
Quick update on the packaging effort, and a question for the core team on how you see shard discovery evolving.
Following up on the scheme I outlined earlier (installing shards to /usr/share/crystal/shards/<name>-<version> and symlinking them into ./lib), it has solved my offline build requirements completely. Compiler, shards, ameba, flaw, db drivers, and kemal all build cleanly in offline chroots.
Because shards are source-only and RPM packages cannot have file collisions, we install into versioned directories (kemal-1.13.0, kemal-1.12.0) following the compat package pattern (like LLVM). The primary package drops an unversioned symlink (kemal -> kemal-1.13.0), while compat packages omit it so multiple versions can coexist on disk.
This works cleanly for isolated RPM builds, but I’m hitting friction with developer workflows on a live system:
-
Standalone scripts: If someone writes a quick script like app.cr with require "kemal" without a shard.yml or a lib/ folder, CRYSTAL_PATH looks for kemal. That resolves via the primary package’s unversioned symlink, but only for whichever version is primary. Has there ever been any thought on the compiler natively understanding versioned directories in CRYSTAL_PATH?
-
Dangling symlinks on patch updates: When a developer links a local project against system shards (e.g. lib/kemal -> /usr/share/crystal/shards/kemal-1.13.0) and runs a routine dnf upgrade, the package updates to 1.13.1. RPM removes the 1.13.0 directory and replaces it with 1.13.1, which breaks the symlink in ./lib.
Here is what I tried to address this, and why it didn’t work:
- Hybrid SONAME approach: Since we can’t use ELF SONAMEs, I tried emulating them with minor-version symlinks (
kemal-1.13 -> kemal-1.13.1) so patch updates wouldn’t break ./lib. But shard.lock pins exact patch versions (1.13.0), so linking against minor versions bypasses the lockfile’s pinned version.
shard.override.yml: I tried generating an override file so shards install could manage the links. But running shards install mutates shard.lock with local absolute paths and # Overridden comments, which dirties git and breaks CI for anyone else pulling the repo. Plus, if any transitive dependency is missing from the overrides, shards install tries to hit the network, which fails in offline/air-gapped environments.
Where I landed for now is a small helper tool called shards-link. It reads shard.lock (or falls back to shard.yml), matches the version against /usr/share/crystal/shards/, and symlinks the dependencies into ./lib without touching shard.lock at all. If a system update removes an old patch version, running shards-link again relinks to the newest matching installed version.
It works, but it feels like a downstream hack for something that might be better addressed upstream.
Has there been any discussion around shards having an offline or shared cache mechanism (similar to Cargo or Go), or a native way to consume system-installed shards without dirtying shard.lock?
I’d love to hear your thoughts before I cement too many conventions on the Fedora side.