如何使用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包中:我需要构建并运行程序,以将输入输入到代码生成器,该生成器产生需要链接的输出进入...我的程序。
I have a very peculiar dependency situation that I would like to package up in a single Stack/Cabal package: I need to build and run my program to get the input to a code-generator which produces output that needs to be linked in to... my program.
好的,因此,更具体的说,这是手动操作的步骤:
OK so in more concrete terms, here are the steps manually:
-
堆栈构建
以安装所有依赖项,并构建所有不使用竞争者的可执行文件。 -
stack exec phase1
运行第一阶段,该阶段将生成一个Verilog文件和一个Clash.manifest
文件。 - 我有一个自定义源生成器,它使用了第2步中的
.manifest
文件,并生成C ++代码和Makefile
可用于驱动Verilator。 - 运行在第3步中生成的
Makefile
:
stack build
to install all dependencies, and build all non-Verilator-using executables.stack exec phase1
to run the first phase which generates, among other things, a Verilog file and a Clash.manifest
file.- I have a custom source generator, which consumes the
.manifest
file from step 2, and produces C++ code and aMakefile
that can be used to drive Verilator. - Run the
Makefile
generated in step 3:
- 它在第2步的Verilog源代码上运行Verilator,这会产生更多的C ++源代码和新的
Makefile
- 然后运行新生成的第二个
Makefile
二进制库
- It runs Verilator on the Verilog sources from step 2, which produces more C++ source code and a new
Makefile
- Then it runs the newly generated second
Makefile
, which produces a binary library
stack build --flag phase2
生成第二个可执行文件。该可执行文件包括 .hsc
文件,这些文件处理步骤2中生成的标头,并且链接到步骤4/2中生成的C ++库。
stack build --flag phase2
builds the second executable. This executable includes .hsc
files that process headers produced in step 2, and it links to the C++ libraries produced in step 4/2.我想使它自动化,以便我可以运行 stack build
,所有这些操作都会在后台进行。我什至从哪里开始?!
I would like to automate this so that I can just run stack build
and all this would happen behind the scenes. Where do I even start?!
为了说明整个过程,这里有一个自包含的模型:
To illustrate the whole process, here is a self-contained model:
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
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
src/phase2.hs
import Interface
import Foreign.Marshal.Utils
main :: IO ()
main = with (FOO 42) bar
脚本手动运行整个程序
Script to run the whole thing manually
stack build
stack run phase1
make -C _build
stack build --flag clashilator-model:phase2
stack exec phase2
推荐答案
The牛是裸露的:我设法通过自定义 Setup.hs
来解决。
The yak is fully bare: I managed to solve it with a custom Setup.hs
.
-
在
buildHook
中,我基本上会执行phase1
所做的任何操作应该这样做(而不是将其保留在phase1
可执行文件中),将所有生成的文件放在buildDir
以下的位置LocalBuildInfo
参数。这些生成的文件是C ++源文件和.hsc
文件。
In
buildHook
, I basically do whateverphase1
was supposed to do (instead of leaving it in aphase1
executable), putting all generated files in places below thebuildDir
of theLocalBuildInfo
argument. These generated files are C++ source files and an.hsc
file.
然后在正确的目录中运行 make
,生成一些 libFoo.a
。
I then run make
in the right directory, producing some libFoo.a
.
仍然保留在 buildHook
中,现在有趣的部分开始了:编辑 Executable
在 PackageDescription
中。
Still in buildHook
, now the fun part starts: editing the Executable
s in the PackageDescription
.
我将 hsc
文件的位置添加到 hsSourceDirs
,而模块本身为 otherModules
。由于 hsc2hs
需要访问生成的C ++标头,因此我还将正确的目录添加到 includeDirs
。对于库本身,我添加到 extraLibDirs
并编辑选项
以静态链接到 libFoo。 a
,将标志直接传递到链接器。
I add the hsc
file's location to hsSourceDirs
, and the module itself to otherModules
. Since hsc2hs
requires access to the generated C++ headers, I also add the right directory to includeDirs
. For the library itself, I add to extraLibDirs
and edit options
to link statically to libFoo.a
, by passing flags directly to the linker.
所有这些的结果是对可执行文件
,然后将其传递给 PackageDescription
,然后将其传递给默认的 buildHook
。然后运行 hsc2hs
和 ghc
来编译和链接 phase2
可执行文件。
The result of all this is a modified set of Executable
s, which I put back into the PackageDescription
before passing it to the default buildHook
. That one then runs hsc2hs
and ghc
to compile and link the phase2
executables.
我已经将 Github上的完整示例项目。查看 Setup.hs
和 clashilator / src / Clash / Clashilator / Setup.hs
来看看它的作用;特别是在 PackageDescription
中的 Executable
的编辑:
I have put a full example project on Github. Look at Setup.hs
and clashilator/src/Clash/Clashilator/Setup.hs
to see this in action; in particular, here is the editing of the Executable
s in the 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
这篇关于如何使用Stack / Cabal构建早期版本的程序输出作为同一构建后期版本的源代码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!