Skip to content

Commit

Permalink
Prepare to publish
Browse files Browse the repository at this point in the history
  • Loading branch information
Jordan Mackie committed Nov 29, 2020
1 parent 86faddc commit 4f2213a
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 169 deletions.
1 change: 1 addition & 0 deletions .ghcid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--command="cabal new-repl test"
37 changes: 37 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI
on:
pull_request:
push:
branches:
- master

jobs:
build-test-check:
matrix:
os:
- ubuntu-latest
# TODO: check more OS?
compiler:
- "ghc802"
- "ghc822"
- "ghc844"
- "ghc865"
- "ghc883"
- "ghc8101"

runs-on: "${{ matrix.os }}"
steps:
- name: Checkout repository
uses: actions/[email protected]

- name: Install Nix
uses: cachix/install-nix-action@v12

- name: Setup cabal cache
uses: actions/cache@v1
with:
path: "~/.cabal"
key: ${{ runner.os }}-${{ matrix.ghc }}-${{ hashFiles('kesha.cabal') }}

- name: Run ci.sh
run: "nix-shell --arg dev false --run ./ci.sh"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist-newstyle/
dist/
.stack-work/
result
cabal.project.freeze
3 changes: 0 additions & 3 deletions .travis.yml

This file was deleted.

3 changes: 1 addition & 2 deletions ci.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env nix-shell
#! nix-shell -i bash
#!/usr/bin/env bash

set -euo pipefail
shopt -s inherit_errexit
Expand Down
62 changes: 39 additions & 23 deletions kesha.cabal
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
cabal-version: >=1.10
build-type: Simple
cabal-version: >= 1.10

name: kesha
version: 0.2.0
synopsis: Compute the cryptographic hash of any path
description:
A Haskell library and executable for computing the cryptographic hash of any path.

version: 0.1.0
synopsis: Haskell implementation of nix-hash
description: Compute the cryptographic hash of a path, à la <https://nixos.org/ Nix>.
homepage: https://github.com/jmackie/kesha
bug-reports: https://github.com/jmackie/kesha/issues
license: MIT
license-file: LICENSE
author: Jordan Mackie
maintainer: [email protected]
maintainer: [email protected]
copyright: (c) 2020 Jordan Mackie
category: System
build-type: Simple
extra-source-files:
README.md

tested-with:
GHC ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.3
GHC == 8.0.2,
GHC == 8.2.2,
GHC == 8.4.4,
GHC == 8.6.5,
GHC == 8.8.3,
GHC == 8.10.1

source-repository head
type: git
Expand All @@ -28,7 +31,11 @@ source-repository head
library
default-language: Haskell2010
hs-source-dirs: src
ghc-options: -Wall
ghc-options:
-Weverything
-fno-warn-missing-import-lists
-fno-warn-safe
-fno-warn-unsafe
exposed-modules:
Kesha
Kesha.NAR
Expand All @@ -37,34 +44,43 @@ library
-- >= 8.2.2 && < 8.9
base >= 4.10.1 && < 4.14,

-- boot libraries
binary,
bytestring,
containers,
-- core libraries
binary >= 0.8.6 && < 0.9,
bytestring >= 0.10.8 && < 0.11,
containers >= 0.6.0 && < 0.7,
filepath >= 1.4.2 && < 1.5,
text >= 1.2.3 && < 1.3,
-- 1.3.1.0 introduced `getSymbolicLinkTarget`
directory >= 1.3.1,
filepath,
text,
directory >= 1.3.1 && < 1.4,

cryptohash-sha256
cryptohash-md5 >= 0.11.100 && < 0.12,
cryptohash-sha1 >= 0.11.100 && < 0.12,
cryptohash-sha256 >= 0.11.101 && < 0.12

test-suite test
default-language: Haskell2010
type: exitcode-stdio-1.0
main-is: Main.hs
hs-source-dirs: test
ghc-options: -Wall -threaded -rtsopts
ghc-options:
-Weverything
-fno-warn-missing-import-lists
-fno-warn-safe
-fno-warn-unsafe

-threaded
-rtsopts
build-depends:
kesha,

