从(Action a)的内容动态生成规则

时间:2015-04-26 04:12:31

标签: haskell shake-build-system

我目前正在测试将我们的构建系统从make to shake移动并遇到障碍:

鉴于以下项目结构:

static/a.js
static/b.coffee

build/a.js
build/b.js

也就是说,各种输入扩展名映射到相同的输出扩展名,因此直截了当的"build//*.js" %>规则无法正常工作。

我希望尽可能避免使用优先级,并编写一个特殊的构建规则来检查是否存在任何可能的输入感到笨拙(特别是因为这种情况也会出现在其他文件类型中),所以我写了以下内容:

data StaticFileMapping a = StaticFileMapping String String (FilePath -> FilePath -> Action a)

staticInputs :: FilePath -> StaticFileMapping a -> Action [FilePath]
staticInputs dir (StaticFileMapping iExt _ _) = (findFiles (dir </> "static") [iExt])

staticInputToOutput :: StaticFileMapping a -> FilePath -> FilePath
staticInputToOutput (StaticFileMapping _ oExt _) = (remapDir ["build"]) . (-<.> oExt)

staticTargets :: FilePath -> StaticFileMapping a -> Action [FilePath]
staticTargets dir sfm = (map $ staticInputToOutput sfm) <$> staticInputs dir sfm

rules :: FilePath -> StaticFileMapping a -> Rules ()
rules dir sfm@(StaticFileMapping _ _ process) = join $ mconcat . (map buildInputRule) <$> staticInputs dir sfm
    where buildInputRule :: FilePath -> Rules ()
          buildInputRule input = (staticInputToOutput sfm input) %> (process input)

这样我就可以为每种输入类型(.coffee -> .js.svg -> .png)等定义一个映射,只需要很少量的代码就可以为每种输入类型实现转换。它几乎可以工作。

但据我所知,似乎不可能从(Action a)转到Rules _,而不会先将Action内的值扔掉。

是否有(Action a) -> (a -> Rules ()) -> Rules ()(Action a) -> (Rules a)类型的函数?我可以自己实现任何一个,还是需要修改库的代码?

或者这整个方法是不是很明智,我应该采取其他一些方法?

1 个答案:

答案 0 :(得分:2)

首先,使用priority不起作用,因为静态选择规则然后运行它 - 它不会回溯。同样重要的是,Shake没有运行任何Action操作来生成Rules(根据您建议的两个函数),因为Action可能会调用{{1}在need上它自己定义,或由另一个动作规则定义,从而使这些Rule调用的顺序可见。您可以添加Action,这可能足以满足您的想法(目录列表),但它目前尚未公开(我有一个内部函数可以完全执行此操作)。

举几个示例方法,定义合理命令来转换IO (Rules ()) -> Rules () / .js文件非常有用:

.coffee

方法1:使用cmdCoffee :: FilePath -> FilePath -> Action () cmdCoffee src out = do need [src] cmd "coffee-script-convertor" [src] [out] cmdJavascript :: FilePath -> FilePath -> Action () cmdJavascript = copyFile'

这将是我的标准方法,编写如下内容:

doesFileExist

这准确地捕获了依赖关系,即如果用户在目录中添加"build/*.js" %> \out -> do let srcJs = "static" </> dropDirectory1 out let srcCf = srcJs -<.> "coffee" b <- doesFileExist srcCf if b then cmdCoffee srcCf out else cmdJavascript srcJs out 文件,则应重新运行该规则。如果这是一种常见的模式,你可以想象加油.coffee。您甚至可以从doesFileExist结构列表中对其进行驱动(在StaticFileMapping字段上执行group,每oExt添加一条规则,而不是每条oExt反过来doesFileExists。这种方法的一个优点是,如果你做iExt它不需要进行目录列表,尽管可能成本可以忽略不计。

方法2:在调用shake build/out.js之前列出文件

而不是写shake做:

main = shakeArgs ...

这里您在IO中操作以获取文件列表,然后可以通过引用先前捕获的值来添加规则。添加import System.Directory.Extra(listFilesRecursive) -- from the "extra" package main = do files <- listFilesRecursive "static" shakeArgs shakeOptions $ do forM_ files $ \src -> case takeExtension src of ".js" -> do let out = "build" </> takeDirectory1 src want [out] out %> \_ -> cmdJavascript src out -- rules for all other types you care about _ -> return () 将允许您列出rulesIO :: IO (Rules ()) -> Rules ()内的文件。

方法3:列出里面的文件规则

您可以在目录列表中定义文件名和输出之间的映射:

shakeArgs

然后将其提升为一套规则:

buildJs :: Action (Map FilePath (Action ()))
buildJs = do
    js <- getDirectoryFiles "static" ["*.js"]
    cf <- getDirectoryFiles "static" ["*.coffee"]
    return $ Map.fromList $
        [("build" </> j, cmdJavascript ("static" </> j) ("build" </> j)) | j <- js] ++
        [("build" </> c, cmdCoffee ("static" </> c) ("")) | c <- cf]

但是,重新计算我们构建的每个文件的目录列表,因此我们应该缓存它并确保它只计算一次:

action $ do
    mpJs <- buildJs
    need $ Map.keys mpJs
"//*.js" %> \out -> do
    mpJs <- buildJs
    mpJs Map.! out

此解决方案可能与您的原始方法最接近,但我发现它最复杂。