2

我目前正在测试将我们的构建系统从 make 移植到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

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

或者这整个方法是愚蠢的,我应该采取其他途径?

4

1 回答 1

3

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

举几个示例方法,定义似是而非的命令来转换.js/.coffee文件很有用:

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结构列表中驱动它(groupoExt字段上做一个添加一个规则而不是依次oExt调用doesFileExists每个规则)。iExt这种方法的一个优点是,如果您这样做shake build/out.js,则不需要进行目录列表,尽管成本可能可以忽略不计。

方法二:调用列出文件shake

而不是写main = shakeArgs ...做:

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 ()

这里你在 IO 中操作获取文件列表,然后可以通过引用之前捕获的值来添加规则。添加rulesIO :: IO (Rules ()) -> Rules ()将允许您列出shakeArgs.

方法 3:列出规则内的文件

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

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

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

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

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

于 2015-04-26T11:43:52.487 回答