haskell - Dynamically generating Rules from the content of an (Action a) -
i'm testing porting our build system make shake , have hit roadblock:
given following project structure:
static/a.js static/b.coffee build/a.js build/b.js
that is, various input extensions map identical output extensions, straightforward "build//*.js" %>
rule isn't going work.
i wanted avoid using priority if possible, , writing ad-hoc build rule checks existence of either possible input felt clunky (especially since situation occurs other filetypes well), wrote following:
data staticfilemapping = staticfilemapping string string (filepath -> filepath -> action a) staticinputs :: filepath -> staticfilemapping -> action [filepath] staticinputs dir (staticfilemapping iext _ _) = (findfiles (dir </> "static") [iext]) staticinputtooutput :: staticfilemapping -> filepath -> filepath staticinputtooutput (staticfilemapping _ oext _) = (remapdir ["build"]) . (-<.> oext) statictargets :: filepath -> staticfilemapping -> action [filepath] statictargets dir sfm = (map $ staticinputtooutput sfm) <$> staticinputs dir sfm rules :: filepath -> staticfilemapping -> rules () rules dir sfm@(staticfilemapping _ _ process) = join $ mconcat . (map buildinputrule) <$> staticinputs dir sfm buildinputrule :: filepath -> rules () buildinputrule input = (staticinputtooutput sfm input) %> (process input)
that way can define mapping each input type (.coffee -> .js
, .svg -> .png
) , on, tiny amount of code implementing transformation each. , works.
but seems impossible go (action a)
rules _
without throwing value inside action
away first, far can tell.
is there function type (action a) -> (a -> rules ()) -> rules ()
or (action a) -> (rules a)
? can implement either 1 myself, or need modify library's code?
or entire approach hare-brained , should take other route?
first off, using priority
not work, picks rule statically runs - doesn't backtrack. it's important shake doesn't run action
operations produce rules
(as per 2 functions propose) since action
might call need
on rule
defines, or defined action rule, making ordering of action
calls visible. add io (rules ()) -> rules ()
, might enough thinking of (directory listing), isn't exposed (i have internal function that).
to give few example approaches it's useful define plausible commands convert .js
/.coffee
files:
cmdcoffee :: filepath -> filepath -> action () cmdcoffee src out = need [src] cmd "coffee-script-convertor" [src] [out] cmdjavascript :: filepath -> filepath -> action () cmdjavascript = copyfile'
approach 1: use doesfileexist
this standard approach, writing like:
"build/*.js" %> \out -> let srcjs = "static" </> dropdirectory1 out let srccf = srcjs -<.> "coffee" b <- doesfileexist srccf if b cmdcoffee srccf out else cmdjavascript srcjs out
this accurately captures dependency if user adds .coffee
file in directory rule should rerun. imagine sugaring doesfileexist
if common pattern you. drive list of staticfilemapping
structures (do group
on oext
field add 1 rule per oext
calls doesfileexists
on each iext
in turn). advantage of approach if shake build/out.js
doesn't need directory listing, although cost negligible.
approach 2: list files before calling shake
instead of writing main = shakeargs ...
do:
import system.directory.extra(listfilesrecursive) -- "extra" package main = files <- listfilesrecursive "static" shakeargs shakeoptions $ form_ files $ \src -> case takeextension src of ".js" -> let out = "build" </> takedirectory1 src want [out] out %> \_ -> cmdjavascript src out -- rules other types care _ -> return ()
here operate in io list of files, can add rules referring captured value. adding rulesio :: io (rules ()) -> rules ()
allow list files inside shakeargs
.
approach 3: list files inside rules
you can define mapping between file names , outputs, driven directory listing:
buildjs :: action (map filepath (action ())) buildjs = 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]
then lift set of rules:
action $ mpjs <- buildjs need $ map.keys mpjs "//*.js" %> \out -> mpjs <- buildjs mpjs map.! out
however, recomputes directory listing every file build, should cache , make sure it's computed once:
mpjs <- newcache $ \() -> buildjs action $ mpjs <- mpjs () need $ map.keys mpjs "//*.js" %> \out -> mpjs <- mpjs () mpjs map.! out
this solution closest original approach, find complex.
Comments
Post a Comment