Crystal packaging in Fedora

I, for one, am a Fedora contributor. I’ve loved Crystal since it’s beginnings and would love to see it as a first class citizen in Fedora.

Up to now, I’ve been using Zawertun’s package as my main crystal driver in Fedora: Making sure you're not a bot!. Kudos to him for maintaining it and doing so in such a great way for so many years.

That said, I’d love to take it into the main repos and have packaging guidelines for it. Every major language has them.

I am a Fedora packager and I would like to know if there is interest in this and, if so, who could help with the packaging process.

This is important. I’ve seen a mini-revival as of late of Crystal within the community. Apps, frameworks and libraries are starting to move again.

We could use that momentum and synergize with Crystal being fully supported in Fedora.

It could bring in more users and make things nicer in general for Crystal.

I’d like to read your opinion.

Happy to help with upstream support. I don’t know much about Fedora specifics, though.

Don’t worry, I’ve got that covered. I mean, I’ve never introduced a “new” language but I am sure it’s documented somewhere. That’s the good thing about Fedora.

Hi everyone,

I’m moving the proposal to bring the Crystal Programming Language into the official Fedora and EPEL repositories into its next phase. The initial infrastructure is now functional, and I’m looking for peer review, QA, and collaborators to help refine the implementation before we finalize the FESCo Change Proposal for Fedora 46.

:hammer_and_wrench: The Stack So Far

We’ve built a robust foundation to ensure Crystal packaging is as idiomatic and automated as possible:

  1. cr2rpm: A dedicated Python-based metadata engine and spec generator. It parses shard.yml to drive the build process, similar to rust2rpm or go2rpm. It includes a metadata subcommand for programmatic querying of shard properties.

  2. Standardized RPM Macros: A complete suite (%crystal_setup, %crystal_build, %crystal_install, etc.) that handles:

    • Offline Builds: Automatic detection and handling of vendor tarballs (Source1).
    • Architecture Support: Standardized ExclusiveArch for x86_64 and aarch64.
    • FHS Compliance: Proper placement of binaries and the Crystal standard library.
  3. CI & Validation: The tooling is backed by a pytest suite and passes rpmlint with zero errors/warnings on generated specs.

:microscope: Request for Review & QA

I am looking for “Vibe-Engineers” and packagers to tear this apart and help us find the edge cases:

  • Logic & Architecture: Review the macros.crystal implementation. Is there a more efficient way to handle the bootstrap or the vendor caching?

  • Tooling QA: Run cr2rpm against complex shards (especially those with heavy C-bindings or non-standard directory structures) and report where the spec generation fails or produces non-idiomatic results.

  • EPEL Strategy: Review the epel.rst plan. We need to ensure long-term maintainability given RHEL’s lifecycle.

  • Opinions Welcome: Should we change the naming convention for libraries? Is the %crystal_setup parameterization flexible enough?

:package: Repository & Docs

The entire planning, guidelines, and tooling source is hosted here:
:backhand_index_pointing_right: renich/crystal-in-fedora: Project to take Crystal to Fedora Linux and make it a first-class citizen there. - Codeberg.org

If you’ve ever wanted a first-class Crystal experience on a major enterprise-grade distro, now is the time to weigh in on the foundation.

Looking forward to your PRs, issues, and brutal honesty.

Cheers,

Rénich Bon Ćirić
Fedora Packager

I’ve posted this at Fedora’s Discourse: Bringing Crystal to Fedora LInux - Fedora Discussion

I took a look at the proposal. It’s a lot of text to ingest, with lots of overlap between the documents. But I suppose that’s the amount of bureaucracy involved with package management for a big distro :person_shrugging:

I started a discussion about dependencies, but apart from that I couldn’t find much where my immediate input could be helpful. Please let me know if there is anything I can do.

Fair enough. I will try to condense it. The thing is, I tried to divide it into sections. One thing is submitting a package, another is proposing a policy to package applications and libraries (shards) of that language’s ecosystem and, yet, another is to submit those things to EPEL.

Well kinda. That is, definitely, one way of seeing it and it’s correct. The other perspective is to view it as what it was intended: a procedure engineered by engineers (no pun intended) to try to ensure maximum quality, security and overall maintainability.

I understand. I am grateful and will try assimilate the feedback you’ve provided. Thank you. :slight_smile:

Hello Crystal community,

I want to share an update regarding the ongoing initiative to bring the Crystal programming language natively into official Fedora Linux and EPEL (Extra Packages for Enterprise Linux) repositories.

