licensor 0.4.3 → 0.5.0
raw patch · 5 files changed
+123/−22 lines, 5 filesdep +bytestringdep +http-clientdep +http-client-tlsdep ~Cabaldep ~base
Dependencies added: bytestring, http-client, http-client-tls, tar, temporary, zlib
Dependency ranges changed: Cabal, base
Files
- CHANGELOG.md +4/−0
- README.md +24/−3
- app/Main.hs +21/−12
- licensor.cabal +10/−4
- src/Licensor.hs +64/−3
CHANGELOG.md view
@@ -1,3 +1,7 @@+# 0.5.0 (2021-05-30)++- Add `--licenses` option to collect the license files for dependencies+ # 0.4.3 (2021-02-06) - Use System.Console.GetOpt instead of cmdargs
README.md view
@@ -81,11 +81,25 @@ $ cd licensor/ && stack build --exec licensor ``` +To generate a report including license files for all dependencies,+use the `--licenses` option and redirect the output to a file:++```+$ licensor --quiet --licenses > licenses.md+```++See [licenses.md](licenses.md) for an example.++NOTE: This is currently using the Hackage API, which doesn't require+building any package, but could probably be improved to use the+installed packages if present.+ For more information, run `licensor --help`: ``` usage: licensor -q, -s --quiet, --silent Quiet/silent mode+ --licenses Fetch license files -h --help Display help message -V --version Print version information ```@@ -139,24 +153,31 @@ ### License report -Licensor (0.4.3) depends on the following libraries:+Licensor (0.5.0) depends on the following libraries: Library | License ------------------------- | ------------------------- Cabal | BSD-3 base | BSD-3+bytestring | BSD-3 containers | BSD-3 directory | BSD-3+http-client | MIT+http-client-tls | MIT process | BSD-3+tar | BSD-3+temporary | BSD-3+zlib | BSD-3 And the following licenses (including transitive dependencies): License | Number of libraries ------------------------- | ------------------------- BSD-2 | 1-BSD-3 | 20+BSD-3 | 22+MIT | 5 -(Tested with Licensor 0.4.3, Stack 2.5.1, and LTS Haskell 15.8.)+(Tested with Licensor 0.5.0, Stack 2.7.1, and LTS Haskell 17.13.) ## Additional resources
app/Main.hs view
@@ -17,8 +17,8 @@ import Distribution.Text -- base-import Control.Monad (unless)-import Data.Foldable (for_)+import Control.Monad (unless, when)+import Data.Foldable (for_, traverse_) import Data.List (foldl', intercalate) import Data.Version (showVersion) import System.Console.GetOpt@@ -47,11 +47,14 @@ unwords [progName, showVersion version] -optDescrs :: [OptDescr (Bool -> IO Bool)]+optDescrs :: [OptDescr ((Bool, Bool) -> IO (Bool, Bool))] optDescrs = [ Option ['q', 's'] ["quiet", "silent"]- (NoArg (\_ -> pure False))+ (NoArg (\(_, b) -> pure (False, b))) "Quiet/silent mode"+ , Option [] ["licenses"]+ (NoArg (\(a, _) -> pure (a, True)))+ "Fetch license files" , Option ['h'] ["help"] (NoArg (\_ -> exitWith prettyUsageInfo)) "Display help message"@@ -72,7 +75,7 @@ progName <- getProgName case getOpt' Permute optDescrs args of (opts, [], [], []) -> do- verbose <- foldl' (>>=) (pure True) opts+ verbose <- foldl' (>>=) (pure (True, False)) opts main' verbose (_, command:_, _, _) -> do hPutStrLn stderr ("unknown command: " <> command)@@ -87,8 +90,8 @@ exitFailure -main' :: Bool -> IO ()-main' verbose = do+main' :: (Bool, Bool) -> IO ()+main' (verbose, fetchLicenses) = do let silent = not verbose maybePackage <- getPackage@@ -105,6 +108,9 @@ die "Error: No stack.yaml file found." Just pd -> do+ when fetchLicenses $ do+ putStrLn $ "# " <> display (package pd)+ putStrLn "" putStrLn $ "Package: " <> display (package pd)@@ -119,24 +125,27 @@ case (maybeDependencies, maybeLicenses) of (Just dependencies, Just licenses) -> do- (dependenciesByLicense', failed) <-+ (dependenciesByLicense, failed) <- orderPackagesByLicense silent pid licenses dependencies - let dependenciesByLicense = fmap (Set.map display) dependenciesByLicense'- for_ (Map.keys dependenciesByLicense) $ \li -> let n = dependenciesByLicense Map.! li in do- putStrLn "-----"+ when fetchLicenses $ do+ putStrLn ""+ putStrLn $ "## " <> display li+ putStrLn "" putStrLn $ show (Set.size n) <> (if Set.size n == 1 then " package " else " packages ") <> "licensed under " <> display li <> ": "- <> intercalate ", " (Set.toList n)+ <> intercalate ", " (Set.toList (Set.map display n))+ when fetchLicenses $+ traverse_ getPackageLicenseFiles (Set.toList n) unless (null failed) $ do putStr "Failed: "
licensor.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2 name: licensor-version: 0.4.3+version: 0.5.0 synopsis: A license compatibility helper description: A license compatibility helper. category: Distribution@@ -21,8 +21,8 @@ common common build-depends:- Cabal >= 3.0- , base >= 4.8 && < 5+ Cabal >= 3.0.1 && < 3.3+ , base >= 4.13.0 && < 4.15 , containers , directory default-language: Haskell2010@@ -41,7 +41,13 @@ autogen-modules: Paths_licensor build-depends:- process+ bytestring+ , http-client >= 0.6.4 && < 0.8+ , http-client-tls >= 0.3.5 && < 0.4+ , process+ , tar >= 0.5.1 && < 0.6+ , temporary >= 1.3 && < 1.4+ , zlib >= 0.6.2 && < 0.7 executable licensor import:
src/Licensor.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-} ---------------------------------------------------------------------- -- |@@ -16,6 +17,7 @@ , getDependencies , getLicenses , getPackage+ , getPackageLicenseFiles , orderPackagesByLicense , version )@@ -24,13 +26,19 @@ -- base import qualified Control.Exception as Exception import Control.Monad (unless)+import Data.Traversable (for) import Data.Version (Version) +-- bytestring+import qualified Data.ByteString.Lazy as LBS+ -- Cabal import Distribution.License (License)-import Distribution.Package (PackageIdentifier(..), PackageName)-import Distribution.PackageDescription (PackageDescription, packageDescription)-import Distribution.PackageDescription.Parsec (readGenericPackageDescription)+import Distribution.Package (PackageIdentifier(..), PackageName, unPackageName)+import Distribution.PackageDescription+ (PackageDescription, licenseFiles, packageDescription)+import Distribution.PackageDescription.Parsec+ (parseGenericPackageDescriptionMaybe, readGenericPackageDescription) import Distribution.Pretty (Pretty) import Distribution.Simple.Utils (comparing, findPackageDesc) import Distribution.Text (display, simpleParse)@@ -45,13 +53,27 @@ -- directory import System.Directory (getCurrentDirectory) +-- http-client+import Network.HTTP.Client (httpLbs, parseRequest, responseBody)++-- http-client-tls+import Network.HTTP.Client.TLS (getGlobalManager)+ -- licensor import qualified Paths_licensor -- process import System.Process (readProcess) +-- tar+import qualified Codec.Archive.Tar as Tar +-- temporary+import qualified System.IO.Temp as Temp++-- zlib+import qualified Codec.Compression.GZip as GZip+ -- | -- --@@ -206,3 +228,42 @@ version :: Version version = Paths_licensor.version+++getPackageLicenseFiles :: PackageIdentifier -> IO (Maybe [String])+getPackageLicenseFiles packageIdentifier =+ getPackageLicenseFiles'+ (unPackageName (pkgName packageIdentifier))+ (display packageIdentifier)++getPackageLicenseFiles' :: String -> String -> IO (Maybe [String])+getPackageLicenseFiles' packageName packageId = do+ putStrLn ""+ putStrLn ("### " <> packageId)+ putStrLn ""+ manager <- getGlobalManager+ genPkgDescrReq <- parseRequest (mkRequest (packageName <> ".cabal"))+ genPkgDescr <- LBS.toStrict . responseBody <$> httpLbs genPkgDescrReq manager+ let mGenPkgDescr = parseGenericPackageDescriptionMaybe genPkgDescr+ case licenseFiles . packageDescription <$> mGenPkgDescr of+ Just files -> do+ pkgReq <- parseRequest (mkRequest (packageId <> ".tar.gz"))+ pkg <- responseBody <$> httpLbs pkgReq manager+ let entries = Tar.read (GZip.decompress pkg)+ pkgLicenseFiles <-+ Temp.withSystemTempDirectory packageId $ \tmpDir -> do+ Tar.unpack tmpDir entries+ for files $ \file -> do+ contents <- readFile (tmpDir <> "/" <> packageId <> "/" <> file)+ putStrLn (file <> ":")+ putStrLn ""+ putStrLn "```"+ putStrLn contents+ putStrLn "```"+ pure file+ pure (Just pkgLicenseFiles)+ Nothing ->+ pure Nothing+ where+ mkRequest r =+ "GET https://hackage.haskell.org/package/" <> packageId <> "/" <> r