运行 在构建组件之前自定义每个组件的 `IO ()`,与实际构建交错

Running a custom per-component `IO ()` before building components, interleaved with actual building

我有一个每个组件的自定义操作,我想在构建给定组件之前 运行:

justBeforeBuilding :: LocalBuildInfo -> BuildFlags -> Component -> IO BuildInfo

因为该操作可能需要已经构建的依赖项,例如,如果给定的 Cabal 包既有库又有使用该库的可执行文件,那么每个组件的 justBeforeBuilding 是 运行 仅在构建实际组件之前,但在构建其所有依赖项之后。

所以这就是我尝试的方法:我没有调用一次默认构建挂钩,而是按依赖顺序一个一个地检查所有启用的组件,调用我的 justBeforeBuilding 函数,然后偷偷更改 buildArgs 这样默认的构建挂钩只会构建单个当前组件:

restrictBuildFlags :: PackageDescription -> Component -> BuildFlags -> BuildFlags
restrictBuildFlags pkg c buildFlags = buildFlags
    { buildArgs = selectedArgs
    }
  where
    selectedArgs = [showBuildTarget (packageId pkg) $ BuildTargetComponent $ componentName c]

type BuildHook = PackageDescription -> LocalBuildInfo -> UserHooks -> BuildFlags -> IO ()

myBuildHook :: BuildHook -> BuildHook
myBuildHook nextBuildHook pkg lbi userHooks flags = do
    let reqSpec = componentEnabledSpec lbi
    withAllComponentsInBuildOrder pkg lbi $ \c clbi -> do
        flags <- return $ restrictBuildFlags pkg c flags
        when (componentEnabled reqSpec c && not (null $ buildArgs flags)) $ do
            justBeforeBuilding lbi flags c
            nextBuildHook pkg lbi userHooks flags

main :: IO ()
main = defaultMainWithHooks simpleUserHooks
    { buildHook = myBuildHook $ buildHook simpleUserHooks
    }

问题是当 justBeforeBuilding 运行s 时,它无法访问库依赖项 即使它们已经在那时构建 。为了演示,这里有一个打印 Cabal 提供的包 DBs 内容的函数:

justBeforeBuilding :: LocalBuildInfo -> BuildFlags -> Component -> IO ()
justBeforeBuilding lbi flags c = do
    pkgdbs <- absolutePackageDBPaths $ withPackageDB lbi
    let dbpaths = nub . sort $ [ path | SpecificPackageDB path <- pkgdbs ]
        dbflags = concat [ ["-package-db", path] | path <- dbpaths ]

    putStrLn $ "!!! Processing component " <> show (componentName c)
    putStrLn "!!! At this point, the package DB paths are:"
    forM_ dbpaths $ \dir -> do
        putStrLn dir
        files <- listDirectory dir
        mapM_ (printf "    %s\n") files

为了测试,我使用了一个包含一个库和一个 exe 的 Cabal 包; HPack 格式:

name: cabal-component-hook
version: 0.1.0

custom-setup:
  dependencies:
    - base
    - Cabal
    - directory

dependencies:
  - base >= 4.7 && < 5

library:
  source-dirs: lib
  exposed-modules:
    - Foo
    
executables:
  bar:
    source-dirs: app
    main: bar.hs
    dependencies:
      - cabal-component-hook

如果我然后做 stack build,首先,库被构建,当然包 DB 还不包含库:

!!! Processing component CLibName LMainLibName
!!! At this point, the package DB paths are:
/home/cactus/prog/clash/bugs/cabal-component-hook/.stack-work/install/x86_64-linux-tinfo6/82781a0829e0e0da301c1db4825858dea8980a6a982b679eb178870c6c0ec1ee/8.10.4/pkgdb
    package.cache.lock
    package.cache
