Converting .cabal files to .json

· 3 min read

Someone on #haskell last night was looking for a tool to convert a cabal-install package description to JSON. I hacked something together a while back to do exactly this for the cabal-waf project. While it’s not worthy of a standalone release on hackage it might be useful for someone else out there.

The source is also available for your cloning pleasure on GitHub. Enjoy.

{-# OPTIONS_GHC -fno-warn-deprecations #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE TemplateHaskell #-}

module Main (main) where

import Control.Monad (when)
import qualified Data.ByteString.Lazy as BL
import Data.List
import Data.Aeson
import Data.Aeson.TH
import Distribution.ModuleName (ModuleName)
import Distribution.Package
import Distribution.PackageDescription
import Distribution.PackageDescription.Configuration
import Distribution.PackageDescription.Parse (readPackageDescription)
import Distribution.Verbosity
import Distribution.Version
import Distribution.License
import Distribution.Compiler
import Language.Haskell.Extension
import System.Environment (getArgs)

deriveJSON id ''PackageDescription
deriveJSON id ''PackageIdentifier
deriveJSON id ''PackageName
deriveJSON id ''Version
deriveJSON id ''SourceRepo
deriveJSON id ''RepoKind
deriveJSON id ''RepoType
deriveJSON id ''BuildType
deriveJSON id ''Library
deriveJSON id ''Executable
deriveJSON id ''TestSuite
deriveJSON id ''License
deriveJSON id ''CompilerFlavor
deriveJSON id ''TestSuiteInterface
deriveJSON id ''BuildInfo
deriveJSON id ''ModuleName
deriveJSON id ''Dependency
deriveJSON id ''Language
deriveJSON id ''Extension
deriveJSON id ''KnownExtension
deriveJSON id ''TestType
deriveJSON id ''VersionRange

main :: IO ()
main = do
  args <- getArgs
  when (length args == 0) $ fail "missing .cabal file"

  let (source:_) = args
  gdesc <- readPackageDescription normal source
  let desc = flattenPackageDescription gdesc
      bs = encode . toJSON $ desc
  BL.putStrLn bs

Running it on a small-ish .cabal file…

name:           ghc-prim
version:        0.2.0.0
license:        BSD3
license-file:   LICENSE
maintainer:     libraries@haskell.org
bug-reports: http://hackage.haskell.org/trac/ghc/newticket?component=libraries%20%28other%29
synopsis:       GHC primitives
description:
    GHC primitives.
cabal-version:  >=1.6
build-type: Custom

source-repository head
    type:     git
    location: http://darcs.haskell.org/packages/ghc-prim.git/

flag include-ghc-prim {
    Description: Include GHC.Prim in exposed-modules
    default: False
}

Library {
    build-depends: rts
    exposed-modules:
        GHC.Classes
        GHC.CString
        GHC.Debug
        GHC.Generics
        GHC.Magic
        GHC.PrimopWrappers
        GHC.IntWord64
        GHC.Tuple
        GHC.Types

    if flag(include-ghc-prim) {
        exposed-modules: GHC.Prim
    }

    c-sources:
        cbits/debug.c
        cbits/longlong.c
        cbits/popcnt.c
    extensions: CPP, MagicHash, ForeignFunctionInterface, UnliftedFFITypes,
                UnboxedTuples, EmptyDataDecls, NoImplicitPrelude
    -- We need to set the package name to ghc-prim (without a version number)
    -- as it's magic.
    ghc-options: -package-name ghc-prim
}

Produces output that is easier to consume outside of Haskell. It’s worth noting that conditional statements in the .cabal script are resolved, so the if flag statement above results in GHC.Prim being listed as an exposedModule.

{
  "author": "",
  "bugReports": "http://hackage.haskell.org/trac/ghc/newticket?component=libraries%20%28other%29",
  "buildDepends": [["rts", { "AnyVersion": [] }]],
  "buildType": { "Custom": [] },
  "category": "",
  "copyright": "",
  "customFieldsPD": [],
  "dataDir": "",
  "dataFiles": [],
  "description": "GHC primitives.",
  "executables": [],
  "extraSrcFiles": [],
  "extraTmpFiles": [],
  "homepage": "",
  "library": {
    "exposedModules": [
      ["GHC", "Classes"],
      ["GHC", "CString"],
      ["GHC", "Debug"],
      ["GHC", "Generics"],
      ["GHC", "Magic"],
      ["GHC", "PrimopWrappers"],
      ["GHC", "IntWord64"],
      ["GHC", "Tuple"],
      ["GHC", "Types"],
      ["GHC", "Prim"]
    ],
    "libBuildInfo": {
      "buildTools": [],
      "buildable": true,
      "cSources": ["cbits/debug.c", "cbits/longlong.c", "cbits/popcnt.c"],
      "ccOptions": [],
      "cppOptions": [],
      "customFieldsBI": [],
      "defaultExtensions": [],
      "defaultLanguage": null,
      "extraLibDirs": [],
      "extraLibs": [],
      "frameworks": [],
      "ghcProfOptions": [],
      "ghcSharedOptions": [],
      "hsSourceDirs": ["."],
      "includeDirs": [],
      "includes": [],
      "installIncludes": [],
      "ldOptions": [],
      "oldExtensions": [
        { "EnableExtension": { "CPP": [] } },
        { "EnableExtension": { "MagicHash": [] } },
        { "EnableExtension": { "ForeignFunctionInterface": [] } },
        { "EnableExtension": { "UnliftedFFITypes": [] } },
        { "EnableExtension": { "UnboxedTuples": [] } },
        { "EnableExtension": { "EmptyDataDecls": [] } },
        { "DisableExtension": { "ImplicitPrelude": [] } }
      ],
      "options": [[{ "GHC": [] }, ["-package-name", "ghc-prim"]]],
      "otherExtensions": [],
      "otherLanguages": [],
      "otherModules": [],
      "pkgconfigDepends": [],
      "targetBuildDepends": []
    },
    "libExposed": true
  },
  "license": { "BSD3": [] },
  "licenseFile": "LICENSE",
  "maintainer": "libraries@haskell.org",
  "package": {
    "pkgName": "ghc-prim",
    "pkgVersion": { "versionBranch": [0, 2, 0, 0], "versionTags": [] }
  },
  "pkgUrl": "",
  "sourceRepos": [
    {
      "repoBranch": null,
      "repoKind": { "RepoHead": [] },
      "repoLocation": "http://darcs.haskell.org/packages/ghc-prim.git/",
      "repoModule": null,
      "repoSubdir": null,
      "repoTag": null,
      "repoType": { "Git": [] }
    }
  ],
  "specVersionRaw": {
    "Right": {
      "UnionVersionRanges": [
        { "ThisVersion": { "versionBranch": [1, 6], "versionTags": [] } },
        { "LaterVersion": { "versionBranch": [1, 6], "versionTags": [] } }
      ]
    }
  },
  "stability": "",
  "synopsis": "GHC primitives",
  "testSuites": [],
  "testedWith": []
}

Comments

2 comments · 1 reply · 2 participants

  1. Avatar for Benjamin Edwards
    Benjamin Edwards Permalink to comment 622649756

    Hey, did you ever build this into cabal-waf? I am the guy that started the thread on reddit about haskell builds. I am right in the middle of hacking on your cabal-waf project as it stands on github to do automatic dependency resolution and fetching from hackage. Then I saw this, and I was wondering if you had moved the project on and not pushed to github. If you can't open source it because it's work related, I'm down, I just don't want to waste a weekend doing something that has already been done.

    1. Avatar for Nathan Howell
      Nathan Howell Permalink to comment 622817498

      No, I never got around to it. We went a different direction (shake-install) instead since our codebase is nearly 100% Haskell now. If you do get it integrated please send a pull request!