Over the past few months, we have developed and verified the packaging architecture (Gemini and me), RPM macro toolchain, automated spec generator (cr2rpm), and a multi-stage bootstrap pipeline that complies with Fedora Packaging Guidelines.

Here is an overview of the technical architecture, bootstrap model, current status, and roadmap.


1. Compliance Requirements and Architecture Overview

Packaging a self-hosted language in Fedora requires addressing two primary constraints:

  1. No Pre-compiled Binaries Policy: All binaries in Fedora must be built from source inside isolated build environments (Koji/Mock) without downloading opaque binary seeds during the build.

  2. Hermetic Builds (No Network Access): Build roots have network access disabled. Package managers cannot fetch dependencies dynamically during rpmbuild.

To meet these requirements, we established the Shard-as-RPM model:

  • Offline Dependency Resolution: Standardized CRYSTAL_PATH="lib:/usr/share/crystal/shards:/usr/share/crystal/src". When dependent shards are installed as RPMs, their source trees are located under /usr/share/crystal/shards/<name>, enabling compiler discovery without vendoring or network access.
  • Automatic RPM Dependency Triggers: Custom RPM file attributes (crystalshard.attr) and generator scripts (crystalshard.prov, crystalshard.req) parse shard.yml to automatically emit virtual provides (crystal-shard(name) = version) and build requirements (crystal-shard(dep)).
  • Hardening and DWARF Debuginfo: Build macros (%crystal_flags) pass %build_ldflags (-Wl,-z,relro -Wl,--as-needed) and preserve DWARF debug symbols for standard Fedora -debuginfo subpackage generation.

2. Multi-Stage Bootstrap Pipeline

To achieve self-hosting compliance without external binaries, the toolchain is structured into three discrete stages:

[ Stage 0: Bootstrap Seed ]
   │  C source bundle (crystal build --cross-compile)
   │  Compiled with system GCC/Clang + LLVM, GC, PCRE2, libffi
   ▼
[ Stage 1: Core Toolchain ]
   │  • crystal (Self-hosted compiler built by Stage 0)
   │  • crystal-rpm-macros (RPM build macros & fileattr generators)
   │  • shards (Dependency manager built from source)
   ▼
[ Stage 2: Ecosystem Verification & Shards ]
   │  • Libraries: kemal, radix, exception_page
   │  • CLI Tools & Linters: ameba (1.7.0), flaw (0.1.0)
   │  • Applications: Native standalone ELF executables

Stage 0: Source-Based Bootstrap

The bootstrap seed is built using cross-compiled C source representation (crystal build --cross-compile) linked against system libraries (llvm-devel, gc-devel, pcre2-devel, libffi-devel) using system gcc or clang. This produces a functional Stage 0 compiler without violating Fedora build policies.

Stage 1: Self-Hosted Compiler & Packaging Tools

Using the Stage 0 compiler in a transient build environment:

  1. crystal is rebuilt from its Crystal source tree into the final self-hosted compiler.

  2. crystal-rpm-macros installs /usr/lib/rpm/macros.d/macros.crystal and dependency extraction helpers.

  3. shards is compiled from source and tested against the new compiler.

Stage 2: Ecosystem & Applications

With Stage 1 packages published into the build root, higher-level shards and applications are built and tested offline.


3. Automated Spec Generation: cr2rpm

To simplify packaging shards and applications for packagers, we created cr2rpm, an automated spec generator written in Python:

  • Metadata Extraction: Reads shard.yml and shard.lock to determine package type (library vs. executable), licenses (mapping to standard SPDX identifiers), and target dependencies.
  • Template Generation: Produces Fedora-compliant .spec files utilizing %crystal_setup, %crystal_build, %crystal_install, and %crystal_check.
  • Dependency Tree Analysis: Generates recursive dependency trees and mock chain-build ordering scripts.

4. Current Status and Verification

We have validated the entire multi-stage pipeline locally using mock --chain against clean Fedora build environments:

  • Stage 0 (crystal-1.21.0): Compiled from source, self-hosted, and passed the full spec/std_spec.cr standard library test suite.
  • Stage 1 (crystal-rpm-macros, shards-0.20.0): Built and passed all unit and integration test suites.
  • Stage 2 Applications & Tools:
    • ameba (1.7.0): Built and verified in chroot; all 2,139 spec examples passed.
    • flaw (0.1.0): Built and verified in chroot; security rules and tests passed.
    • hello-crystal (0.1.0): Verified compilation, packaging, and chroot execution.

