如何将 Stack/Cabal 构建的较早部分的程序输出用作同一构建的较晚部分的源?
How do I use the output of a program from an earlier part of a Stack/Cabal build as source in a later part of the same build?
我有一个非常特殊的依赖情况,我想将其打包成一个 Stack/Cabal 包:我需要构建 运行 我的程序以获取 [=64] 的输入=] 产生需要链接到...我的程序的输出。
好的,更具体地说,这里是手动步骤:
stack build
安装所有依赖项,并构建所有 non-Verilator-using 可执行文件。
stack exec phase1
到 运行 第一阶段生成一个 Verilog 文件和一个 Clash .manifest
文件。
- 我有一个自定义源代码生成器,它使用步骤 2 中的
.manifest
文件,并生成 C++ 代码和一个可用于驱动 Verilator 的 Makefile
。
- 运行第3步生成的
Makefile
:
- 它 运行在步骤 2 的 Verilog 源上使用 Verilator,它会生成更多的 C++ 源代码和一个新的
Makefile
- 然后运行是新生成的第二个
Makefile
,生成二进制库
stack build --flag phase2
构建第二个可执行文件。此可执行文件包括 .hsc
个处理步骤 2 中生成的 headers 的文件,它链接到步骤 4/2 中生成的 C++ 库。
我想将其自动化,这样我就可以 运行 stack build
而所有这一切都将在幕后发生。我该从哪里开始呢?!
为了说明整个过程,这里有一个self-contained模型:
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
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); }"
]
src/phase2.hs
import Interface
import Foreign.Marshal.Utils
main :: IO ()
main = with (FOO 42) bar
手动编写 运行 整个事情的脚本
stack build
stack run phase1
make -C _build
stack build --flag clashilator-model:phase2
stack exec phase2
牦牛完全光秃秃的: 我用自定义解决了Setup.hs
.
在buildHook
中,我基本上做任何phase1
应该做的事情(而不是把它留在phase1
可执行文件中),把所有生成的文件放在位于 LocalBuildInfo
参数的 buildDir
下方。这些生成的文件是 C++ 源文件和一个 .hsc
文件。
然后我在正确的目录下运行make
,生成一些libFoo.a
.
还在 buildHook
,现在有趣的部分开始了:编辑 PackageDescription
.
中的 Executable
我将 hsc
文件的位置添加到 hsSourceDirs
,并将模块本身添加到 otherModules
。由于 hsc2hs
需要访问生成的 C++ 头文件,我还将正确的目录添加到 includeDirs
。对于库本身,我添加到 extraLibDirs
并将 options
编辑为 link 静态地添加到 libFoo.a
,方法是将标志直接传递给 linker.
所有这一切的结果是修改后的 Executable
集,我在将其传递给默认值 buildHook
之前将其放回 PackageDescription
中。然后 运行s hsc2hs
和 ghc
编译和 link phase2
可执行文件。
我放了一个full example project on Github。查看 Setup.hs
和 clashilator/src/Clash/Clashilator/Setup.hs
以了解实际效果;特别是,这里是 PackageDescription
:
中 Executable
s 的编辑
-- 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
我有一个非常特殊的依赖情况,我想将其打包成一个 Stack/Cabal 包:我需要构建 运行 我的程序以获取 [=64] 的输入=] 产生需要链接到...我的程序的输出。
好的,更具体地说,这里是手动步骤:
stack build
安装所有依赖项,并构建所有 non-Verilator-using 可执行文件。stack exec phase1
到 运行 第一阶段生成一个 Verilog 文件和一个 Clash.manifest
文件。- 我有一个自定义源代码生成器,它使用步骤 2 中的
.manifest
文件,并生成 C++ 代码和一个可用于驱动 Verilator 的Makefile
。 - 运行第3步生成的
Makefile
:- 它 运行在步骤 2 的 Verilog 源上使用 Verilator,它会生成更多的 C++ 源代码和一个新的
Makefile
- 然后运行是新生成的第二个
Makefile
,生成二进制库
- 它 运行在步骤 2 的 Verilog 源上使用 Verilator,它会生成更多的 C++ 源代码和一个新的
stack build --flag phase2
构建第二个可执行文件。此可执行文件包括.hsc
个处理步骤 2 中生成的 headers 的文件,它链接到步骤 4/2 中生成的 C++ 库。
我想将其自动化,这样我就可以 运行 stack build
而所有这一切都将在幕后发生。我该从哪里开始呢?!
为了说明整个过程,这里有一个self-contained模型:
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
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); }"
]
src/phase2.hs
import Interface
import Foreign.Marshal.Utils
main :: IO ()
main = with (FOO 42) bar
手动编写 运行 整个事情的脚本
stack build
stack run phase1
make -C _build
stack build --flag clashilator-model:phase2
stack exec phase2
牦牛完全光秃秃的: 我用自定义解决了Setup.hs
.
在
buildHook
中,我基本上做任何phase1
应该做的事情(而不是把它留在phase1
可执行文件中),把所有生成的文件放在位于LocalBuildInfo
参数的buildDir
下方。这些生成的文件是 C++ 源文件和一个.hsc
文件。然后我在正确的目录下运行
make
,生成一些libFoo.a
.还在
中的buildHook
,现在有趣的部分开始了:编辑PackageDescription
.Executable
我将
hsc
文件的位置添加到hsSourceDirs
,并将模块本身添加到otherModules
。由于hsc2hs
需要访问生成的 C++ 头文件,我还将正确的目录添加到includeDirs
。对于库本身,我添加到extraLibDirs
并将options
编辑为 link 静态地添加到libFoo.a
,方法是将标志直接传递给 linker.所有这一切的结果是修改后的
Executable
集,我在将其传递给默认值buildHook
之前将其放回PackageDescription
中。然后 运行shsc2hs
和ghc
编译和 linkphase2
可执行文件。
我放了一个full example project on Github。查看 Setup.hs
和 clashilator/src/Clash/Clashilator/Setup.hs
以了解实际效果;特别是,这里是 PackageDescription
:
Executable
s 的编辑
-- 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