diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+  * Redistributions of source code must retain the above
+    copyright notice, this list of conditions and the
+    following disclaimer.
+
+  * Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the
+    following disclaimer in the documentation and/or other
+    materials provided with the distribution.
+
+  * The names of its contributors may not be used to endorse
+    or promote products derived from this software without
+    specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,8 @@
+#!/usr/bin/env runhaskell
+
+> module Main (main) where
+>
+> import Distribution.Simple
+>
+> main :: IO ()
+> main = defaultMain
diff --git a/distribution-nixpkgs.cabal b/distribution-nixpkgs.cabal
new file mode 100644
--- /dev/null
+++ b/distribution-nixpkgs.cabal
@@ -0,0 +1,70 @@
+-- This file has been generated from package.yaml by hpack version 0.14.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           distribution-nixpkgs
+version:        1
+synopsis:       Types and functions to manipulate the Nixpkgs distribution.
+description:    Types and functions to represent, query, and manipulate the Nixpkgs distribution.
+category:       Distribution
+homepage:       https://github.com/peti/distribution-nixpkgs#readme
+bug-reports:    https://github.com/peti/distribution-nixpkgs/issues
+maintainer:     Peter Simons <simons@cryp.to>
+license:        BSD3
+license-file:   LICENSE
+tested-with:    GHC > 7.10 && < 8.1
+build-type:     Simple
+cabal-version:  >= 1.10
+
+source-repository head
+  type: git
+  location: https://github.com/peti/distribution-nixpkgs
+
+library
+  hs-source-dirs:
+      src
+  ghc-options: -Wall
+  build-depends:
+      aeson
+    , base > 4.2 && < 5
+    , bytestring
+    , Cabal > 1.24
+    , containers
+    , deepseq >= 1.4
+    , language-nix > 2
+    , lens
+    , pretty >= 1.1.2
+    , process
+    , split
+  exposed-modules:
+      Distribution.Nixpkgs.License
+      Distribution.Nixpkgs.Meta
+      Distribution.Nixpkgs.PackageMap
+      Language.Nix.PrettyPrinting
+  other-modules:
+      Internal.OrphanInstances
+  default-language: Haskell2010
+
+test-suite spec
+  type: exitcode-stdio-1.0
+  main-is: Main.hs
+  hs-source-dirs:
+      test
+  ghc-options: -Wall
+  build-depends:
+      aeson
+    , base > 4.2 && < 5
+    , bytestring
+    , Cabal > 1.24
+    , containers
+    , deepseq >= 1.4
+    , language-nix > 2
+    , lens
+    , pretty >= 1.1.2
+    , process
+    , split
+    , distribution-nixpkgs
+    , doctest
+    , hspec
+    , QuickCheck
+  default-language: Haskell2010
diff --git a/src/Distribution/Nixpkgs/License.hs b/src/Distribution/Nixpkgs/License.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Nixpkgs/License.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+{- |
+   Known licenses in Nix expressions are represented using the
+   attributes defined in @pkgs\/lib\/licenses.nix@, and unknown licenses
+   are represented as a literal string.
+ -}
+
+module Distribution.Nixpkgs.License ( License(..) ) where
+
+import Control.DeepSeq
+import Data.Maybe
+import GHC.Generics ( Generic )
+import Language.Nix.PrettyPrinting
+
+-- | The representation for licenses used in Nix derivations. Known
+-- licenses are Nix expressions -- such as @stdenv.lib.licenses.bsd3@
+-- --, so their exact \"name\" is not generally known, because the path
+-- to @stdenv@ depends on the context defined in the expression. In
+-- Cabal expressions, for example, the BSD3 license would have to be
+-- referred to as @self.stdenv.lib.licenses.bsd3@. Other expressions,
+-- however, use different paths to the @licenses@ record. Because of tat
+-- situation, the library cannot provide an abstract data type that
+-- encompasses all known licenses. Instead, the @License@ type just
+-- distinguishes references to known and unknown licenses. The
+-- difference between the two is in the way they are pretty-printed:
+--
+-- > > putStrLn (display (Known "stdenv.lib.license.gpl2"))
+-- > stdenv.lib.license.gpl2
+-- >
+-- > > putStrLn (display (Unknown (Just "GPL")))
+-- > "GPL"
+-- >
+-- > > putStrLn (display (Unknown Nothing))
+-- > "unknown"
+--
+-- Note that the "Text" instance definition provides pretty-printing,
+-- but no parsing as of now!
+
+data License = Known String
+             | Unknown (Maybe String)
+  deriving (Show, Eq, Ord, Generic)
+
+instance Pretty License where
+  pPrint (Known x)   = text x
+  pPrint (Unknown x) = string (fromMaybe "unknown" x)
+
+instance NFData License
diff --git a/src/Distribution/Nixpkgs/Meta.hs b/src/Distribution/Nixpkgs/Meta.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Nixpkgs/Meta.hs
@@ -0,0 +1,100 @@
+{-# LANGUAGE DeriveGeneric   #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+{- |
+   A representation of the @meta@ section used in Nix expressions. A
+   detailed description can be found in section 4, \"Meta-attributes\",
+   of the Nixpkgs manual at <http://nixos.org/nixpkgs/docs.html>.
+ -}
+
+module Distribution.Nixpkgs.Meta
+  ( Meta, nullMeta
+  , homepage, description, license, platforms, hydraPlatforms, maintainers, broken
+  , allKnownPlatforms
+  ) where
+
+import Control.DeepSeq
+import Control.Lens
+import Data.Set ( Set )
+import qualified Data.Set as Set
+import Distribution.Nixpkgs.License
+import Distribution.System
+import GHC.Generics ( Generic )
+import Internal.OrphanInstances ( )
+import Language.Nix.Identifier
+import Language.Nix.PrettyPrinting
+
+-- | A representation of the @meta@ section used in Nix expressions.
+--
+-- >>> :set -XOverloadedStrings
+-- >>> :{
+--   print (pPrint (Meta "http://example.org" "an example package" (Unknown Nothing)
+--                  (Set.singleton (Platform X86_64 Linux))
+--                  Set.empty
+--                  (Set.fromList ["joe","jane"])
+--                  True))
+-- :}
+-- homepage = "http://example.org";
+-- description = "an example package";
+-- license = "unknown";
+-- platforms = [ "x86_64-linux" ];
+-- hydraPlatforms = stdenv.lib.platforms.none;
+-- maintainers = with stdenv.lib.maintainers; [ jane joe ];
+-- broken = true;
+
+data Meta = Meta
+  { _homepage       :: String           -- ^ URL of the package homepage
+  , _description    :: String           -- ^ short description of the package
+  , _license        :: License          -- ^ licensing terms
+  , _platforms      :: Set Platform     -- ^ We re-use the Cabal type for convenience, but render it to conform to @pkgs\/lib\/platforms.nix@.
+  , _hydraPlatforms :: Set Platform     -- ^ list of platforms built by Hydra (render to conform to @pkgs\/lib\/platforms.nix@)
+  , _maintainers    :: Set Identifier   -- ^ list of maintainers from @pkgs\/lib\/maintainers.nix@
+  , _broken         :: Bool             -- ^ set to @true@ if the build is known to fail
+  }
+  deriving (Show, Eq, Ord, Generic)
+
+makeLenses ''Meta
+
+instance NFData Meta
+
+instance Pretty Meta where
+  pPrint Meta {..} = vcat
+    [ onlyIf (not (null _homepage)) $ attr "homepage" $ string _homepage
+    , onlyIf (not (null _description)) $ attr "description" $ string _description
+    , attr "license" $ pPrint _license
+    , onlyIf (_platforms /= allKnownPlatforms) $ renderPlatforms "platforms" _platforms
+    , onlyIf (_hydraPlatforms /= _platforms) $ renderPlatforms "hydraPlatforms" _hydraPlatforms
+    , setattr "maintainers" (text "with stdenv.lib.maintainers;") (Set.map (view ident) _maintainers)
+    , boolattr "broken" _broken _broken
+    ]
+
+renderPlatforms :: String -> Set Platform -> Doc
+renderPlatforms field ps
+  | Set.null ps = sep [ text field <+> equals <+> text "stdenv.lib.platforms.none" <> semi ]
+  | otherwise   = sep [ text field <+> equals <+> lbrack
+                      , nest 2 $ fsep $ map text (toAscList (Set.map fromCabalPlatform ps))
+                      , rbrack <> semi
+                      ]
+
+nullMeta :: Meta
+nullMeta = Meta
+  { _homepage = error "undefined Meta.homepage"
+  , _description = error "undefined Meta.description"
+  , _license = error "undefined Meta.license"
+  , _platforms = error "undefined Meta.platforms"
+  , _hydraPlatforms = error "undefined Meta.hydraPlatforms"
+  , _maintainers = error "undefined Meta.maintainers"
+  , _broken = error "undefined Meta.broken"
+  }
+
+allKnownPlatforms :: Set Platform
+allKnownPlatforms = Set.fromList [ Platform I386 Linux, Platform X86_64 Linux
+                                 , Platform X86_64 OSX
+                                 ]
+
+fromCabalPlatform :: Platform -> String
+fromCabalPlatform (Platform I386 Linux)   = "\"i686-linux\""
+fromCabalPlatform (Platform X86_64 Linux) = "\"x86_64-linux\""
+fromCabalPlatform (Platform X86_64 OSX)   = "\"x86_64-darwin\""
+fromCabalPlatform p                       = error ("fromCabalPlatform: invalid Nix platform" ++ show p)
diff --git a/src/Distribution/Nixpkgs/PackageMap.hs b/src/Distribution/Nixpkgs/PackageMap.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Nixpkgs/PackageMap.hs
@@ -0,0 +1,59 @@
+module Distribution.Nixpkgs.PackageMap
+  ( PackageMap, readNixpkgPackageMap
+  , resolve
+  ) where
+
+import qualified Data.Aeson as JSON
+import qualified Data.ByteString.Lazy as LBS
+import Data.Function
+import Data.List as List
+import Data.List.Split
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict ( Map )
+import Data.Maybe
+import Data.Set ( Set )
+import qualified Data.Set as Set
+import Distribution.Text
+import Language.Nix
+import System.Process
+import Control.Lens
+
+type PackageMap = Map Identifier (Set Path)
+
+readNixpkgPackageMap :: FilePath -> Maybe Path -> IO PackageMap
+readNixpkgPackageMap nixpkgs attrpath = fmap identifierSet2PackageMap (readNixpkgSet nixpkgs attrpath)
+
+readNixpkgSet :: FilePath -> Maybe Path -> IO (Set String)
+readNixpkgSet nixpkgs attrpath = do
+  let extraArgs = maybe [] (\p -> ["-A", display p]) attrpath
+  (_, Just h, _, _) <- createProcess (proc "nix-env" (["-qaP", "--json", "-f", nixpkgs] ++ extraArgs))
+                       { std_out = CreatePipe, env = Nothing }  -- TODO: ensure that overrides don't screw up our results
+  buf <- LBS.hGetContents h
+  let pkgmap :: Either String (Map String JSON.Object)
+      pkgmap = JSON.eitherDecode buf
+  either fail (return . Map.keysSet) pkgmap
+
+identifierSet2PackageMap :: Set String -> PackageMap
+identifierSet2PackageMap pkgset = foldr (uncurry insertIdentifier) Map.empty pkglist
+  where
+    pkglist :: [(Identifier, Path)]
+    pkglist = mapMaybe parsePackage (Set.toList pkgset)
+
+    insertIdentifier :: Identifier -> Path -> PackageMap -> PackageMap
+    insertIdentifier i = Map.insertWith Set.union i . Set.singleton
+
+parsePackage :: String -> Maybe (Identifier, Path)
+parsePackage x | null x                 = error "Distribution.Nixpkgs.PackageMap.parsepackage: empty string is no valid identifier"
+               | xs <- splitOn "." x    = if needsQuoting (head xs)
+                                             then Nothing
+                                             else Just (ident # last xs, path # map (review ident) xs)
+
+resolve :: PackageMap -> Identifier -> Maybe Binding
+resolve nixpkgs i = case Map.lookup i nixpkgs of
+                      Nothing -> Nothing
+                      Just ps -> let p = chooseShortestPath (Set.toList ps)
+                                 in Just $ binding # (i,p)
+
+chooseShortestPath :: [Path] -> Path
+chooseShortestPath [] = error "chooseShortestPath: called with empty list argument"
+chooseShortestPath ps = minimumBy (on compare (view (path . to length))) ps
diff --git a/src/Internal/OrphanInstances.hs b/src/Internal/OrphanInstances.hs
new file mode 100644
--- /dev/null
+++ b/src/Internal/OrphanInstances.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module Internal.OrphanInstances ( ) where
+
+import Control.DeepSeq
+import Distribution.System
+
+instance NFData Arch
+instance NFData OS
+instance NFData Platform
diff --git a/src/Language/Nix/PrettyPrinting.hs b/src/Language/Nix/PrettyPrinting.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Nix/PrettyPrinting.hs
@@ -0,0 +1,62 @@
+-- | Internal pretty-printing helpers for Nix expressions.
+
+module Language.Nix.PrettyPrinting
+  ( onlyIf
+  , setattr, toAscList
+  , listattr
+  , boolattr
+  , attr
+  , string
+  , funargs
+  -- * Re-exports from other modules
+  , module Text.PrettyPrint.HughesPJClass
+  , Text, disp
+  )
+  where
+
+import Data.Char
+import Data.Function
+import Data.List
+import Data.Set ( Set )
+import qualified Data.Set as Set
+import Distribution.Text ( Text, disp )
+import Text.PrettyPrint.HughesPJClass
+
+attr :: String -> Doc -> Doc
+attr n v = text n <+> equals <+> v <> semi
+
+onlyIf :: Bool -> Doc -> Doc
+onlyIf b d = if b then d else empty
+
+boolattr :: String -> Bool -> Bool -> Doc
+boolattr n p v = if p then attr n (bool v) else empty
+
+listattr :: String -> Doc -> [String] -> Doc
+listattr n prefix vs = onlyIf (not (null vs)) $
+                sep [ text n <+> equals <+> prefix <+> lbrack,
+                      nest 2 $ fsep $ map text vs,
+                      rbrack <> semi
+                    ]
+
+setattr :: String -> Doc -> Set String -> Doc
+setattr name prefix set = listattr name prefix (toAscList set)
+
+toAscList :: Set String -> [String]
+toAscList = sortBy (compare `on` map toLower) . Set.toList
+
+bool :: Bool -> Doc
+bool True  = text "true"
+bool False = text "false"
+
+string :: String -> Doc
+string = doubleQuotes . text
+
+prepunctuate :: Doc -> [Doc] -> [Doc]
+prepunctuate _ []     = []
+prepunctuate p (d:ds) = d : map (p <>) ds
+
+funargs :: [Doc] -> Doc
+funargs xs = sep [
+               lbrace <+> fcat (prepunctuate (comma <> text " ") $ map (nest 2) xs),
+               rbrace <> colon
+             ]
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,32 @@
+module Main ( main ) where
+
+import Control.DeepSeq
+import Control.Exception
+import Control.Lens
+import Data.Maybe
+import Distribution.Nixpkgs.License
+import Distribution.Nixpkgs.Meta
+import System.Environment
+import Test.DocTest
+import Test.Hspec
+
+main :: IO ()
+main = do
+  distDir <- fromMaybe "dist" `fmap` lookupEnv "HASKELL_DIST_DIR"
+  let cabalMacrosHeader = distDir ++ "/build/autogen/cabal_macros.h"
+  doctest [ "-isrc", "-optP-include", "-optP"++cabalMacrosHeader, "src" ]
+
+  hspec $
+    describe "DeepSeq instances work properly for" $ do
+      it "License" $ mapM_ hitsBottom [Known undefined, Unknown (Just undefined)]
+      it "Meta" $ mapM_ hitsBottom
+                    [ nullMeta & homepage .~ undefined
+                    , nullMeta & description .~ undefined
+                    , nullMeta & license .~ undefined
+                    , nullMeta & platforms .~ undefined
+                    , nullMeta & maintainers .~ undefined
+                    , nullMeta & broken .~ undefined
+                    ]
+
+hitsBottom :: NFData a => a -> Expectation
+hitsBottom x = evaluate (rnf x) `shouldThrow` anyErrorCall
