Support Cabal conditions and flags#236
Conversation
31c2893 to
fa34858
Compare
This adds support for flags and conditions in .cabal files which are mapped (if possible) to select statements (configurable attributes) in Bazel.
The former depends on attoparsec which is more complicated to set up (for different GHC versions / stackage snapshots).
The archive tarballs might change.
Some modules do not yet exist in the BCR and we use overrides for them. This makes sure the versions of rules_nixpkgs_core (in BCR) and rules_nixpkgs_posix / rules_nixpkgs_cc (non-BCR) as well as rules_haskell (in BCR) and rules_haskell_nix (non-BCR) are actually consistent.
Expose CPP defines for the different flags and print if they are enabled or disabled in the app. Add a test script to check if building with different flags enabled / disabled has the desired effect.
There was a problem hiding this comment.
Pull request overview
Adds first-class support for Cabal conditional blocks and flags by extending cabalscan to emit configurable attributes and gazelle_cabal to generate corresponding Bazel bool_flag, config_setting, select(...), and selects.config_setting_group(...) constructs, plus an example project to validate the behavior end-to-end.
Changes:
- Extend Cabal scanning output to include flags and conditional/select-shaped attributes (DNF-based condition handling).
- Teach Gazelle rule generation + dependency resolution to carry configurable lists through to generated BUILD attributes.
- Add a new example (
example/package-d) and CI coverage to validate flag-driven selects.
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| gazelle_cabal/lang_test.go | Formatting/import order adjustments in tests. |
| gazelle_cabal/lang.go | Generates bool_flag/config_setting/config_setting_group and adds configurable list handling. |
| gazelle_cabal/dependency_resolution_test.go | Updates tests to use configurable list structures. |
| gazelle_cabal/dependency_resolution.go | Updates dependency resolution to support configurable deps/opts/tools and expression walking. |
| gazelle_cabal/BUILD.bazel | Adds buildtools dependency for manipulating bzl AST. |
| example/package-d/test_flags.sh | Adds a script to validate different flag combinations via bazel run. |
| example/package-d/src/PackageD/NetworkDatabase.hs | Example module gated by combined flags. |
| example/package-d/src/PackageD/Network.hs | Example module gated by network flag. |
| example/package-d/src/PackageD/Internal/NetworkImpl.hs | Example internal module for network support. |
| example/package-d/src/PackageD/Internal/DatabaseImpl.hs | Example internal module for database support. |
| example/package-d/src/PackageD/Internal/Base.hs | Core example module always included. |
| example/package-d/src/PackageD/Experimental.hs | Example module gated by experimental flag. |
| example/package-d/src/PackageD/Database.hs | Example module gated by database flag. |
| example/package-d/src/PackageD/Core.hs | Core library module. |
| example/package-d/package-d.cabal | New example Cabal file defining flags + conditional stanzas. |
| example/package-d/app/Main.hs | Example executable using CPP macros driven by flags. |
| example/package-d/CHANGELOG.md | Example package changelog. |
| example/MODULE.bazel | Adds bazel_skylib and extra packages for the new example. |
| cabalscan/tests/CabalScan/RuleGeneratorSpec.hs | Minor formatting cleanup in existing test. |
| cabalscan/tests/CabalScan/DNFSpec.hs | Adds tests for DNF simplification/negation behavior. |
| cabalscan/src/CabalScan/Rules.hs | Introduces PackageOutput, Flag, and configurable/select representations in JSON output. |
| cabalscan/src/CabalScan/RuleGenerator.hs | Adds conditional traversal and DNF→select/config-group translation. |
| cabalscan/src/CabalScan/DNF.hs | New DNF representation + simplification utilities. |
| cabalscan/src/Cabal/Cabal_9_4.hs | Switches to GenericPackageDescription APIs needed for conditionals/flags. |
| cabalscan/src/Cabal/Cabal_9_2.hs | Same as above for Cabal 9.2. |
| cabalscan/src/Cabal/Cabal_9_0.hs | Same as above for Cabal 9.0. |
| cabalscan/src/Cabal/Cabal_8_10.hs | Same as above for Cabal 8.10 (+ flag/ConfVar compatibility). |
| cabalscan/exe/Main.hs | Outputs aggregated PackageOutput instead of [RuleInfo]. |
| cabalscan/BUILD.bazel | Tweaks test timeout and deps for new DNF tests. |
| MODULE.bazel | Refactors versions/overrides and aligns rules_nixpkgs version usage. |
| .github/workflows/workflow.yaml | Updates CI to rely on .bazelrc.local and adds package-d flag check. |
| .bazelrc | Sets locale env for tests involving Unicode output. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var keptElems []bzl.Expr | ||
| var keptWholeExprs []bzl.Expr | ||
| var keptSelects []*bzl.CallExpr | ||
|
|
There was a problem hiding this comment.
keptSelects is declared but never used, which will fail go test/go vet compilation. Please remove it or use it (e.g., if you intended to treat only select() calls specially).
| // If the whole select is marked keep, save it as a whole expression | ||
| if selectExpr, ok := expr.(*bzl.CallExpr); ok { | ||
| keptSelects = append(keptSelects, selectExpr) | ||
| keptWholeExprs = append(keptWholeExprs, selectExpr) | ||
| return |
There was a problem hiding this comment.
In Merge, any *bzl.CallExpr that is marked # keep is treated as a “select” and preserved wholesale, even if it’s not actually a select(...) call (e.g. glob(...)). This can unintentionally keep unrelated call expressions and change merge behavior. Consider checking e.X is Ident{Name:"select"} (or selects.config_setting_group if needed) before treating it as a keep-worthy select expression, otherwise fall back to the element/whole-expression logic.
| -- Case 5: Complex condition not yet implemented | ||
| | otherwise = unsupported | ||
| where | ||
| unsupported = (traceShow cs $ Select (Map.singleton "not-implemented" trueVal) falseVal, []) | ||
|
|
There was a problem hiding this comment.
For unsupported/complex conditions, dnfToSelect currently emits a Select with a key of "not-implemented" and uses traceShow. This will produce invalid/non-resolvable select conditions in generated BUILD files and also introduces debug logging in normal runs. Prefer failing generation with a clear error (or returning a best-effort default branch only), and remove traceShow/traceStack debugging from production output paths.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Cabal auto-generates a Paths_<package-name>.hs file when referenced in the cabal file. This file does not exist, so we do not generate an entry in `srcs` for it. On the usage side, this file could be generated and added to srcs with a `keep` comment.
There should be no duplicates in attributes after selects are evaluated. Merge and split selects such that Bazel's select invariants are maintained. Remove values from selects that are already part of the non-configurable part of the attribute.
First off, sorry to whoever is looking at this PR it is such a big change. I have been working on this on and off for a few weeks...
This adds support for conditional blocks in .cabal files, generating corresponding
selectstatements in BUILD files.For example, the cabal project in
example/package-d(see https://github.com/avdv/gazelle_cabal/blob/02193153cceeeb5dc900596caf6e43848ff3b5df/example/package-d/package-d.cabal) defines flags and uses them in conditional blocks. Running gazelle generates the followingBUILDfile:I have used this successfully on the xmobar project which makes extensive use of flags and conditions.