ndmitchell / build-shootout

Comparison of build program expressive power
88 stars 11 forks source link

SCons examples #15

Closed dirkbaechle closed 10 years ago

dirkbaechle commented 10 years ago

Hi,

this patch adds SCons examples to the "Shootout". All problems pass with "success" on my side (SCons 2.3.1, Python 2.7.3, Ubuntu LTS 12.04)... There is no script for "pool", it's not really applicable since SCons knows only one "stage"...building at full throttle. ;) Also the "monad" examples look a little wild, but it really needs a lot of trickery to force a completely different dependency handling onto SCons...only to satisfy the given test setup (one wouldn't actually model the build like this).

Best regards,

Dirk

ndmitchell commented 10 years ago

Thanks for these, I'll review them later today.

ndmitchell commented 10 years ago

Thanks very much, I've merged them. I've raised #16 to turn these tests on for Travis.

I'm a little unsure about the Monad examples. The test is deliberately trying to exercise a type of dependency I don't think SCons has, so I would have expected them to not be expressible. I'll have to learn a bit more about SCons so I can properly figure out what is going. These monad style examples are all engineered from real examples - once you start having generated files those dependencies become a lot more common.

dirkbaechle commented 10 years ago

Thanks for the merge. I kind of understand your concerns about the Monad examples, that's why I said they look a little wild. SCons has the concept of Scanners, which is what normally gets used to find "#include"s in C files for example, and then adds implicit dependencies while building stuff. Unfortunately this functionality is linked to file extensions, like use ScannerA for all ".c" files and use ScannerB for all ".idl" files. Since your example files don't carry any extension, I had to be a bit more creative when hooking into the processing of the single build tasks. ;) I'm currently watching the vimeo video of your talk at ICFP 2012, and from what I got so far the basic ideas behind shake and SCons are very similar. We don't build a dependency graph "a priori" either, but make it up on the go. If you need any help getting SCons up and running, or have further questions, feel free to ask please.

ndmitchell commented 10 years ago

I've built projects which used SCons before, so I've got it installed previously, shouldn't be too hard. I wasn't aware of the scanners concept, so I guess that's the additional power. How much cleaner would monad3 look with different extensions? If it makes it clearer, I'd be happy to add monad3-ext - the test is really aimed at clarifying the dependency power, and if SCons has the power nicely, but fitting it into the test framework is obscuring that, it might be useful.

I should also add a digest/hash checking example (#1), which I SCons should do nicely on.

dirkbaechle commented 10 years ago

I looked at this some more, and it's not so much about file extensions actually. If one changes the log file check to:

diff --git a/Main.hs b/Main.hs
index 8e2ff95..a3a6d24 100755
--- a/Main.hs
+++ b/Main.hs
@@ -111,8 +111,8 @@ monad3 run = do
     writeBinary "source" "output1\noutput2\n"
     writeFile "input1" "test"
     writeFile "input2" "again"
-    run [Target "output", Contents "output" "testagain", Log "run"]
-    run [Target "output", NoChange, Log "run", Missing "gen"]
+    run [Target "output", Contents "output" "testagain", Log "run run"]
+    run [Target "output", NoChange, Log "run run", Missing "gen"]
     writeBinary "source" "gen\noutput2\n"
     run [Target "output", Contents "output" "Generated\nagain"]
     run [Target "output", NoChange]

then the "monad3-scons" can be written as

import os

# Builder starts here
import re
import SCons.Script
import SCons.Builder

include_re = re.compile(r'^\s*(\S+)$', re.M)

def infile_scan(node, env, path, arg):
    if not os.path.isfile(str(node)):
        return []
    contents = node.get_contents()
    return include_re.findall(contents)

in_scanner = SCons.Script.Scanner(function = infile_scan,
                                  argument = None)

list_builder = SCons.Builder.Builder(
        action = 'cat $SOURCE | xargs cat > $TARGET',
        source_scanner = in_scanner)
# Builder ends here

env = Environment(ENV = os.environ)
env.AppendENVPath('PATH','.')
env.Append(BUILDERS = {'ListBuild' : list_builder})
env.Command('gen', [], 'sh monad3-gen -- $TARGET')
env.Command('output1', 'input1', 'sh monad3-run $SOURCE -- $TARGET')
env.Command('output2', 'input2', 'sh monad3-run $SOURCE -- $TARGET')
env.ListBuild('output','source')

which is not only simpler, but also more "SCons"ish. Note how the Builder part between "start/end" comments would usually be hidden away into its own Tool module "monad3" ( http://www.scons.org/wiki/ToolsForFools ), such that it can be reused by anyone as follows:

import os

env = Environment(ENV = os.environ, tools=['default','monad3'])
env.AppendENVPath('PATH','.')
env.Command('gen', [], 'sh monad3-gen -- $TARGET')
env.Command('output1', 'input1', 'sh monad3-run $SOURCE -- $TARGET')
env.Command('output2', 'input2', 'sh monad3-run $SOURCE -- $TARGET')
env.ListBuild('output','source')

. Along the same lines, one could even wrap the "monad3-run" call into its own builder, let's call it ReplaceBuild(), and then process an arbitrary number of inputs with:

import os

env = Environment(ENV = os.environ, tools=['default','monad3'])
env.AppendENVPath('PATH','.')
env.Command('gen', [], 'sh monad3-gen -- $TARGET')
env.ReplaceBuild(Glob('input*'))
env.ListBuild('output','source')

which would handle auto-generated "input*" files without any further ado. By the way, there is also an external repo for Haskell support in our http://www.scons.org/wiki/ToolsIndex . Maybe you care to have a quick glance at it...