5. Next Steps and Collaboration

  1. Fedora Change Proposal: Finalizing the system-wide FESCo change proposal targeting Fedora 44+ (subject to change).

  2. Koji Staging: Establishing the Koji side-tag workflow for the official bootstrap build.

  3. Fedora Crystal SIG: Forming the Fedora Crystal Special Interest Group (SIG) to maintain the compiler and core shard packages collaboratively.

We welcome feedback, suggestions, and contributors interested in packaging or testing shards on Fedora/EPEL.


Why Packaged Shards and Offline Builds Matter

The standard Crystal workflow relies on shards fetching Git repositories at build time. For Linux distributions, enterprise infrastructure, and production operations, the Shard-as-RPM model provides several foundational advantages:

  1. Air-Gapped and Secure Enclaves
    In regulated environments (defense, finance, healthcare, government, and critical infrastructure), build environments and production nodes operate with zero internet connectivity. By installing shard source trees into %{_datadir}/crystal/shards/ and leveraging CRYSTAL_PATH=“lib:/usr/share/crystal/shards:/usr/share/crystal/src”, Crystal applications can compile and run purely from internal, offline package mirrors without touching external Git hosts.

  2. Supply Chain Security and Immutability
    Dynamic Git downloads leave builds vulnerable to branch deletions, force-pushes, repository takeovers, and typosquatting. Packaged shards anchor dependencies to cryptographically signed RPM checksums and verified SPDX licenses.

  3. Centralized CVE Remediation and Auditing
    When a vulnerability is identified in a shared shard (such as an HTTP router or cryptographic library), system administrators and security tools (DNF, RPM, OpenSCAP) can track, audit, and patch the library system-wide at the RPM layer, eliminating the need to locate and manually patch vendored lib/ directories across multiple independent application trees.

  4. Hermetic, Reproducible Builds
    Buildroots in Koji and Mock require deterministic, hermetic environments. Packaging shards ensures that every single build input is accounted for and reproducible without network drift.

It’s not clear to me if this means that on every realease the compiler will use a newly generated stage 0 compiler from the previous (or current) release. Or if this is maintaining the whole history since ruby based compiler.

If the former, how are you generating the C source representation? I glanced at the repo but couldn’t find it.

Thanks for the efforts in bringing it to Fedora in a native fashion.

Hey Bryan,

Stage 0 is used only for the initial bootstrap into Fedora (cold start). Once Crystal is in the repository, all future releases self-host natively from the previous Fedora package (BuildRequires: crystal).

We do not maintain history back to Ruby. When needed for cold starts or new architectures, the C source object code representation is generated via standard cross-compilation (make crystal target="x86_64-unknown-linux-gnu" or crystal build --cross-compile).

You can check out our bootstrap strategy in plan.rst.

And, btw, you’re welcome. It’s my pleasure to assist.

And, by the way, I’ve already submitted the proposal to see if we can make the Fedora 45 release: Changes/CrystalLanguage - Fedora Project Wiki

Fingers crossed because the deadline is close.

Thanks for all the work to improve native Crystal package support :heart_hands:

I’m a bit surprised about the shard-as-rpm mechanism to achieve offline dependency resolution. Is that a common strategy in Fedora?
Does it allow installing different versions of the same shard?

Also, I’m wondering wouldn’t it be easier to vendor the dependencies into the source repository?
Upstream repos typically don’t do that, but it should be fairly easy to fork the source repo into a offline-dependency clone with a shards install && git add lib commit.

Thanks for all the work to improve native Crystal package support :heart_hands:

No need. Thank you, guys, for all the great work and the awesome programming language. :slight_smile:

I’m a bit surprised about the shard-as-rpm mechanism to achieve offline dependency resolution. Is that a common strategy in Fedora?

Oh yes. It’s the standard model for modern languages. Just try listing Go and Rust packages. You will find that we package and maintain packages/crates used by software distributed there. Not just that, but libraries useful for systems development in fact.

The benefits are enormous. It allows for offline/air-gapped development. Many organizations require this. I, myself, have worked for UAE and Mexican governments. Both have strict requirements for such practices.

Vetting the code is also important. Fedora maintainers will look at the code of the dependencies used. With the zeitgeist as it is, this is becoming ever more important. We need eyes on the code.

Does it allow installing different versions of the same shard?

No, not under the same name/path at the same time.

By default, an RPM package installs to a fixed path (/usr/share/crystal/shards/<name>). RPM prevents two packages from owning the same filesystem path to avoid file collisions.

