Как использовать вывод программы из более ранней части сборки Stack / Cabal в качестве источника в более поздней части этой же сборки? - PullRequest
9 голосов
/ 03 мая 2020

У меня очень специфическая ситуация с зависимостями, которую я хотел бы упаковать в один пакет Stack / Cabal: мне нужно собрать и запустить мою программу, чтобы получить входные данные для генератора кода, который производит выходные данные, которые необходимо связать в ... моей программе.

ОК, так что более конкретно, вот шаги вручную:

  1. stack build, чтобы установить все зависимости и построить все без Verilator -использование исполняемых файлов.
  2. stack exec phase1 для запуска первой фазы, которая генерирует, среди прочего, файл Verilog и файл Cla sh .manifest.
  3. У меня есть пользовательский источник генератор, который использует файл .manifest из шага 2 и генерирует код C ++ и Makefile, который можно использовать для управления Verilator.
  4. Запустите Makefile, созданный на шаге 3:
    1. Он запускает Verilator на источниках Verilog, начиная с шага 2, который производит больше исходного кода на C ++ и новый Makefile
    2. Затем он запускает только что созданный второй Makefile, который создает двоичную библиотеку
  5. * 10 28 * строит второй исполняемый файл. Этот исполняемый файл содержит .hsc файлов, которые обрабатывают заголовки, созданные на шаге 2, и он ссылается на библиотеки C ++, созданные на шаге 4 / 2.

Я хотел бы автоматизировать это, чтобы я мог просто запустить 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    

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

1 Ответ

0 голосов
/ 05 мая 2020

Як полностью голый : мне удалось решить его с помощью пользовательского Setup.hs.

  1. В buildHook, я в основном делаю все что угодно phase1 должен был сделать (вместо того, чтобы оставить его в phase1 исполняемом файле), поместив все сгенерированные файлы в места ниже buildDir аргумента LocalBuildInfo. Эти сгенерированные файлы являются исходными файлами C ++ и файлом .hsc.

  2. Затем я запускаю make в нужном каталоге, получая немного libFoo.a.

  3. Все еще в buildHook, теперь начинается самое интересное: редактирование Executable s в PackageDescription.

    Я добавляю местоположение файла hsc в hsSourceDirs, а сам модуль в otherModules. Поскольку hsc2hs требуется доступ к сгенерированным заголовкам C ++, я также добавляю правильный каталог в includeDirs. Для самой библиотеки я добавляю к extraLibDirs и редактирую options для статической ссылки на libFoo.a, передавая флаги непосредственно компоновщику.

  4. Результат всего этого модифицированный набор Executable s, который я помещаю обратно в PackageDescription перед передачей его по умолчанию buildHook. Затем он запускает hsc2hs и ghc для компиляции и компоновки исполняемых файлов phase2.

Я поместил полный пример проекта на Github . Посмотрите на Setup.hs и clashilator/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
...