/home/cactus/sdk/stack/snapshots/x86_64-linux-tinfo6/82781a0829e0e0da301c1db4825858dea8980a6a982b679eb178870c6c0ec1ee/8.10.4/pkgdb
    package.cache.lock
    StateVar-1.2.1-7vo3tV8mPssJqOV48TU4OF.conf
    call-stack-0.2.0-4YzMVPI02PxAK0TxyBY7Iv.conf
    cabal-doctest-1.0.8-I4vvWDvrsGA6v5uZj1lZJi.conf
    base-orphans-0.8.4-JVJ8ttw51H7Dc3tJYgx6uK.conf
    parallel-3.2.2.0-HTf2o2horULDwL7cXUB9uv.conf
    hashable-1.3.0.0-K3FFBNAmsvaIvBv4Qg15rQ.conf
    semigroups-0.19.1-CdJZL8lQRXTEuOuxFhFNIa.conf
    contravariant-1.5.3-A4NbUFeaP6W4UzkL1ejoXa.conf
    indexed-traversable-0.1.1-Dw8MIJyvCj8IZQDXOa8TBp.conf
    tagged-0.8.6.1-A3E8I7zg2qBFE9O5vQsg6C.conf
    void-0.7.3-FJLCa6y31Qt20kSi8TCcBC.conf
    transformers-compat-0.6.6-CZr4aajtyBTi4Enjszw8H.conf
    reflection-2.1.6-CzOlI803nFuvt8AikdOut.conf
    distributive-0.6.2.1-Lhog8B4NdHk7JJdshqpkhJ.conf
    transformers-base-0.4.5.2-2JwY8UMK4YFHH9faaNEDAI.conf
    th-abstraction-0.4.2.0-CPMLTlyMgmr6dbHxHL95CG.conf
    comonad-5.0.8-EA0Scey7jOW6LX5RvNTIb8.conf
    primitive-0.7.1.0-Jxsyd70oUttYiCXCa0HqV.conf
    unordered-containers-0.2.13.0-3awuPgUx2yvAACRZkw6am3.conf
    bifunctors-5.5.10-1Xyw3zBBKdPGoolSEEYrSo.conf
    profunctors-5.5.2-Jd7sxJvE4zaBkftBvoi6oJ.conf
    semigroupoids-5.3.5-A5MCqcbuwFnHzZu6aqZxm4.conf
    invariant-0.5.4-Ca6182XTMBJ4627vLKNFdU.conf
    free-5.1.5-JCTHYv08sV0j7gsEXshfc.conf
    adjunctions-4.4-4Q0IXuLBVoCBKmI2ZpS7bE.conf
    kan-extensions-5.2.2-Z55rpCSAQY7rC9ino1jlr.conf
    vector-0.12.1.2-6jlbObSa8iuJfxUVGBQC5r.conf
    lens-4.19.2-86eTsWPqcVQ3qs5KiS7cYu.conf
    package.cache
Preprocessing library for cabal-component-hook-0.1.0..
Building library for cabal-component-hook-0.1.0..
[1 of 2] Compiling Foo
[2 of 2] Compiling Paths_cabal_component_hook

但是,然后构建可执行文件,并且包 DB 仍然不包含刚刚构建的库:

!!! Processing component CExeName (UnqualComponentName "bar")
!!! At this point, the package DB paths are:
/home/cactus/prog/clash/bugs/cabal-component-hook/.stack-work/install/x86_64-linux-tinfo6/82781a0829e0e0da301c1db4825858dea8980a6a982b679eb178870c6c0ec1ee/8.10.4/pkgdb
    package.cache.lock
    package.cache