How this works in practice and how Fedora handles it:

  1. The Distribution Model (Single Coordinated Baseline):

    • Unlike npm or shards which allow nested, duplicate copies of libraries per-project on developer machines, Linux distributions (Fedora, Debian, Arch) operate on an ecosystem baseline.
    • A single, latest stable release of each shard (e.g., kemal 1.13.0) is packaged. All applications in that Fedora release compile against that coordinated version. When a library updates, dependent applications are rebuilt and tested against it (via Koji mass rebuilds and gating CI).
  2. Parallel Versioning (Compatibility Packages):

    • When an upstream project undergoes an incompatible major API rewrite (e.g., v1.x vs v2.x) and older applications still require v1.x, Fedora creates a compat package:
      • crystal-shard-foo (v2.x, installed to /usr/share/crystal/shards/foo)
      • crystal-shard-foo1 (v1.x, installed to /usr/share/crystal/shards/foo1)
    • Because Crystal’s require "foo" does not support inline version specifiers in code, the older application’s spec file simply adjusts its CRYSTAL_PATH during %build to point to the foo1 directory.

Also, I’m wondering wouldn’t it be easier to vendor the dependencies into the source repository?
Upstream repos typically don’t do that, but it should be fairly easy to fork the source repo into a offline-dependency clone with a shards install && git add lib commit.

Fedora has a strict No Bundled Libraries Policy for several key reasons:

  • Centralized Security/CVE Patching: If a vulnerability is found in a core shard (e.g., an HTTP parser or crypto wrapper), updating that single shard package fixes it centrally across all downstream builds. With vendoring, security teams would have to track, audit, and re-release every single vendored application separately.
  • License & Supply Chain Auditing: Each shard RPM has its own verified SPDX license tag, cryptographic package checksums, and clean upstream provenance.
  • Zero Network Builds: Fedora’s official build systems (Koji and Mock) run completely disconnected from the internet. Pre-installing dependency RPMs into the buildroot allows CRYSTAL_PATH to resolve everything hermetically.

You know what, scratch that! I dug a bit deeper into Fedora’s Rust (rust2rpm) packaging precedents, and we can actually support multi-versioned shards cleanly:

  1. Versioned Install Paths: If shard RPMs install into versioned directories (e.g. /usr/share/crystal/shards/-), multiple versions of the same shard can coexist on the system with zero RPM file conflicts.

  2. Offline Symlinking in %prep: During the build, %crystal_setup (or cr2rpm) can read shard.lock and symlink the exact requested versions into ./lib/:

    lib/kemal → /usr/share/crystal/shards/kemal-1.13.0
    lib/radix → /usr/share/crystal/shards/radix-0.4.1

  3. Transparent Resolution: Crystal’s standard require “kemal” finds ./lib/kemal immediately, allowing different applications to build against different locked shard versions completely
    offline.

So yes; by appending the version to the install directory, parallel multi-versioned shard installations are 100% doable.

Great news! :tada: Just to note: having a single-version shard installation is unfeasible, as many apps will likely require different versions of the same shard, so having them centrally installed would make that impossible.

The discussion is ongoing:

If anyone wants to be at the meeting, it will happen in Matrix. The fedora calendar is here:

That meeting is for the Packaging Committee and Fabio Valenti proposed I should present this change there tomorrow.

The link to the Matrix room: #meeting-1:fedoraproject.org. More information on how to join one of these is at: Packaging Committee - Fedora Project Wiki. I use GNOME’s Fractal but there is a web client. You will have create an account (if you don’t already have one).

And, finally, the general packaging guidelines:

I’ve created the packaging guidelines ticket taking into account Fabio Vallenti’s and Maxwell G’s comments.

I’ve, also, prepared the AsciiDoc version of those at the codeberg repo.

Update: We have officially formed the Fedora Crystal SIG!

We now have a dedicated space to coordinate packaging, tooling, and shard reviews:

Anyone interested in packaging, testing, or following Crystal on Fedora is welcome to drop in!

Being able to install different versions of the same shard sounds great. The symlinking should really work well.

One caveat with the proposed scheme however is that shard names are not centrally managed. That’s different from the Rust cargo ecosystem where library names are guaranteed to be unique.

So it’s totally possible that two different shards share the same name.
For an individual project it’s acceptable that it cannot have two dependencies with the same name.
For a system-wide shared namespace, naming conflicts cannot be that easily excluded.

I suppose that shouldn’t be a huge problem for the Shard-as-RPM model because in case of conflict, the name in the system location could be adjusted to be unique.