base >= 4.10.1 && < 4.14,

base,
bytestring,
containers,
directory >= 1.3.1,
directory,
filepath,
process,

-- Test dependencies
process,
hspec,
QuickCheck,
temporary
37 changes: 20 additions & 17 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
{ compiler ? "ghc865", dev ? true # Include tools useful for development?
}:
let
pinnedPkgs =
# 2020-05-17T11:55:13+02:00
let rev = "85d6f3bcd9cbcc52c4a307d2ef5116dab4b41641";
in import (builtins.fetchTarball {
name = "nixpkgs-${rev}";
url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz";
sha256 = "10jxpwq47clbj047jh5zn20hqmwpc821ac8zljgpayk0sk5p0mwv";
}) { };
in { pkgs ? pinnedPkgs, compiler ? "ghc865" }:
pkgs.mkShell {
buildInputs = [
pkgs.haskell.packages."${compiler}".ghc
pkgs.cabal-install
pkgs.ormolu
pkgs.hlint
];
}
pkgs = let rev = "7d75a77954aaa61fa0b2931b354b61cc0aa4a60a";
in import (builtins.fetchTarball {
name = "nixpkgs-${rev}";
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
sha256 = "0qf3rp0jqgipjzz51ws8nwxiaiglz9jf7hc6d7x75ddbp0wlfjmg";
}) { overlays = [ (self: super: { llvm_39 = super.llvm_5; }) ]; };

old-ghc-nix = let rev = "674d459c7376af6641c94e91cd7d36214661b481";
in import (builtins.fetchTarball {
name = "old-ghc-nix-${rev}";
url = "https://github.com/mpickering/old-ghc-nix/archive/${rev}.tar.gz";
sha256 = "1w3d5dm9v7ms1s7xp39wncgwvh3kclp2bkdqa2kxn3x5dyayfm61";
}) { inherit pkgs; };

in pkgs.mkShell {
buildInputs =
[ old-ghc-nix."${compiler}" pkgs.cabal-install pkgs.ormolu pkgs.hlint ]
++ (if dev then [ pkgs.ghcid ] else [ ]);
}
105 changes: 75 additions & 30 deletions src/Kesha.hs
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module: Kesha
-- Copyright: (c) 2020 Jordan Mackie
-- License: MIT
-- Maintainer: Jordan Mackie <[email protected]>
-- Stability: experimental
-- Portability: portable
--
-- An implementation of @<https://nixos.org/guides/nix-pills/nix-store-paths.html#idm140737319621872 nix-hash>@.
module Kesha
( hash,
hashWith,
Opts (..),
defaultOpts,
HashOptions (..),
defaultHashOptions,
HashAlgo (..),
HashRepr (..),
)
where

import qualified Crypto.Hash.MD5 as MD5
import qualified Crypto.Hash.SHA1 as SHA1
import qualified Crypto.Hash.SHA256 as SHA256
import Data.Bits ((.&.), (.|.), shiftL, shiftR)
import Data.Bits (shiftL, shiftR, (.&.), (.|.))
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as ASCII
import qualified Data.Char as Char
Expand All @@ -22,87 +33,121 @@ import qualified Kesha.NAR as NAR
import Prelude hiding ((!!))

-- |
-- Compute the cryptographic hash of a path using the 'defaultOpts'.
-- Compute the cryptographic hash of a path using the 'defaultHashOptions'.
--
-- The output of @'hash' path@ should be consistent with that of
-- @nix-hash --type sha256 --base32 path@ (invoked at the command line).
hash :: FilePath -> IO (Either String BS.ByteString)
hash = hashWith defaultOpts
-- @nix-hash --type sha256 --base32 path@.
hash :: FilePath -> IO (Either NAR.PackError BS.ByteString)
hash = hashWith defaultHashOptions

-- |
-- Compute the cryptographic hash of a path using the given 'Opts'.
hashWith :: Opts -> FilePath -> IO (Either String BS.ByteString)
-- Compute the cryptographic hash of a path using the given 'HashOptions'.
hashWith :: HashOptions -> FilePath -> IO (Either NAR.PackError BS.ByteString)
hashWith opts path =
fmap (printNar (hashAlgo opts) (hashRepr opts)) <$> NAR.localPack path

