Skip to content

Commit 62220ce

Browse files
authored
Merge pull request #3187 from martin-kolinek/sdist-build
Support building the sdist and upload tarball
2 parents 78ebdf5 + e578211 commit 62220ce

File tree

42 files changed

+553
-52
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+553
-52
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,8 @@ Other enhancements:
10881088
* `stack build --fast` turns off optimizations
10891089
* Show progress while downloading package index
10901090
[#1223](https://github.com/commercialhaskell/stack/issues/1223).
1091+
* Allow running tests on tarball created by sdist and upload
1092+
[#717](https://github.com/commercialhaskell/stack/issues/717).
10911093

10921094
Bug fixes:
10931095

src/Stack/Config.hs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module Stack.Config
4444
,defaultConfigYaml
4545
,getProjectConfig
4646
,LocalConfigStatus(..)
47+
,removePathFromPackageEntry
4748
) where
4849

4950
import qualified Codec.Archive.Tar as Tar
@@ -791,6 +792,30 @@ resolvePackageLocation menv projRoot (PLRemote url remotePackageType) = do
791792
throwM $ UnexpectedArchiveContents dirs files
792793
_ -> return dir
793794

795+
-- | Remove path from package entry. If the package entry contains subdirs, then it removes
796+
-- the subdir. If the package entry points to the path to remove, this function returns
797+
-- Nothing. If the package entry doesn't mention the path to remove, it is returned unchanged
798+
removePathFromPackageEntry
799+
:: (StackMiniM env m, HasConfig env)
800+
=> EnvOverride
801+
-> Path Abs Dir -- ^ project root
802+
-> Path Abs Dir -- ^ path to remove
803+
-> PackageEntry
804+
-> m (Maybe PackageEntry)
805+
-- ^ Nothing if the whole package entry should be removed, otherwise
806+
-- it returns the updated PackageEntry
807+
removePathFromPackageEntry menv projectRoot pathToRemove packageEntry = do
808+
locationPath <- resolvePackageLocation menv projectRoot (peLocation packageEntry)
809+
case peSubdirs packageEntry of
810+
[] -> if locationPath == pathToRemove then return Nothing else return (Just packageEntry)
811+
subdirPaths -> do
812+
let shouldKeepSubdir path = do
813+
resolvedPath <- resolveDir locationPath path
814+
return (pathToRemove /= resolvedPath)
815+
filteredSubdirs <- filterM shouldKeepSubdir subdirPaths
816+
if null filteredSubdirs then return Nothing else return (Just packageEntry {peSubdirs = filteredSubdirs})
817+
818+
794819

795820
-- | Get the stack root, e.g. @~/.stack@, and determine whether the user owns it.
796821
--

src/Stack/Options/SDistParser.hs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module Stack.Options.SDistParser where
2+
3+
import Data.Monoid
4+
import Options.Applicative
5+
import Options.Applicative.Builder.Extra
6+
import Stack.SDist
7+
import Stack.Options.HpcReportParser (pvpBoundsOption)
8+
9+
-- | Parser for arguments to `stack sdist` and `stack upload`
10+
sdistOptsParser :: Bool -- ^ Whether to sign by default `stack upload` does, `stack sdist` doesn't
11+
-> Parser SDistOpts
12+
sdistOptsParser signDefault = SDistOpts <$>
13+
many (strArgument $ metavar "DIR" <> completer dirCompleter) <*>
14+
optional pvpBoundsOption <*>
15+
ignoreCheckSwitch <*>
16+
(if signDefault
17+
then switch (long "no-signature" <> help "Do not sign & upload signatures")
18+
else switch (long "sign" <> help "Sign & upload signatures")) <*>
19+
strOption
20+
(long "sig-server" <> metavar "URL" <> showDefault <>
21+
value "https://sig.commercialhaskell.org" <>
22+
help "URL") <*>
23+
buildPackageOption
24+
where
25+
ignoreCheckSwitch =
26+
switch (long "ignore-check"
27+
<> help "Do not check package for common mistakes")
28+
buildPackageOption =
29+
boolFlags False "test-tarball" "building of the resulting tarball" idm

src/Stack/SDist.hs

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,21 @@ module Stack.SDist
1111
( getSDistTarball
1212
, checkSDistTarball
1313
, checkSDistTarball'
14+
, SDistOpts (..)
1415
) where
1516

1617
import qualified Codec.Archive.Tar as Tar
1718
import qualified Codec.Archive.Tar.Entry as Tar
1819
import qualified Codec.Compression.GZip as GZip
1920
import Control.Applicative
2021
import Control.Concurrent.Execute (ActionContext(..))
21-
import Control.Monad (unless, void, liftM)
22+
import Control.Monad (unless, void, liftM, filterM, foldM, when)
2223
import Control.Monad.Catch
2324
import Control.Monad.IO.Class
2425
import Control.Monad.Logger
26+
import Control.Monad.Reader.Class (local)
2527
import Control.Monad.Trans.Control (liftBaseWith)
28+
import Control.Monad.Trans.Unlift (MonadBaseUnlift)
2629
import qualified Data.ByteString as S
2730
import qualified Data.ByteString.Char8 as S8
2831
import qualified Data.ByteString.Lazy as L
@@ -35,7 +38,7 @@ import Data.List.Extra (nubOrd)
3538
import Data.List.NonEmpty (NonEmpty)
3639
import qualified Data.List.NonEmpty as NE
3740
import qualified Data.Map.Strict as Map
38-
import Data.Maybe (fromMaybe)
41+
import Data.Maybe (fromMaybe, catMaybes)
3942
import Data.Monoid ((<>))
4043
import qualified Data.Set as Set
4144
import qualified Data.Text as T
@@ -50,14 +53,16 @@ import Distribution.PackageDescription.PrettyPrint (showGenericPackage
5053
import Distribution.Text (display)
5154
import Distribution.Version (simplifyVersionRange, orLaterVersion, earlierVersion)
5255
import Distribution.Version.Extra
56+
import Lens.Micro (set)
5357
import Path
5458
import Path.IO hiding (getModificationTime, getPermissions)
5559
import Prelude -- Fix redundant import warnings
56-
import Stack.Build (mkBaseConfigOpts)
60+
import Stack.Build (mkBaseConfigOpts, build)
5761
import Stack.Build.Execute
5862
import Stack.Build.Installed
5963
import Stack.Build.Source (loadSourceMap, getDefaultPackageConfig)
6064
import Stack.Build.Target
65+
import Stack.Config (resolvePackageEntry, removePathFromPackageEntry)
6166
import Stack.Constants
6267
import Stack.Package
6368
import Stack.Types.Build
@@ -74,6 +79,21 @@ import qualified System.FilePath as FP
7479
-- | Special exception to throw when you want to fail because of bad results
7580
-- of package check.
7681

82+
data SDistOpts = SDistOpts
83+
{ sdoptsDirsToWorkWith :: [String]
84+
-- ^ Directories to package
85+
, sdoptsPvpBounds :: Maybe PvpBounds
86+
-- ^ PVP Bounds overrides
87+
, sdoptsIgnoreCheck :: Bool
88+
-- ^ Whether to ignore check of the package for common errors
89+
, sdoptsSign :: Bool
90+
-- ^ Whether to sign the package
91+
, sdoptsSignServerUrl :: String
92+
-- ^ The URL of the signature server
93+
, sdoptsBuildTarball :: Bool
94+
-- ^ Whether to build the tarball
95+
}
96+
7797
newtype CheckException
7898
= CheckException (NonEmpty Check.PackageCheck)
7999
deriving (Typeable)
@@ -317,13 +337,21 @@ dirsFromFiles dirs = Set.toAscList (Set.delete "." results)
317337
-- and will throw an exception in case of critical errors.
318338
--
319339
-- Note that we temporarily decompress the archive to analyze it.
320-
checkSDistTarball :: (StackM env m, HasEnvConfig env)
321-
=> Path Abs File -- ^ Absolute path to tarball
340+
checkSDistTarball :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m)
341+
=> SDistOpts -- ^ The configuration of what to check
342+
-> Path Abs File -- ^ Absolute path to tarball
322343
-> m ()
323-
checkSDistTarball tarball = withTempTarGzContents tarball $ \pkgDir' -> do
344+
checkSDistTarball opts tarball = withTempTarGzContents tarball $ \pkgDir' -> do
324345
pkgDir <- (pkgDir' </>) `liftM`
325346
(parseRelDir . FP.takeBaseName . FP.takeBaseName . toFilePath $ tarball)
326347
-- ^ drop ".tar" ^ drop ".gz"
348+
when (sdoptsBuildTarball opts) (buildExtractedTarball pkgDir)
349+
unless (sdoptsIgnoreCheck opts) (checkPackageInExtractedTarball pkgDir)
350+
351+
checkPackageInExtractedTarball :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m)
352+
=> Path Abs Dir -- ^ Absolute path to tarball
353+
-> m ()
354+
checkPackageInExtractedTarball pkgDir = do
327355
cabalfp <- findOrGenerateCabalFile pkgDir
328356
name <- parsePackageNameFromFilePath cabalfp
329357
config <- getDefaultPackageConfig
@@ -345,16 +373,56 @@ checkSDistTarball tarball = withTempTarGzContents tarball $ \pkgDir' -> do
345373
Nothing -> return ()
346374
Just ne -> throwM $ CheckException ne
347375

376+
buildExtractedTarball :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => Path Abs Dir -> m ()
377+
buildExtractedTarball pkgDir = do
378+
projectRoot <- view projectRootL
379+
envConfig <- view envConfigL
380+
menv <- getMinimalEnvOverride
381+
localPackageToBuild <- readLocalPackage pkgDir
382+
let packageEntries = bcPackageEntries (envConfigBuildConfig envConfig)
383+
getPaths entry = do
384+
resolvedEntry <- resolvePackageEntry menv projectRoot entry
385+
return $ fmap fst resolvedEntry
386+
allPackagePaths <- fmap mconcat (mapM getPaths packageEntries)
387+
-- We remove the path based on the name of the package
388+
let isPathToRemove path = do
389+
localPackage <- readLocalPackage path
390+
return $ packageName (lpPackage localPackage) == packageName (lpPackage localPackageToBuild)
391+
pathsToRemove <- filterM isPathToRemove allPackagePaths
392+
let adjustPackageEntries entries path = do
393+
adjustedPackageEntries <- mapM (removePathFromPackageEntry menv projectRoot path) entries
394+
return (catMaybes adjustedPackageEntries)
395+
entriesWithoutBuiltPackage <- foldM adjustPackageEntries packageEntries pathsToRemove
396+
let newEntry = PackageEntry Nothing (PLFilePath (toFilePath pkgDir)) []
397+
newPackagesRef <- liftIO (newIORef Nothing)
398+
let adjustEnvForBuild env =
399+
let updatedEnvConfig = envConfig
400+
{envConfigPackagesRef = newPackagesRef
401+
,envConfigBuildConfig = updatePackageInBuildConfig (envConfigBuildConfig envConfig)
402+
}
403+
in set envConfigL updatedEnvConfig env
404+
updatePackageInBuildConfig buildConfig = buildConfig
405+
{ bcPackageEntries = newEntry : entriesWithoutBuiltPackage
406+
, bcConfig = (bcConfig buildConfig)
407+
{ configBuild = defaultBuildOpts
408+
{ boptsTests = True
409+
}
410+
}
411+
}
412+
local adjustEnvForBuild $
413+
build (const (return ())) Nothing defaultBuildOptsCLI
414+
348415
-- | Version of 'checkSDistTarball' that first saves lazy bytestring to
349416
-- temporary directory and then calls 'checkSDistTarball' on it.
350-
checkSDistTarball' :: (StackM env m, HasEnvConfig env)
351-
=> String -- ^ Tarball name
417+
checkSDistTarball' :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m)
418+
=> SDistOpts
419+
-> String -- ^ Tarball name
352420
-> L.ByteString -- ^ Tarball contents as a byte string
353421
-> m ()
354-
checkSDistTarball' name bytes = withSystemTempDir "stack" $ \tpath -> do
422+
checkSDistTarball' opts name bytes = withSystemTempDir "stack" $ \tpath -> do
355423
npath <- (tpath </>) `liftM` parseRelFile name
356424
liftIO $ L.writeFile (toFilePath npath) bytes
357-
checkSDistTarball npath
425+
checkSDistTarball opts npath
358426

359427
withTempTarGzContents :: (MonadIO m, MonadMask m)
360428
=> Path Abs File -- ^ Location of tarball

src/main/Main.hs

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,19 @@ import Stack.Options.DotParser
8282
import Stack.Options.ExecParser
8383
import Stack.Options.GhciParser
8484
import Stack.Options.GlobalParser
85+
8586
import Stack.Options.HpcReportParser
8687
import Stack.Options.NewParser
8788
import Stack.Options.NixParser
8889
import Stack.Options.ScriptParser
90+
import Stack.Options.SDistParser
8991
import Stack.Options.SolverParser
9092
import Stack.Options.Utils
9193
import qualified Stack.PackageIndex
9294
import qualified Stack.Path
9395
import Stack.Runners
9496
import Stack.Script
95-
import Stack.SDist (getSDistTarball, checkSDistTarball, checkSDistTarball')
97+
import Stack.SDist (getSDistTarball, checkSDistTarball, checkSDistTarball', SDistOpts(..))
9698
import Stack.SetupCmd
9799
import qualified Stack.Sig as Sig
98100
import Stack.Solver (solveExtraDeps)
@@ -299,26 +301,12 @@ commandLineHandler currentDir progName isInterpreter = complicatedOptions
299301
"upload"
300302
"Upload a package to Hackage"
301303
uploadCmd
302-
((,,,,) <$> many (strArgument $ metavar "TARBALL/DIR" <> completer fileCompleter) <*>
303-
optional pvpBoundsOption <*>
304-
ignoreCheckSwitch <*>
305-
switch (long "no-signature" <> help "Do not sign & upload signatures") <*>
306-
strOption
307-
(long "sig-server" <> metavar "URL" <> showDefault <>
308-
value "https://sig.commercialhaskell.org" <>
309-
help "URL"))
304+
(sdistOptsParser True)
310305
addCommand'
311306
"sdist"
312307
"Create source distribution tarballs"
313308
sdistCmd
314-
((,,,,) <$> many (strArgument $ metavar "DIR" <> completer dirCompleter) <*>
315-
optional pvpBoundsOption <*>
316-
ignoreCheckSwitch <*>
317-
switch (long "sign" <> help "Sign & upload signatures") <*>
318-
strOption
319-
(long "sig-server" <> metavar "URL" <> showDefault <>
320-
value "https://sig.commercialhaskell.org" <>
321-
help "URL"))
309+
(sdistOptsParser False)
322310
addCommand' "dot"
323311
"Visualize your project's dependency graph using Graphviz dot"
324312
dotCmd
@@ -447,10 +435,6 @@ commandLineHandler currentDir progName isInterpreter = complicatedOptions
447435
hpcReportOptsParser)
448436
)
449437
where
450-
ignoreCheckSwitch =
451-
switch (long "ignore-check"
452-
<> help "Do not check package for common mistakes")
453-
454438
-- addCommand hiding global options
455439
addCommand' :: String -> String -> (a -> GlobalOpts -> IO ()) -> Parser a
456440
-> AddCommand
@@ -672,15 +656,15 @@ upgradeCmd upgradeOpts' go = withGlobalConfigAndLock go $
672656
upgradeOpts'
673657

674658
-- | Upload to Hackage
675-
uploadCmd :: ([String], Maybe PvpBounds, Bool, Bool, String) -> GlobalOpts -> IO ()
676-
uploadCmd ([], _, _, _, _) _ = throwString "Error: To upload the current package, please run 'stack upload .'"
677-
uploadCmd (args, mpvpBounds, ignoreCheck, don'tSign, sigServerUrl) go = do
659+
uploadCmd :: SDistOpts -> GlobalOpts -> IO ()
660+
uploadCmd (SDistOpts [] _ _ _ _ _) _ = throwString "Error: To upload the current package, please run 'stack upload .'"
661+
uploadCmd sdistOpts go = do
678662
let partitionM _ [] = return ([], [])
679663
partitionM f (x:xs) = do
680664
r <- f x
681665
(as, bs) <- partitionM f xs
682666
return $ if r then (x:as, bs) else (as, x:bs)
683-
(files, nonFiles) <- partitionM D.doesFileExist args
667+
(files, nonFiles) <- partitionM D.doesFileExist (sdoptsDirsToWorkWith sdistOpts)
684668
(dirs, invalid) <- partitionM D.doesDirectoryExist nonFiles
685669
unless (null invalid) $ do
686670
hPutStrLn stderr $
@@ -690,55 +674,54 @@ uploadCmd (args, mpvpBounds, ignoreCheck, don'tSign, sigServerUrl) go = do
690674
withBuildConfigAndLock go $ \_ -> do
691675
config <- view configL
692676
getCreds <- liftIO (runOnce (Upload.loadCreds config))
693-
unless ignoreCheck $
694-
mapM_ (resolveFile' >=> checkSDistTarball) files
677+
mapM_ (resolveFile' >=> checkSDistTarball sdistOpts) files
695678
forM_
696679
files
697680
(\file ->
698681
do tarFile <- resolveFile' file
699682
liftIO $ do
700683
creds <- getCreds
701684
Upload.upload creds (toFilePath tarFile)
702-
unless
703-
don'tSign
685+
when
686+
(sdoptsSign sdistOpts)
704687
(void $
705688
Sig.sign
706-
sigServerUrl
689+
(sdoptsSignServerUrl sdistOpts)
707690
tarFile))
708691
unless (null dirs) $
709692
forM_ dirs $ \dir -> do
710693
pkgDir <- resolveDir' dir
711-
(tarName, tarBytes, mcabalRevision) <- getSDistTarball mpvpBounds pkgDir
712-
unless ignoreCheck $ checkSDistTarball' tarName tarBytes
694+
(tarName, tarBytes, mcabalRevision) <- getSDistTarball (sdoptsPvpBounds sdistOpts) pkgDir
695+
checkSDistTarball' sdistOpts tarName tarBytes
713696
liftIO $ do
714697
creds <- getCreds
715698
Upload.uploadBytes creds tarName tarBytes
716699
forM_ mcabalRevision $ uncurry $ Upload.uploadRevision creds
717700
tarPath <- parseRelFile tarName
718-
unless
719-
don'tSign
701+
when
702+
(sdoptsSign sdistOpts)
720703
(void $
721704
Sig.signTarBytes
722-
sigServerUrl
705+
(sdoptsSignServerUrl sdistOpts)
723706
tarPath
724707
tarBytes)
725708

726-
sdistCmd :: ([String], Maybe PvpBounds, Bool, Bool, String) -> GlobalOpts -> IO ()
727-
sdistCmd (dirs, mpvpBounds, ignoreCheck, sign, sigServerUrl) go =
709+
sdistCmd :: SDistOpts -> GlobalOpts -> IO ()
710+
sdistCmd sdistOpts go =
728711
withBuildConfig go $ do -- No locking needed.
729712
-- If no directories are specified, build all sdist tarballs.
730-
dirs' <- if null dirs
713+
dirs' <- if null (sdoptsDirsToWorkWith sdistOpts)
731714
then liftM Map.keys getLocalPackages
732-
else mapM resolveDir' dirs
715+
else mapM resolveDir' (sdoptsDirsToWorkWith sdistOpts)
733716
forM_ dirs' $ \dir -> do
734-
(tarName, tarBytes, _mcabalRevision) <- getSDistTarball mpvpBounds dir
717+
(tarName, tarBytes, _mcabalRevision) <- getSDistTarball (sdoptsPvpBounds sdistOpts) dir
735718
distDir <- distDirFromDir dir
736719
tarPath <- (distDir </>) <$> parseRelFile tarName
737720
ensureDir (parent tarPath)
738721
liftIO $ L.writeFile (toFilePath tarPath) tarBytes
739-
unless ignoreCheck (checkSDistTarball tarPath)
722+
checkSDistTarball sdistOpts tarPath
740723
$logInfo $ "Wrote sdist tarball to " <> T.pack (toFilePath tarPath)
741-
when sign (void $ Sig.sign sigServerUrl tarPath)
724+
when (sdoptsSign sdistOpts) (void $ Sig.sign (sdoptsSignServerUrl sdistOpts) tarPath)
742725

743726
-- | Execute a command.
744727
execCmd :: ExecOpts -> GlobalOpts -> IO ()

stack.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ library
137137
Stack.Options.PackageParser
138138
Stack.Options.ResolverParser
139139
Stack.Options.ScriptParser
140+
Stack.Options.SDistParser
140141
Stack.Options.SolverParser
141142
Stack.Options.TestParser
142143
Stack.Options.Utils

0 commit comments

Comments
 (0)