Skip to content

Commit 0077a69

Browse files
authored
Merge pull request #7997 from bacchanalia/5698
Add support for cabal.project fields to scripts
2 parents 632571c + f94ff6f commit 0077a69

File tree

7 files changed

+98
-35
lines changed

7 files changed

+98
-35
lines changed

cabal-install/src/Distribution/Client/ProjectConfig.hs

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ module Distribution.Client.ProjectConfig (
2929
readGlobalConfig,
3030
readProjectLocalExtraConfig,
3131
readProjectLocalFreezeConfig,
32+
parseProjectConfig,
33+
reportParseResult,
3234
showProjectConfig,
3335
withProjectOrGlobalConfig,
3436
writeProjectLocalExtraConfig,

cabal-install/src/Distribution/Client/ScriptUtils.hs

+48-24
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import Distribution.Client.HashValue
3030
import Distribution.Client.NixStyleOptions
3131
( NixStyleFlags (..) )
3232
import Distribution.Client.ProjectConfig
33-
( ProjectConfig(..), ProjectConfigShared(..), withProjectOrGlobalConfig )
33+
( ProjectConfig(..), ProjectConfigShared(..)
34+
, parseProjectConfig, reportParseResult, withProjectOrGlobalConfig )
3435
import Distribution.Client.ProjectFlags
3536
( flagIgnoreProject )
3637
import Distribution.Client.Setup
@@ -46,7 +47,7 @@ import Distribution.Fields
4647
import Distribution.PackageDescription.FieldGrammar
4748
( executableFieldGrammar )
4849
import Distribution.PackageDescription.PrettyPrint
49-
( showGenericPackageDescription, writeGenericPackageDescription )
50+
( showGenericPackageDescription )
5051
import Distribution.Parsec
5152
( Position(..) )
5253
import Distribution.Simple.Flag
@@ -56,7 +57,7 @@ import Distribution.Simple.PackageDescription
5657
import Distribution.Simple.Setup
5758
( Flag(..) )
5859
import Distribution.Simple.Utils
59-
( createDirectoryIfMissingVerbose, createTempDirectory, die', handleDoesNotExist, readUTF8File, warn )
60+
( createDirectoryIfMissingVerbose, createTempDirectory, die', handleDoesNotExist, readUTF8File, warn, writeUTF8File )
6061
import qualified Distribution.SPDX.License as SPDX
6162
import Distribution.Solver.Types.SourcePackage as SP
6263
( SourcePackage(..) )
@@ -214,10 +215,13 @@ withContextAndSelectors noTargets kind flags@NixStyleFlags {..} targetStrings gl
214215
let projectRoot = distProjectRootDirectory $ distDirLayout ctx
215216
writeFile (projectRoot </> "scriptlocation") =<< canonicalizePath script
216217

217-
executable <- readScriptBlockFromScript verbosity =<< BS.readFile script
218+
scriptContents <- BS.readFile script
219+
executable <- readExecutableBlockFromScript verbosity scriptContents
220+
projectCfg <- readProjectBlockFromScript verbosity (takeFileName script) scriptContents
218221

219222
let executable' = executable & L.buildInfo . L.defaultLanguage %~ maybe (Just Haskell2010) Just
220-
return (ScriptContext script executable', ctx, defaultTarget)
223+
ctx' = ctx & lProjectConfig %~ (<> projectCfg)
224+
return (ScriptContext script executable', ctx', defaultTarget)
221225
else reportTargetSelectorProblems verbosity err
222226

223227
withTemporaryTempDirectory :: (IO FilePath -> IO a) -> IO a
@@ -236,17 +240,18 @@ withTemporaryTempDirectory act = newEmptyMVar >>= \m -> bracket (getMkTmp m) (rm
236240
-- | Add the 'SourcePackage' to the context and use it to write a .cabal file.
237241
updateContextAndWriteProjectFile' :: ProjectBaseContext -> SourcePackage (PackageLocation (Maybe FilePath)) -> IO ProjectBaseContext
238242
updateContextAndWriteProjectFile' ctx srcPkg = do
239-
let projectRoot = distProjectRootDirectory $ distDirLayout ctx
240-
projectFile = projectRoot </> fakePackageCabalFileName
241-
writeProjectFile = writeGenericPackageDescription (projectRoot </> fakePackageCabalFileName) (srcpkgDescription srcPkg)
242-
projectFileExists <- doesFileExist projectFile
243+
let projectRoot = distProjectRootDirectory $ distDirLayout ctx
244+
packageFile = projectRoot </> fakePackageCabalFileName
245+
contents = showGenericPackageDescription (srcpkgDescription srcPkg)
246+
writePackageFile = writeUTF8File packageFile contents
243247
-- TODO This is here to prevent reconfiguration of cached repl packages.
244248
-- It's worth investigating why it's needed in the first place.
245-
if projectFileExists then do
246-
contents <- force <$> readUTF8File projectFile
247-
when (contents /= showGenericPackageDescription (srcpkgDescription srcPkg))
248-
writeProjectFile
249-
else writeProjectFile
249+
packageFileExists <- doesFileExist packageFile
250+
if packageFileExists then do
251+
cached <- force <$> readUTF8File packageFile
252+
when (cached /= contents)
253+
writePackageFile
254+
else writePackageFile
250255
return (ctx & lLocalPackages %~ (++ [SpecificSourcePackage srcPkg]))
251256

252257
-- | Add add the executable metadata to the context and write a .cabal file.
@@ -283,26 +288,41 @@ parseScriptBlock str =
283288
readScriptBlock :: Verbosity -> BS.ByteString -> IO Executable
284289
readScriptBlock verbosity = parseString parseScriptBlock verbosity "script block"
285290

286-
-- | Extract the first encountered script metadata block started end
287-
-- terminated by the bellow tokens or die.
291+
-- | Extract the first encountered executable metadata block started and
292+
-- terminated by the below tokens or die.
288293
--
289294
-- * @{- cabal:@
290295
--
291296
-- * @-}@
292297
--
293298
-- Return the metadata.
294-
readScriptBlockFromScript :: Verbosity -> BS.ByteString -> IO Executable
295-
readScriptBlockFromScript verbosity str = do
296-
str' <- case extractScriptBlock str of
299+
readExecutableBlockFromScript :: Verbosity -> BS.ByteString -> IO Executable
300+
readExecutableBlockFromScript verbosity str = do
301+
str' <- case extractScriptBlock "cabal" str of
297302
Left e -> die' verbosity $ "Failed extracting script block: " ++ e
298303
Right x -> return x
299304
when (BS.all isSpace str') $ warn verbosity "Empty script block"
300305
readScriptBlock verbosity str'
301306

307+
-- | Extract the first encountered project metadata block started and
308+
-- terminated by the below tokens.
309+
--
310+
-- * @{- project:@
311+
--
312+
-- * @-}@
313+
--
314+
-- Return the metadata.
315+
readProjectBlockFromScript :: Verbosity -> String -> BS.ByteString -> IO ProjectConfig
316+
readProjectBlockFromScript verbosity scriptName str = do
317+
case extractScriptBlock "project" str of
318+
Left _ -> return mempty
319+
Right x -> reportParseResult verbosity "script" scriptName
320+
$ parseProjectConfig scriptName x
321+
302322
-- | Extract the first encountered script metadata block started end
303323
-- terminated by the tokens
304324
--
305-
-- * @{- cabal:@
325+
-- * @{- <header>:@
306326
--
307327
-- * @-}@
308328
--
@@ -311,8 +331,8 @@ readScriptBlockFromScript verbosity str = do
311331
--
312332
-- In case of missing or unterminated blocks a 'Left'-error is
313333
-- returned.
314-
extractScriptBlock :: BS.ByteString -> Either String BS.ByteString
315-
extractScriptBlock str = goPre (BS.lines str)
334+
extractScriptBlock :: BS.ByteString -> BS.ByteString -> Either String BS.ByteString
335+
extractScriptBlock header str = goPre (BS.lines str)
316336
where
317337
isStartMarker = (== startMarker) . stripTrailSpace
318338
isEndMarker = (== endMarker) . stripTrailSpace
@@ -330,8 +350,8 @@ extractScriptBlock str = goPre (BS.lines str)
330350
| otherwise = goBody (l:acc) ls
331351

332352
startMarker, endMarker :: BS.ByteString
333-
startMarker = fromString "{- cabal:"
334-
endMarker = fromString "-}"
353+
startMarker = "{- " <> header <> ":"
354+
endMarker = "-}"
335355

336356
-- | The base for making a 'SourcePackage' for a fake project.
337357
-- It needs a 'Distribution.Types.Library.Library' or 'Executable' depending on the command.
@@ -362,6 +382,10 @@ lLocalPackages :: Lens' ProjectBaseContext [PackageSpecifier UnresolvedSourcePac
362382
lLocalPackages f s = fmap (\x -> s { localPackages = x }) (f (localPackages s))
363383
{-# inline lLocalPackages #-}
364384

385+
lProjectConfig :: Lens' ProjectBaseContext ProjectConfig
386+
lProjectConfig f s = fmap (\x -> s { projectConfig = x }) (f (projectConfig s))
387+
{-# inline lProjectConfig #-}
388+
365389
-- Character classes
366390
-- Transcribed from "templates/Lexer.x"
367391
ccSpace, ccCtrlchar, ccPrintable, ccSymbol', ccParen, ccNamecore :: Set Char
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# cabal v2-run
2+
Resolving dependencies...
3+
Build profile: -w ghc-<GHCVER> -O2
4+
In order, the following will be built:
5+
- fake-package-0 (exe:cabal-script-s.hs) (first run)
6+
Configuring executable 'cabal-script-s.hs' for fake-package-0..
7+
Building executable 'cabal-script-s.hs' for fake-package-0..
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Test.Cabal.Prelude
2+
3+
main = cabalTest $ do
4+
-- script is called "s.hs" to avoid Windows long path issue in CI
5+
res <- cabal' "v2-run" ["s.hs"]
6+
assertOutputContains "Hello World" res
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env cabal
2+
{- cabal:
3+
build-depends: base
4+
-}
5+
{- project:
6+
optimization: 2
7+
-}
8+
9+
main :: IO ()
10+
main = putStrLn "Hello World"

changelog.d/pr-7851

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
synopsis: Better support for scripts
22
packages: cabal-install
3-
prs: #7851 #7925 #7938 #7990
4-
issues: #7842 #7073 #6354 #6149 #5508
3+
prs: #7851 #7925 #7938 #7990 #7997
4+
issues: #7842 #7073 #6354 #6149 #5508 #5698
55

66
description: {
77

@@ -15,5 +15,7 @@ description: {
1515
- The name of the generated script executable has been changed from "script" to
1616
"cabal-script-<your-sanitized-script-name>" for easier process management.
1717
- Reduce the default verbosity of scripts, so that the build output doesn't interfere with the script output.
18+
- Scripts now support a project metadata block that allows them to use options
19+
that would normally be set in a cabal.project file.
1820

1921
}

doc/cabal-commands.rst

+21-9
Original file line numberDiff line numberDiff line change
@@ -436,13 +436,15 @@ See `the v2-build section <#cabal-v2-build>`__ for the target syntax.
436436

437437
When ``TARGET`` is one of the following:
438438

439-
- A component target: execute the specified executable, benchmark or test suite
439+
- A component target: execute the specified executable, benchmark or test suite.
440440

441441
- A package target:
442442
1. If the package has exactly one executable component, it will be selected.
443443
2. If the package has multiple executable components, an error is raised.
444444
3. If the package has exactly one test or benchmark component, it will be selected.
445-
4. Otherwise an issue is raised
445+
4. Otherwise an issue is raised.
446+
447+
- The path to a script: execute the script at the path.
446448

447449
- Empty target: Same as package target, implicitly using the package from the current
448450
working directory.
@@ -458,28 +460,38 @@ have to separate them with ``--``.
458460

459461
$ cabal v2-run target -- -a -bcd --argument
460462

461-
``v2-run`` also supports running script files that use a certain format. With
462-
a script that looks like:
463+
``v2-run`` supports running script files that use a certain format.
464+
Scripts look like:
463465

464466
::
465467

466468
#!/usr/bin/env cabal
467469
{- cabal:
468-
build-depends: base ^>= 4.11
469-
, shelly ^>= 1.8.1
470+
build-depends: base ^>= 4.14
471+
, shelly ^>= 1.10
472+
-}
473+
{- project:
474+
with-compiler: ghc-8.10.7
470475
-}
471476

472477
main :: IO ()
473478
main = do
474479
...
475480

476-
It can either be executed like any other script, using ``cabal`` as an
477-
interpreter, or through this command:
481+
Where there cabal metadata block is mandatory and contains fields from a
482+
package executable block, and the project metadata block is optional and
483+
contains fields that would be in the cabal.project file in a regular project.
484+
485+
Only some fields are supported in the metadata blocks, and these fields are
486+
currently not validated. See
487+
`#8024 <https://github.com/haskell/cabal/issues/8024>`__ for details.
488+
489+
A script can either be executed directly using `cabal` as an interpreter or
490+
with the command:
478491

479492
::
480493

481494
$ cabal v2-run path/to/script
482-
$ cabal v2-run path/to/script -- --arg1 # args are passed like this
483495

484496
The executable is cached under the cabal directory, and can be pre-built with
485497
``cabal v2-build path/to/script`` and the cache can be removed with

0 commit comments

Comments
 (0)