/home/cactus/sdk/stack/snapshots/x86_64-linux-tinfo6/82781a0829e0e0da301c1db4825858dea8980a6a982b679eb178870c6c0ec1ee/8.10.4/pkgdb
    package.cache.lock
    StateVar-1.2.1-7vo3tV8mPssJqOV48TU4OF.conf
    call-stack-0.2.0-4YzMVPI02PxAK0TxyBY7Iv.conf
    cabal-doctest-1.0.8-I4vvWDvrsGA6v5uZj1lZJi.conf
    base-orphans-0.8.4-JVJ8ttw51H7Dc3tJYgx6uK.conf
    parallel-3.2.2.0-HTf2o2horULDwL7cXUB9uv.conf
    hashable-1.3.0.0-K3FFBNAmsvaIvBv4Qg15rQ.conf
    semigroups-0.19.1-CdJZL8lQRXTEuOuxFhFNIa.conf
    contravariant-1.5.3-A4NbUFeaP6W4UzkL1ejoXa.conf
    indexed-traversable-0.1.1-Dw8MIJyvCj8IZQDXOa8TBp.conf
    tagged-0.8.6.1-A3E8I7zg2qBFE9O5vQsg6C.conf
    void-0.7.3-FJLCa6y31Qt20kSi8TCcBC.conf
    transformers-compat-0.6.6-CZr4aajtyBTi4Enjszw8H.conf
    reflection-2.1.6-CzOlI803nFuvt8AikdOut.conf
    distributive-0.6.2.1-Lhog8B4NdHk7JJdshqpkhJ.conf
    transformers-base-0.4.5.2-2JwY8UMK4YFHH9faaNEDAI.conf
    th-abstraction-0.4.2.0-CPMLTlyMgmr6dbHxHL95CG.conf
    comonad-5.0.8-EA0Scey7jOW6LX5RvNTIb8.conf
    primitive-0.7.1.0-Jxsyd70oUttYiCXCa0HqV.conf
    unordered-containers-0.2.13.0-3awuPgUx2yvAACRZkw6am3.conf
    bifunctors-5.5.10-1Xyw3zBBKdPGoolSEEYrSo.conf
    profunctors-5.5.2-Jd7sxJvE4zaBkftBvoi6oJ.conf
    semigroupoids-5.3.5-A5MCqcbuwFnHzZu6aqZxm4.conf
    invariant-0.5.4-Ca6182XTMBJ4627vLKNFdU.conf
    free-5.1.5-JCTHYv08sV0j7gsEXshfc.conf
    adjunctions-4.4-4Q0IXuLBVoCBKmI2ZpS7bE.conf
    kan-extensions-5.2.2-Z55rpCSAQY7rC9ino1jlr.conf
    vector-0.12.1.2-6jlbObSa8iuJfxUVGBQC5r.conf
    lens-4.19.2-86eTsWPqcVQ3qs5KiS7cYu.conf
    package.cache
Preprocessing library for cabal-component-hook-0.1.0..
Building library for cabal-component-hook-0.1.0..
Preprocessing executable 'bar' for cabal-component-hook-0.1.0..
Building executable 'bar' for cabal-component-hook-0.1.0..
[1 of 2] Compiling Main
[2 of 2] Compiling Paths_cabal_component_hook
Linking .stack-work/dist/x86_64-linux-tinfo6/Cabal-3.2.1.0/build/bar/bar ...

在整个过程结束时,虽然包 DB 目录已正确填充:

/home/cactus/prog/clash/bugs/cabal-component-hook/.stack-work/install/x86_64-linux-tinfo6/82781a0829e0e0da301c1db4825858dea8980a6a982b679eb178870c6c0ec1ee/8.10.4/pkgdb
├── cabal-component-hook-0.1.0-8kPvy0LMfqKAbvXmH5zWaP.conf
├── package.cache
└── package.cache.lock

所以我的问题是,如何在构建每个单独的组件之前安排 justBeforeBuilding 到 运行,当它的依赖关系已经 完全 已处理,即库依赖项被复制到构建内部包 DB?

事实证明我走对了,除了我还需要在包 DB 列表中包含所谓的 内部包 DBDistribution.Simple.Build 导出一个名为 createInternalPackageDB 的函数,不幸的是,我们不能使用它(因为它删除并重新初始化内部包 DB,实际上删除了以前组件的构建输出);但我们可以复制它的行为:

justBeforeBuilding :: LocalBuildInfo -> BuildFlags -> Component -> IO ()
justBeforeBuilding lbi flags c = do
    pkgdb0 <- do
        let dbPath = internalPackageDBPath lbi distPref
        existsAlready <- doesPackageDBExist dbPath
        unless existsAlready $ do
            createPackageDB verbosity (compiler lbi) (withPrograms lbi) False dbPath
        return $ SpecificPackageDB dbPath
    pkgdbs <- absolutePackageDBPaths $ withPackageDB lbi

    let dbpaths = nub . sort $ [ path | SpecificPackageDB path <- pkgdb0:pkgdbs ]
    ... -- Here we can proceed as before and things work out
  where
    verbosity = fromFlagOrDefault normal (buildVerbosity flags)
    distPref  = fromFlag (buildDistPref flags)