cabal-install-3.16.0.0: src/Distribution/Client/CmdSdist.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-}
module Distribution.Client.CmdSdist
( sdistCommand
, sdistAction
, packageToSdist
, OutputFormat (..)
) where
import Distribution.Client.Compat.Prelude
import Prelude ()
import Distribution.Client.CmdErrorMessages
( Plural (..)
, renderComponentKind
)
import Distribution.Client.DistDirLayout
( DistDirLayout (..)
, ProjectRoot (..)
)
import Distribution.Client.NixStyleOptions
( NixStyleFlags (..)
, defaultNixStyleFlags
, updNixStyleCommonSetupFlags
)
import Distribution.Client.ProjectConfig
( ProjectConfig
, commandLineFlagsToProjectConfig
, projectConfigConfigFile
, projectConfigShared
, withGlobalConfig
, withProjectOrGlobalConfig
)
import Distribution.Client.ProjectFlags
( ProjectFlags (..)
, defaultProjectFlags
, projectFlagsOptions
)
import Distribution.Client.ProjectOrchestration
( CurrentCommand (..)
, ProjectBaseContext (..)
, establishProjectBaseContext
, establishProjectBaseContextWithRoot
)
import Distribution.Client.Setup
( CommonSetupFlags (..)
, GlobalFlags (..)
)
import Distribution.Client.TargetSelector
( ComponentKind
, TargetSelector (..)
, readTargetSelectors
, reportTargetSelectorProblems
)
import Distribution.Client.Types
( PackageLocation (..)
, PackageSpecifier (..)
, UnresolvedSourcePackage
)
import Distribution.Solver.Types.SourcePackage
( SourcePackage (..)
)
import Distribution.Utils.Path hiding
( (<.>)
, (</>)
)
import Distribution.Client.Errors
import Distribution.Client.SrcDist
( packageDirToSdist
)
import Distribution.Compat.Lens
( _1
, _2
)
import Distribution.Package
( Package (packageId)
)
import Distribution.PackageDescription.Configuration
( flattenPackageDescription
)
import Distribution.ReadE
( succeedReadE
)
import Distribution.Simple.Command
( CommandUI (..)
, OptionField
, ShowOrParseArgs
, liftOptionL
, option
, reqArg
)
import Distribution.Simple.PreProcess
( knownSuffixHandlers
)
import Distribution.Simple.Setup
( Flag
, flagToList
, flagToMaybe
, fromFlagOrDefault
, optionDistPref
, optionVerbosity
, toFlag
, trueArg
, pattern Flag
)
import Distribution.Simple.SrcDist
( listPackageSourcesWithDie
)
import Distribution.Simple.Utils
( dieWithException
, notice
, withOutputMarker
, wrapText
)
import Distribution.Types.ComponentName
( ComponentName
, showComponentName
)
import Distribution.Types.GenericPackageDescription (GenericPackageDescription)
import Distribution.Types.PackageName
( PackageName
, unPackageName
)
import Distribution.Verbosity
( normal
)
import qualified Data.ByteString.Lazy.Char8 as BSL
import System.Directory
( createDirectoryIfMissing
, getCurrentDirectory
, makeAbsolute
)
import System.FilePath
( makeRelative
, normalise
, (<.>)
, (</>)
)
-------------------------------------------------------------------------------
-- Command
-------------------------------------------------------------------------------
sdistCommand :: CommandUI (ProjectFlags, SdistFlags)
sdistCommand =
CommandUI
{ commandName = "v2-sdist"
, commandSynopsis = "Generate a source distribution file (.tar.gz)."
, commandUsage = \pname ->
"Usage: " ++ pname ++ " v2-sdist [FLAGS] [PACKAGES]\n"
, commandDescription = Just $ \_ ->
wrapText
"Generates tarballs of project packages suitable for upload to Hackage."
, commandNotes = Nothing
, commandDefaultFlags = (defaultProjectFlags, defaultSdistFlags)
, commandOptions = \showOrParseArgs ->
map (liftOptionL _1) (projectFlagsOptions showOrParseArgs)
++ map (liftOptionL _2) (sdistOptions showOrParseArgs)
}
-------------------------------------------------------------------------------
-- Flags
-------------------------------------------------------------------------------
data SdistFlags = SdistFlags
{ sdistVerbosity :: Flag Verbosity
, sdistDistDir :: Flag (SymbolicPath Pkg (Dir Dist))
, sdistListSources :: Flag Bool
, sdistNulSeparated :: Flag Bool
, sdistOutputPath :: Flag FilePath
}
defaultSdistFlags :: SdistFlags
defaultSdistFlags =
SdistFlags
{ sdistVerbosity = toFlag normal
, sdistDistDir = mempty
, sdistListSources = toFlag False
, sdistNulSeparated = toFlag False
, sdistOutputPath = mempty
}
sdistOptions :: ShowOrParseArgs -> [OptionField SdistFlags]
sdistOptions showOrParseArgs =
[ optionVerbosity
sdistVerbosity
(\v flags -> flags{sdistVerbosity = v})
, optionDistPref
sdistDistDir
(\dd flags -> flags{sdistDistDir = dd})
showOrParseArgs
, option
['l']
["list-only"]
"Just list the sources, do not make a tarball"
sdistListSources
(\v flags -> flags{sdistListSources = v})
trueArg
, option
[]
["null-sep"]
"Separate the source files with NUL bytes rather than newlines."
sdistNulSeparated
(\v flags -> flags{sdistNulSeparated = v})
trueArg
, option
['o']
["output-directory", "outputdir"]
"Choose the output directory of this command. '-' sends all output to stdout"
sdistOutputPath
(\o flags -> flags{sdistOutputPath = o})
(reqArg "PATH" (succeedReadE Flag) flagToList)
]
-------------------------------------------------------------------------------
-- Action
-------------------------------------------------------------------------------
sdistAction :: (ProjectFlags, SdistFlags) -> [String] -> GlobalFlags -> IO ()
sdistAction (pf@ProjectFlags{..}, SdistFlags{..}) targetStrings globalFlags = do
(baseCtx, distDirLayout) <-
withProjectOrGlobalConfig
flagIgnoreProject
withProject
(withGlobalConfig verbosity globalConfigFlag withoutProject)
let localPkgs = localPackages baseCtx
targetSelectors <-
either (reportTargetSelectorProblems verbosity) return
=<< readTargetSelectors localPkgs Nothing targetStrings
-- elaborate path, create target directory
mOutputPath' <- case mOutputPath of
Just "-" -> return (Just "-")
Just path -> do
abspath <- makeAbsolute path
createDirectoryIfMissing True abspath
return (Just abspath)
Nothing -> do
createDirectoryIfMissing True (distSdistDirectory distDirLayout)
return Nothing
let format :: OutputFormat
format =
if
| listSources, nulSeparated -> SourceList '\0'
| listSources -> SourceList '\n'
| otherwise -> TarGzArchive
ext = case format of
SourceList _ -> "list"
TarGzArchive -> "tar.gz"
outputPath pkg = case mOutputPath' of
Just path
| path == "-" -> "-"
| otherwise -> path </> prettyShow (packageId pkg) <.> ext
Nothing
| listSources -> "-"
| otherwise -> distSdistFile distDirLayout (packageId pkg)
case reifyTargetSelectors localPkgs targetSelectors of
Left errs -> dieWithException verbosity $ SdistActionException . fmap renderTargetProblem $ errs
Right pkgs
| length pkgs > 1
, not listSources
, Just "-" <- mOutputPath' ->
dieWithException verbosity Can'tWriteMultipleTarballs
| otherwise ->
for_ pkgs $ \pkg ->
packageToSdist
verbosity
(distProjectRootDirectory distDirLayout)
format
(outputPath pkg)
pkg
where
verbosity = fromFlagOrDefault normal sdistVerbosity
listSources = fromFlagOrDefault False sdistListSources
nulSeparated = fromFlagOrDefault False sdistNulSeparated
mOutputPath = flagToMaybe sdistOutputPath
prjConfig :: ProjectConfig
prjConfig =
commandLineFlagsToProjectConfig
globalFlags
(updNixStyleCommonSetupFlags (const commonFlags) $ defaultNixStyleFlags ())
{ projectFlags = pf
}
mempty
commonFlags =
mempty
{ setupVerbosity = sdistVerbosity
, setupDistPref = sdistDistDir
}
globalConfigFlag = projectConfigConfigFile (projectConfigShared prjConfig)
withProject :: IO (ProjectBaseContext, DistDirLayout)
withProject = do
baseCtx <- establishProjectBaseContext verbosity prjConfig OtherCommand
return (baseCtx, distDirLayout baseCtx)
withoutProject :: ProjectConfig -> IO (ProjectBaseContext, DistDirLayout)
withoutProject config = do
cwd <- getCurrentDirectory
baseCtx <- establishProjectBaseContextWithRoot verbosity (config <> prjConfig) (ProjectRootImplicit cwd) OtherCommand
return (baseCtx, distDirLayout baseCtx)
data OutputFormat
= SourceList Char
| TarGzArchive
deriving (Show, Eq)
packageToSdist :: Verbosity -> FilePath -> OutputFormat -> FilePath -> UnresolvedSourcePackage -> IO ()
packageToSdist verbosity projectRootDir format outputFile pkg = do
let death = dieWithException verbosity $ ImpossibleHappened (show pkg)
dir0 <- case srcpkgSource pkg of
LocalUnpackedPackage path -> pure (Right path)
RemoteSourceRepoPackage _ (Just tgz) -> pure (Left tgz)
RemoteSourceRepoPackage{} -> death
LocalTarballPackage tgz -> pure (Left tgz)
RemoteTarballPackage _ (Just tgz) -> pure (Left tgz)
RemoteTarballPackage{} -> death
RepoTarballPackage{} -> death
let
-- Write String to stdout or file, using the default TextEncoding.
write str
| outputFile == "-" = putStr (withOutputMarker verbosity str)
| otherwise = do
writeFile outputFile str
notice verbosity $ "Wrote source list to " ++ outputFile ++ "\n"
-- Write raw ByteString to stdout or file as it is, without encoding.
writeLBS lbs
| outputFile == "-" = BSL.putStr lbs
| otherwise = do
BSL.writeFile outputFile lbs
notice verbosity $ "Wrote tarball sdist to " ++ outputFile ++ "\n"
case dir0 of
Left tgz -> do
case format of
TarGzArchive -> do
writeLBS =<< BSL.readFile tgz
_ -> dieWithException verbosity $ CannotConvertTarballPackage (show format)
Right dir -> do
case format of
SourceList nulSep -> do
let gpd :: GenericPackageDescription
gpd = srcpkgDescription pkg
files' <- listPackageSourcesWithDie verbosity dieWithException (Just $ makeSymbolicPath dir) (flattenPackageDescription gpd) knownSuffixHandlers
let files = nub $ sort $ map (normalise . getSymbolicPath) files'
let prefix = makeRelative (normalise projectRootDir) dir
write $ concat [prefix </> i ++ [nulSep] | i <- files]
TarGzArchive -> do
packageDirToSdist verbosity (srcpkgDescription pkg) dir >>= writeLBS
--
reifyTargetSelectors :: [PackageSpecifier UnresolvedSourcePackage] -> [TargetSelector] -> Either [TargetProblem] [UnresolvedSourcePackage]
reifyTargetSelectors pkgs sels =
case partitionEithers (foldMap go sels) of
([], sels') -> Right sels'
(errs, _) -> Left errs
where
-- there can be pkgs which are in extra-packages:
-- these are not SpecificSourcePackage
--
-- Why these packages are in localPkgs, it's confusing.
-- Anyhow, better to be lenient here.
--
flatten (SpecificSourcePackage pkg@SourcePackage{}) = Just pkg
flatten _ = Nothing
pkgs' = mapMaybe flatten pkgs
getPkg pid = case find ((== pid) . packageId) pkgs' of
Just pkg -> Right pkg
Nothing -> error "The impossible happened: we have a reference to a local package that isn't in localPackages."
go :: TargetSelector -> [Either TargetProblem UnresolvedSourcePackage]
go (TargetPackage _ pids Nothing) = fmap getPkg pids
go (TargetAllPackages Nothing) = Right <$> pkgs'
go (TargetPackage _ _ (Just kind)) = [Left (AllComponentsOnly kind)]
go (TargetAllPackages (Just kind)) = [Left (AllComponentsOnly kind)]
go (TargetPackageNamed pname _) = [Left (NonlocalPackageNotAllowed pname)]
go (TargetComponentUnknown pname _ _) = [Left (NonlocalPackageNotAllowed pname)]
go (TargetComponent _ cname _) = [Left (ComponentsNotAllowed cname)]
data TargetProblem
= AllComponentsOnly ComponentKind
| NonlocalPackageNotAllowed PackageName
| ComponentsNotAllowed ComponentName
renderTargetProblem :: TargetProblem -> String
renderTargetProblem (AllComponentsOnly kind) =
"It is not possible to package only the "
++ renderComponentKind Plural kind
++ " from a package "
++ "for distribution. Only entire packages may be packaged for distribution."
renderTargetProblem (ComponentsNotAllowed cname) =
"The component "
++ showComponentName cname
++ " cannot be packaged for distribution on its own. "
++ "Only entire packages may be packaged for distribution."
renderTargetProblem (NonlocalPackageNotAllowed pname) =
"The package "
++ unPackageName pname
++ " cannot be packaged for distribution, because it is not "
++ "local to this project."