我如何使用 Stack/Cabal 构建早期部分的程序输出作为同一构建后期部分的源代码?

Cac*_*tus 9 haskell build ffi cabal haskell-stack

我有一个非常特殊的依赖情况,我想将其打包到单个 Stack/Cabal 包中:我需要构建并运行我的程序以获取代码生成器的输入,该代码生成器生成需要链接到的输出。 .. 我的程序。

好的,更具体地说,这里是手动步骤:

  1. stack build 安装所有依赖项,并构建所有非 Verilator 使用的可执行文件。
  2. stack exec phase1运行第一个阶段,其中生成一个 Verilog 文件和一个 Clash.manifest文件。
  3. 我有一个自定义源代码生成器,它.manifest使用步骤 2 中的文件,并生成 C++ 代码和Makefile可用于驱动 Verilator 的代码。
  4. 运行Makefile步骤 3 中生成的:
    1. 它在第 2 步的 Verilog 源代码上运行 Verilator,生成更多的 C++ 源代码和一个新的 Makefile
    2. 然后它运行新生成的 second Makefile,它生成一个二进制库
  5. stack build --flag phase2构建第二个可执行文件。此可执行文件包含.hsc处理步骤 2 中生成的头文件的文件,并链接到步骤 4/2 中生成的 C++ 库。

我想自动化这个,这样我就可以运行,stack build而这一切都在幕后发生。我从哪里开始?!

为了说明整个过程,这里是一个独立的模型:

package.yaml

name: clashilator-model
version: 0
category: acme

dependencies:
  - base
  - directory

source-dirs:
  - src

flags:
  phase2:
    manual: True
    default: False

executables:
  phase1:
    main: phase1.hs

  phase2:
    main: phase2.hs
    when:
    - condition: flag(phase2)
      then:
        source-dirs:
          - src
          - _build/generated
        extra-libraries: stdc++ 
        extra-lib-dirs: _build/compiled
        ghc-options:
          -O3 -fPIC -pgml g++
          -optl-Wl,--allow-multiple-definition
          -optl-Wl,--whole-archive -optl-Wl,-Bstatic
          -optl-Wl,-L_build/compiled -optl-Wl,-lImpl
          -optl-Wl,-Bdynamic -optl-Wl,--no-whole-archive

        build-tools: hsc2hs
        include-dirs: _build/generated
      else:
        buildable: false    
Run Code Online (Sandbox Code Playgroud)

src/phase1.hs

import System.Directory

main :: IO ()
main = do
    createDirectoryIfMissing True "_build/generated"
    writeFile "_build/generated/Interface.hsc" hsc
    writeFile "_build/generated/Impl.h" h
    writeFile "_build/generated/Impl.c" c
    writeFile "_build/Makefile" makeFile

makeFile = unlines
    [ "compiled/libImpl.a: compiled/Impl.o"
    , "\trm -f $@"
    , "\tmkdir -p compiled"
    , "\tar rcsT $@ $^"
    , ""
    , "compiled/Impl.o: generated/Impl.c generated/Impl.h"
    , "\tmkdir -p compiled"
    , "\t$(COMPILE.c) $(OUTPUT_OPTION) $<"
    ]

hsc = unlines
    [ "module Interface where"
    , "import Foreign.Storable"
    , "import Foreign.Ptr"
    , ""
    , "data FOO = FOO Int deriving Show"
    , ""
    , "#include \"Impl.h\""
    , ""
    , "foreign import ccall unsafe \"bar\" bar :: Ptr FOO -> IO ()"
    , "instance Storable FOO where"
    , "  alignment _ = #alignment FOO"
    , "  sizeOf _ = #size FOO"
    , "  peek ptr = FOO <$> (#peek FOO, fd1) ptr"
    , "  poke ptr (FOO x) = (#poke FOO, fd1) ptr x"
    ]

h = unlines
   [ "#pragma once"
   , ""
   , "typedef struct{ int fd1; } FOO;"
   ]

c = unlines
   [ "#include \"Impl.h\""
   , "#include <stdio.h>"
   , ""
   , "void bar(FOO* arg)"
   , "{ printf(\"bar: %d\\n\", arg->fd1); }"
   ]
Run Code Online (Sandbox Code Playgroud)

src/phase2.hs

import Interface
import Foreign.Marshal.Utils

main :: IO ()
main = with (FOO 42) bar
Run Code Online (Sandbox Code Playgroud)

手动运行整个过程的脚本

stack build
stack run phase1
make -C _build
stack build --flag clashilator-model:phase2
stack exec phase2
Run Code Online (Sandbox Code Playgroud)

Cac*_*tus 0

牦牛是完全裸露的:我设法通过定制解决了它Setup.hs

  1. 在 中buildHook,我基本上做了所有phase1应该做的事情(而不是将其保留在phase1可执行文件中),将所有生成的文件放在参数下方的buildDir位置LocalBuildInfo。这些生成的文件是C++源文件和一个.hsc文件。

  2. 然后我make在正确的目录中运行,生成一些libFoo.a.

  3. 仍然在 中buildHook,现在有趣的部分开始:Executable编辑PackageDescription.

    我将hsc文件的位置添加到hsSourceDirs,并将模块本身添加到otherModules。由于hsc2hs需要访问生成的 C++ 标头,我还将正确的目录添加到includeDirs. 对于库本身,我通过将标志直接传递给链接器来添加extraLibDirs和编辑options以静态链接到。libFoo.a

  4. 所有这一切的结果是一组经过修改的Executables,我将其放回到 中PackageDescription,然后再将其传递给默认值buildHook。然后该程序运行hsc2hsghc编译和链接可执行phase2文件。

我已经在 Github 上放置了一个完整的示例项目。看看Setup.hsclashilator/src/Clash/Clashilator/Setup.hs看看它的实际效果;Executable特别是,这里是对 s的编辑PackageDescription

-- TODO: Should we also edit `Library` components?
buildVerilator :: LocalBuildInfo -> BuildFlags -> [FilePath] -> String -> IO (Executable -> Executable)
buildVerilator localInfo buildFlags srcDir mod = do
    let outDir = buildDir localInfo
    (verilogDir, manifest) <- clashToVerilog localInfo buildFlags srcDir mod

    let verilatorDir = "_verilator"
    Clashilator.generateFiles (".." </> verilogDir) (outDir </> verilatorDir) manifest

    -- TODO: bake in `pkg-config --cflags verilator`
    () <- cmd (Cwd (outDir </> verilatorDir)) "make"

    let incDir = outDir </> verilatorDir </> "src"
        libDir = outDir </> verilatorDir </> "obj"
        lib = "VerilatorFFI"

    let fixupOptions f (PerCompilerFlavor x y) = PerCompilerFlavor (f x) (f y)

        linkFlags =
            [ "-fPIC"
            , "-pgml", "g++"
            , "-optl-Wl,--whole-archive"
            , "-optl-Wl,-Bstatic"
            , "-optl-Wl,-l" <> lib
            , "-optl-Wl,-Bdynamic"
            , "-optl-Wl,--no-whole-archive"
            ]

        fixupExe = foldr (.) id $
            [ includeDirs %~ (incDir:)
            , extraLibDirs %~ (libDir:)
            , options %~ fixupOptions (linkFlags++)

            , hsSourceDirs %~ (incDir:)
            , otherModules %~ (fromString lib:)
            ]

    return fixupExe
Run Code Online (Sandbox Code Playgroud)