-- |
-- Hashing options.
data Opts
= Opts
{ -- | cryptographic hash algorithm to use
hashAlgo :: HashAlgo,
-- | how to print the hash
hashRepr :: HashRepr
}
data HashOptions = HashOptions
{ -- | cryptographic hash algorithm to use
hashAlgo :: HashAlgo,
-- | how to print the hash
hashRepr :: HashRepr
}

-- |
-- Default hashing options.
--
-- These are the default options used by most of the Nix tooling (e.g.
-- @nix-prefetch-git@).
defaultOpts :: Opts
defaultOpts = Opts SHA256 Base32
defaultHashOptions :: HashOptions
defaultHashOptions = HashOptions SHA256 Base32

-- |
-- Available hash algorithms.
data HashAlgo
= SHA256
= MD5
| SHA1
| SHA256

-- |
-- Printable hash representations.
data HashRepr
= Base32
= Base16
| Base32

printNar :: HashAlgo -> HashRepr -> NAR.NAR -> BS.ByteString
printNar SHA256 Base32 =
printNar algo repr =
ASCII.map Char.toLower
. printHash32 SHA256
. SHA256.hash
. ( case repr of
Base16 -> printHash16 algo
Base32 -> printHash32 algo
)
. ( case algo of
MD5 -> MD5.hash
SHA1 -> SHA1.hash
SHA256 -> SHA256.hash
)
. NAR.dump

-- https://github.com/NixOS/nix/blob/master/src/libutil/hash.cc
printHash16 :: HashAlgo -> BS.ByteString -> BS.ByteString
printHash16 algo rawHash =
ASCII.pack $
foldMap
( \i ->
[ base16Chars !! fromIntegral (BS.index rawHash i `shiftR` 4),
base16Chars !! fromIntegral (BS.index rawHash i .&. 15)
]
)
[0 .. hashSize - 1]
where
hashSize :: Int
hashSize = hashSizeForAlgo algo

base16Chars :: Seq.Seq Char
base16Chars = "0123456789abcdef"

-- https://github.com/NixOS/nix/blob/master/src/libutil/hash.cc
printHash32 :: HashAlgo -> BS.ByteString -> BS.ByteString
printHash32 algo rawHash = go (len - 1) ""
where
hashSize :: Int
hashSize = hashSizeForAlgo algo

-- omitted: E O U T
base32Chars :: Seq.Seq Char
base32Chars = Seq.fromList "0123456789abcdfghijklmnpqrsvwxyz"

len :: Int
len = (hashSize * 8 - 1) `div` 5 + 1

go :: Int -> BS.ByteString -> BS.ByteString
go n accum
| n < 0 = accum
| otherwise =
go (pred n) $
ASCII.snoc accum (base32Chars !! (fromIntegral c .&. 0x1f))
where
b , i, j :: Int
b, i, j :: Int
b = n * 5
i = b `div` 8
j = b `mod` 8
c :: Word8
c =
((bytes !! i) `shiftR` j)
.|. (if i >= (hashSize - 1) then 0 else (bytes !! (i + 1)) `shiftL` (8 - j))

bytes :: Seq.Seq Word8
bytes = Seq.fromList (BS.unpack rawHash)
(!!) :: Seq.Seq a -> Int -> a
(!!) xs i = fromJust (Seq.lookup i xs)
infixl 9 !!

-- https://github.com/NixOS/nix/blob/master/src/libutil/hash.hh
hashSizeForAlgo :: HashAlgo -> Int
hashSizeForAlgo MD5 = 16
hashSizeForAlgo SHA1 = 20
hashSizeForAlgo SHA256 = 32

-- omitted: E O U T
base32Chars :: Seq.Seq Char
base32Chars = Seq.fromList "0123456789abcdfghijklmnpqrsvwxyz"
(!!) :: Seq.Seq a -> Int -> a
(!!) xs i = fromJust (Seq.lookup i xs)

infixl 9 !!
Loading

0 comments on commit 4f2213a

Please sign in to comment.