Skip to content

Commit 4914a58

Browse files
authored
Merge pull request #6451 from commercialhaskell/fix5974
Fix #5974 If new Cabal copy is available, use it
2 parents 447fa45 + a9b20d7 commit 4914a58

File tree

3 files changed

+77
-91
lines changed

3 files changed

+77
-91
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ Behavior changes:
3838
* If Stack's `--resolver` option is not specified, Stack's `unpack` command with
3939
a package name will seek to update the package index before seeking to
4040
download the most recent version of the package in the index.
41+
* If the version of Cabal (the library) provided with the specified GHC can copy
42+
specific components, Stack will copy only the components built and will not
43+
build all executable components at least once.
4144

4245
Other enhancements:
4346

doc/GUIDE.md

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -972,88 +972,44 @@ end up including the helloworld-test component as well.
972972

973973
You can bypass this implicit adding of components by being much more explicit,
974974
and stating the components directly. For example, the following will not build
975-
the `helloworld-exe` executable once all executables have been successfully
976-
built:
975+
the `helloworld-exe` executable:
977976

978977
~~~text
979-
stack clean
978+
stack purge
980979
stack build :helloworld-test
981-
Building all executables for `helloworld' once. After a successful build of all of them, only specified executables will be rebuilt.
982-
helloworld> configure (lib + exe + test)
980+
helloworld> configure (lib + test)
983981
Configuring helloworld-0.1.0.0...
984-
helloworld> build (lib + exe + test) with ghc-x.y.z
982+
helloworld> build (lib + test) with ghc-9.6.4
985983
Preprocessing library for helloworld-0.1.0.0..
986984
Building library for helloworld-0.1.0.0..
987985
[1 of 2] Compiling Lib
988986
[2 of 2] Compiling Paths_helloworld
989-
Preprocessing executable 'helloworld-exe' for helloworld-0.1.0.0..
990-
Building executable 'helloworld-exe' for helloworld-0.1.0.0..
991-
[1 of 2] Compiling Main
992-
[2 of 2] Compiling Paths_helloworld
993-
Linking .stack-work\dist\<hash>\build\helloworld-exe\helloworld-exe.exe ...
994987
Preprocessing test suite 'helloworld-test' for helloworld-0.1.0.0..
995988
Building test suite 'helloworld-test' for helloworld-0.1.0.0..
996989
[1 of 2] Compiling Main
997990
[2 of 2] Compiling Paths_helloworld
998-
Linking .stack-work\dist\<hash>\build\helloworld-test\helloworld-test.exe ...
991+
[3 of 3] Linking .stack-work\dist\<hash>\build\helloworld-test\helloworld-test.exe
999992
helloworld> copy/register
1000993
Installing library in ...\helloworld\.stack-work\install\...
1001-
Installing executable helloworld-exe in ...\helloworld\.stack-work\install\...\bin
1002994
Registering library for helloworld-0.1.0.0..
1003995
helloworld> test (suite: helloworld-test)
1004996
1005997
Test suite not yet implemented
1006998
1007-
helloworld> Test suite helloworld-test passed
1008-
Completed 2 action(s).
1009-
~~~
1010-
1011-
We first cleaned our project to clear old results so we know exactly what Stack
1012-
is trying to do. Note that it says it is building all executables for
1013-
`helloworld` once, and that after a successful build of all of them, only
1014-
specified executables will be rebuilt. If we change the source code of
1015-
`test/Spec.hs`, say to:
1016-
1017-
~~~haskell
1018-
main :: IO ()
1019-
main = putStrLn "Test suite still not yet implemented"
1020-
~~~
1021-
1022-
and command again:
1023-
1024-
~~~text
1025-
stack build :helloworld-test
1026-
helloworld-0.1.0.0: unregistering (local file changes: test\Spec.hs)
1027-
helloworld> build (lib + test) with ghc-x.y.z
1028-
Preprocessing library for helloworld-0.1.0.0..
1029-
Building library for helloworld-0.1.0.0..
1030-
Preprocessing test suite 'helloworld-test' for helloworld-0.1.0.0..
1031-
Building test suite 'helloworld-test' for helloworld-0.1.0.0..
1032-
[2 of 2] Compiling Main
1033-
Linking .stack-work\dist\<hash>\build\helloworld-test\helloworld-test.exe ...
1034-
helloworld> copy/register
1035-
Installing library in ...\helloworld\.stack-work\install\...
1036-
Installing executable helloworld-exe in ...\helloworld\.stack-work\install\...\bin
1037-
Registering library for helloworld-0.1.0.0..
1038-
helloworld> blocking for directory lock on ...\helloworld\.stack-work\dist\<hash>\build-lock
1039-
helloworld> test (suite: helloworld-test)
1040999
1041-
Test suite still not yet implemented
10421000
10431001
helloworld> Test suite helloworld-test passed
10441002
Completed 2 action(s).
10451003
~~~
10461004

1047-
Notice that this time it builds the `helloworld-test` test suite, and the
1048-
`helloworld` library (since it's used by the test suite), but it does not build
1049-
the `helloworld-exe` executable.
1005+
We first purged our project to clear old results so we know exactly what Stack
1006+
is trying to do.
10501007

1051-
And now the final point: in both cases, the last line shows that our command
1052-
also *runs* the test suite it just built. This may surprise some people who
1053-
would expect tests to only be run when using `stack test`, but this design
1054-
decision is what allows the `stack build` command to be as composable as it is
1055-
(as described previously). The same rule applies to benchmarks. To spell it out
1056-
completely:
1008+
The last line shows that our command also *runs* the test suite it just built.
1009+
This may surprise some people who would expect tests to only be run when using
1010+
`stack test`, but this design decision is what allows the `stack build` command
1011+
to be as composable as it is (as described previously). The same rule applies to
1012+
benchmarks. To spell it out completely:
10571013

10581014
* The `--test` and `--bench` flags simply state which components of a package
10591015
should be built, if no explicit set of components is given

src/Stack/Build/ExecutePackage.hs

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ import Stack.Types.ConfigureOpts
117117
import Stack.Types.Curator ( Curator (..) )
118118
import Stack.Types.DumpPackage ( DumpPackage (..) )
119119
import Stack.Types.EnvConfig
120-
( HasEnvConfig (..), actualCompilerVersionL
120+
( EnvConfig (..), HasEnvConfig (..), actualCompilerVersionL
121121
, appropriateGhcColorFlag
122122
)
123123
import Stack.Types.EnvSettings ( EnvSettings (..) )
@@ -362,6 +362,10 @@ singleBuild
362362
installedMap
363363
isFinalBuild
364364
= do
365+
cabalVersion <- view $ envConfigL . to (.compilerPaths.cabalVersion)
366+
-- The old version of Cabal (the library) copy did not allow the components
367+
-- to be copied to be specified.
368+
let isOldCabalCopy = cabalVersion < mkVersion [2, 0]
365369
(allDepsMap, cache) <-
366370
getConfigCache ee task installedMap enableTests enableBenchmarks
367371
let bcoSnapInstallRoot = ee.baseConfigOpts.snapInstallRoot
@@ -371,7 +375,7 @@ singleBuild
371375
Just precompiled -> copyPreCompiled ee task pkgId precompiled
372376
Nothing -> do
373377
curator <- view $ buildConfigL . to (.curator)
374-
realConfigAndBuild cache curator allDepsMap
378+
realConfigAndBuild isOldCabalCopy cache curator allDepsMap
375379
case minstalled of
376380
Nothing -> pure ()
377381
Just installed -> do
@@ -395,7 +399,7 @@ singleBuild
395399
enableTests = buildingFinals && any isCTest (taskComponents task)
396400
enableBenchmarks = buildingFinals && any isCBench (taskComponents task)
397401

398-
annSuffix executableBuildStatuses =
402+
annSuffix isOldCabalCopy executableBuildStatuses =
399403
if result == "" then "" else " (" <> result <> ")"
400404
where
401405
result = T.intercalate " + " $ concat
@@ -410,18 +414,18 @@ singleBuild
410414
let package = lp.package
411415
hasLibrary = hasBuildableMainLibrary package
412416
hasSubLibraries = not $ null package.subLibraries
413-
hasExecutables =
414-
not . Set.null $ exesToBuild executableBuildStatuses lp
417+
hasExecutables = not . Set.null $
418+
exesToBuild isOldCabalCopy executableBuildStatuses lp
415419
in (hasLibrary, hasSubLibraries, hasExecutables)
416420
-- This isn't true, but we don't want to have this info for upstream deps.
417421
_ -> (False, False, False)
418422

419-
realConfigAndBuild cache mcurator allDepsMap =
423+
realConfigAndBuild isOldCabalCopy cache mcurator allDepsMap =
420424
withSingleContext ac ee task.taskType allDepsMap Nothing $
421425
\package cabalFP pkgDir cabal0 announce _outputType -> do
422426
let cabal = cabal0 CloseOnException
423427
executableBuildStatuses <- getExecutableBuildStatuses package pkgDir
424-
when ( not (cabalIsSatisfied executableBuildStatuses)
428+
when ( not (cabalIsSatisfied isOldCabalCopy executableBuildStatuses)
425429
&& taskIsTarget task
426430
) $
427431
prettyInfoL
@@ -437,7 +441,7 @@ singleBuild
437441
ee.buildOpts
438442
( announce
439443
( "configure"
440-
<> display (annSuffix executableBuildStatuses)
444+
<> display (annSuffix isOldCabalCopy executableBuildStatuses)
441445
)
442446
)
443447
cabal
@@ -459,7 +463,7 @@ singleBuild
459463
-- https://github.com/commercialhaskell/stack/issues/2787
460464
(True, _) | null ac.downstream -> pure Nothing
461465
(_, True) | null ac.downstream || installedMapHasThisPkg -> do
462-
initialBuildSteps executableBuildStatuses cabal announce
466+
initialBuildSteps isOldCabalCopy executableBuildStatuses cabal announce
463467
pure Nothing
464468
_ -> fulfillCuratorBuildExpectations
465469
pname
@@ -468,25 +472,27 @@ singleBuild
468472
enableBenchmarks
469473
Nothing
470474
(Just <$>
471-
realBuild cache package pkgDir cabal0 announce executableBuildStatuses)
475+
realBuild isOldCabalCopy cache package pkgDir cabal0 announce executableBuildStatuses)
472476

473-
initialBuildSteps executableBuildStatuses cabal announce = do
477+
initialBuildSteps isOldCabalCopy executableBuildStatuses cabal announce = do
474478
announce
475479
( "initial-build-steps"
476-
<> display (annSuffix executableBuildStatuses)
480+
<> display (annSuffix isOldCabalCopy executableBuildStatuses)
477481
)
478482
cabal KeepTHLoading ["repl", "stack-initial-build-steps"]
479483

480484
realBuild ::
481-
ConfigCache
485+
Bool
486+
-- ^ Is Cabal copy limited to all libraries and executables?
487+
-> ConfigCache
482488
-> Package
483489
-> Path Abs Dir
484490
-> (KeepOutputOpen -> ExcludeTHLoading -> [String] -> RIO env ())
485491
-> (Utf8Builder -> RIO env ())
486492
-- ^ A plain 'announce' function
487493
-> Map Text ExecutableBuildStatus
488494
-> RIO env Installed
489-
realBuild cache package pkgDir cabal0 announce executableBuildStatuses = do
495+
realBuild isOldCabalCopy cache package pkgDir cabal0 announce executableBuildStatuses = do
490496
let cabal = cabal0 CloseOnException
491497
wc <- view $ actualCompilerVersionL . whichCompilerL
492498

@@ -542,7 +548,7 @@ singleBuild
542548
actualCompiler <- view actualCompilerVersionL
543549
() <- announce
544550
( "build"
545-
<> display (annSuffix executableBuildStatuses)
551+
<> display (annSuffix isOldCabalCopy executableBuildStatuses)
546552
<> " with "
547553
<> display actualCompiler
548554
)
@@ -551,16 +557,20 @@ singleBuild
551557
let stripTHLoading
552558
| config.hideTHLoading = ExcludeTHLoading
553559
| otherwise = KeepTHLoading
554-
cabal stripTHLoading (("build" :) $ (++ extraOpts) $
555-
case (task.taskType, task.allInOne, isFinalBuild) of
556-
(_, True, True) -> throwM AllInOneBuildBug
557-
(TTLocalMutable lp, False, False) ->
558-
primaryComponentOptions executableBuildStatuses lp
559-
(TTLocalMutable lp, False, True) -> finalComponentOptions lp
560-
(TTLocalMutable lp, True, False) ->
561-
primaryComponentOptions executableBuildStatuses lp
562-
++ finalComponentOptions lp
563-
(TTRemotePackage{}, _, _) -> [])
560+
(buildOpts, copyOpts) <-
561+
case (task.taskType, task.allInOne, isFinalBuild) of
562+
(_, True, True) -> throwM AllInOneBuildBug
563+
(TTLocalMutable lp, False, False) ->
564+
let componentOpts =
565+
primaryComponentOptions isOldCabalCopy executableBuildStatuses lp
566+
in pure (componentOpts, componentOpts)
567+
(TTLocalMutable lp, False, True) -> pure (finalComponentOptions lp, [])
568+
(TTLocalMutable lp, True, False) ->
569+
let componentOpts =
570+
primaryComponentOptions isOldCabalCopy executableBuildStatuses lp
571+
in pure (componentOpts <> finalComponentOptions lp, componentOpts)
572+
(TTRemotePackage{}, _, _) -> pure ([], [])
573+
cabal stripTHLoading ("build" : buildOpts <> extraOpts)
564574
`catch` \ex -> case ex of
565575
CabalExitedUnsuccessfully{} ->
566576
postBuildCheck False >> prettyThrowM ex
@@ -613,7 +623,8 @@ singleBuild
613623
&& (hasLibrary || hasSubLibraries || hasExecutables)
614624
when shouldCopy $ withMVar ee.installLock $ \() -> do
615625
announce "copy/register"
616-
eres <- try $ cabal KeepTHLoading ["copy"]
626+
let copyArgs = "copy" : if isOldCabalCopy then [] else copyOpts
627+
eres <- try $ cabal KeepTHLoading copyArgs
617628
case eres of
618629
Left err@CabalExitedUnsuccessfully{} ->
619630
throwM $ CabalCopyFailed
@@ -1249,10 +1260,12 @@ extraBuildOptions wc bopts = do
12491260

12501261
-- Library, sub-library, foreign library and executable build components.
12511262
primaryComponentOptions ::
1252-
Map Text ExecutableBuildStatus
1263+
Bool
1264+
-- ^ Is Cabal copy limited to all libraries and executables?
1265+
-> Map Text ExecutableBuildStatus
12531266
-> LocalPackage
12541267
-> [String]
1255-
primaryComponentOptions executableBuildStatuses lp =
1268+
primaryComponentOptions isOldCabalCopy executableBuildStatuses lp =
12561269
-- TODO: get this information from target parsing instead, which will allow
12571270
-- users to turn off library building if desired
12581271
( if hasBuildableMainLibrary package
@@ -1268,7 +1281,7 @@ primaryComponentOptions executableBuildStatuses lp =
12681281
(getBuildableListText package.subLibraries)
12691282
++ map
12701283
(T.unpack . T.append "exe:")
1271-
(Set.toList $ exesToBuild executableBuildStatuses lp)
1284+
(Set.toList $ exesToBuild isOldCabalCopy executableBuildStatuses lp)
12721285
where
12731286
package = lp.package
12741287

@@ -1284,15 +1297,29 @@ primaryComponentOptions executableBuildStatuses lp =
12841297
-- behavior below that we build all executables once (modulo success), and
12851298
-- thereafter pay attention to user-wanted components.
12861299
--
1287-
exesToBuild :: Map Text ExecutableBuildStatus -> LocalPackage -> Set Text
1288-
exesToBuild executableBuildStatuses lp =
1289-
if cabalIsSatisfied executableBuildStatuses && lp.wanted
1300+
-- * The Cabal bug was fixed, in that the copy command of later Cabal versions
1301+
-- allowed components to be specified. Consequently, Cabal may be satisified,
1302+
-- even if all of a package's executables have not yet been built.
1303+
exesToBuild ::
1304+
Bool
1305+
-- ^ Is Cabal copy limited to all libraries and executables?
1306+
-> Map Text ExecutableBuildStatus
1307+
-> LocalPackage
1308+
-> Set Text
1309+
exesToBuild isOldCabalCopy executableBuildStatuses lp =
1310+
if cabalIsSatisfied isOldCabalCopy executableBuildStatuses && lp.wanted
12901311
then exeComponents lp.components
12911312
else buildableExes lp.package
12921313

1293-
-- | Do the current executables satisfy Cabal's bugged out requirements?
1294-
cabalIsSatisfied :: Map k ExecutableBuildStatus -> Bool
1295-
cabalIsSatisfied = all (== ExecutableBuilt) . Map.elems
1314+
-- | Do the current executables satisfy Cabal's requirements?
1315+
cabalIsSatisfied ::
1316+
Bool
1317+
-- ^ Is Cabal copy limited to all libraries and executables?
1318+
-> Map k ExecutableBuildStatus
1319+
-> Bool
1320+
cabalIsSatisfied False _ = True
1321+
cabalIsSatisfied True executableBuildStatuses =
1322+
all (== ExecutableBuilt) $ Map.elems executableBuildStatuses
12961323

12971324
-- Test-suite and benchmark build components.
12981325
finalComponentOptions :: LocalPackage -> [String]

0 commit comments

Comments
 (0)