├── broke ├── main ├── src │ └── main.c └── SConstruct ├── program ├── main.c └── SConscript ├── .gitignore ├── common ├── file.c ├── includes │ └── file.h ├── option@value │ └── file.c └── SConscript ├── obfuscate ├── src │ └── main.c └── SConstruct ├── SConstruct ├── site_scons ├── Debug.py ├── Target.py └── site_init.py ├── README.md └── exportEnvironment └── SConstruct /broke/main: -------------------------------------------------------------------------------- 1 | Hello bdbaddog! 2 | -------------------------------------------------------------------------------- /broke/src/main.c: -------------------------------------------------------------------------------- 1 | Hello bdbaddog! 2 | -------------------------------------------------------------------------------- /program/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | HelloWorld(); 5 | return 0; 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sconsign.dblite 2 | export/ 3 | build/ 4 | __pycache__ 5 | *.pyc 6 | *.o 7 | program 8 | .DS_Store -------------------------------------------------------------------------------- /common/file.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void HelloWorld(void) { 4 | printf("Hello World %d\n", FLAGVALUE); 5 | } -------------------------------------------------------------------------------- /common/includes/file.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_H 2 | #define FILE_H 3 | 4 | #include 5 | 6 | void HelloWorld(void); 7 | 8 | #endif -------------------------------------------------------------------------------- /common/option@value/file.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void HelloWorld(void) { 4 | printf("Goodbye cruel world! %d\n", FLAGVALUE); 5 | } -------------------------------------------------------------------------------- /obfuscate/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | char *hello = SCONS_OBFUSCATE("Hello world!"); 5 | printf("%s\n",hello); 6 | } -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | for target in COMMAND_LINE_TARGETS: 4 | PARSE_TARGET(target).SConscript() 5 | 6 | # Set the default behavior 7 | if not COMMAND_LINE_TARGETS: 8 | Default(PARSE_TARGET('program').SConscript()) 9 | -------------------------------------------------------------------------------- /broke/SConstruct: -------------------------------------------------------------------------------- 1 | env = Environment() 2 | 3 | env.VariantDir('build', 'src') 4 | 5 | def myAction(target, source, env): 6 | print(target, source, env) 7 | print(target[0].abspath) 8 | with open(target[0].abspath, 'w+') as f: 9 | f.write("Hello bdbaddog!\n") 10 | 11 | env.Command('${SOURCE.base}', 'build/main.c', myAction) 12 | -------------------------------------------------------------------------------- /site_scons/Debug.py: -------------------------------------------------------------------------------- 1 | import SCons 2 | 3 | SCons.Script.Main.AddOption('--verbose', 4 | dest='verbose', 5 | action='count', 6 | help='Set verbosity level. Default: 0') 7 | 8 | def DebugPrint(p, *args, **kwawrgs): 9 | if SCons.Script.Main.GetOption('verbose') and SCons.Script.Main.GetOption('verbose') > 0: 10 | print(p, *args, **kwargs) -------------------------------------------------------------------------------- /common/SConscript: -------------------------------------------------------------------------------- 1 | # put common in the parent build folder and not common build folder 2 | try: 3 | Import('self') 4 | Import('env') 5 | except: 6 | env = SpecialEnvironment() 7 | env.Include(self, 'common') 8 | 9 | if 'linux' in self.options['platform']: 10 | env.Append(CPPDEFINES={'FLAGVALUE': 666}) 11 | else: 12 | env.Append(CPPDEFINES={'FLAGVALUE': 1}) 13 | 14 | library = env.Library('common', SpecialGlob(self, '*.c')) 15 | 16 | Return('library') -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SCons Magic 2 | 3 | #### Build `program` (default) 4 | ``` 5 | scons 6 | ``` 7 | 8 | #### Build `program` 9 | ``` 10 | scons program 11 | ``` 12 | 13 | #### Build multiple versions of `program` 14 | ``` 15 | scons program program+platform@linux program+option@value 16 | ``` 17 | 18 | When `platform@linux` is set the program will act differently - same with `option@value`! Try it out/experiment with different options. 19 | 20 | The `program`s will be exported to `export/`. All build files should be contained inside `build/` 21 | 22 | Huge thanks to [bdbaddog](https://github.com/bdbaddog) for the guidance and support. 23 | -------------------------------------------------------------------------------- /program/SConscript: -------------------------------------------------------------------------------- 1 | import os 2 | env = SpecialEnvironment() 3 | 4 | Import('self') 5 | DebugPrint("Parsing '%s' with options:\n\t%s" % (self.name, self.options)) 6 | 7 | print('debug', ARGUMENTS.get('debug', 0)) 8 | 9 | self.env = env 10 | 11 | # Includes('common', env) 12 | lib = env.ImportLibrary(self, 'common') 13 | 14 | if 'linux' in self.options["platform"]: 15 | env.Append(LINKFLAGS='-rdynamic') 16 | 17 | p = env.Program(self.uniqueName(), SpecialGlob(self, '*.c'), LIBS=[lib]) 18 | 19 | exported = env.InstallAs(os.path.join('#/export', self.path(), self.name), p) 20 | Default(exported) 21 | env.Alias(self.originalTargetString, exported) 22 | Return('exported') -------------------------------------------------------------------------------- /obfuscate/SConstruct: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | 4 | def mudgeBuilder(target, source, env): 5 | for (i,_s) in enumerate(source): 6 | with open(_s.abspath, 'r') as s: 7 | with open(target[i].abspath, 'w+') as t: 8 | slines = s.readlines() 9 | for line in slines: 10 | if "SCONS_OBFUSCATE" in line: 11 | r = r'(\s*).*\*(\w*).*SCONS_OBFUSCATE\(\"(.*)\"\);\s*' 12 | matches = re.search(r, line) 13 | spacing = matches.group(1) 14 | varname = matches.group(2) 15 | word = matches.group(3) 16 | t.write(spacing + 'char ' + varname + '[%d];\n' % (len(word)+1)) 17 | for (index, letter) in enumerate(word): 18 | t.write('%s%s[%d] = \'%c\';\n' % (spacing, varname, index, letter)) 19 | t.write('%s%s[%d] = 0;\n' % (spacing, varname, index+1)) 20 | else: 21 | t.write(line) 22 | 23 | 24 | def mudgeEmitter(target, source, env): 25 | actualTarget = [] 26 | for s in source: 27 | path = s.abspath 28 | path = os.path.join(os.path.split(path)[0], 'munged/'+os.path.split(path)[1]) 29 | actualTarget.append(path) 30 | 31 | return actualTarget, source 32 | 33 | bld = Builder(action=mudgeBuilder, emitter=mudgeEmitter) 34 | env = Environment(BUILDERS={'Munge': bld}) 35 | 36 | env.VariantDir('build', 'src') 37 | 38 | munge = env.Munge(Glob('build/*.c')) 39 | 40 | p = env.Program('main', munge) 41 | -------------------------------------------------------------------------------- /exportEnvironment/SConstruct: -------------------------------------------------------------------------------- 1 | import SCons 2 | 3 | env = Environment() 4 | 5 | 6 | def writeEnv(target, source, env): 7 | contents = "" 8 | 9 | contents += 'env = DefaultEnvironment()\n' 10 | 11 | for key in env.Dictionary().keys(): 12 | ignore = [ 13 | # generic 14 | 'BUILDERS', 15 | 'SCANNERS', 16 | 'TEMPFILE', 17 | 'Dir', 18 | 'Dirs', 19 | 'File', 20 | 'RDirs', 21 | 'PSPAWN', 22 | 'SPAWN', 23 | 'ESCAPE', 24 | 'SHLIBEMITTER', # is this important? 25 | 'SMARTLINK', 26 | 'ZIPCOM', 27 | 'LINESEPARATOR', 28 | 'LINKCALLBACKS', 29 | #darwin 30 | 'DShLibSonameGenerator', 31 | #linux 32 | 'ShLibSonameGenerator', 33 | 'LdModSonameGenerator' 34 | ] 35 | if key in ignore: 36 | continue 37 | if key[0] == "_": 38 | continue 39 | if isinstance(env[key], dict): 40 | contents += "env['%s'] = %s\n" % (key, env[key]) 41 | elif isinstance(env[key], str): 42 | contents += "env['%s'] = '%s'\n" % (key, env[key]) 43 | elif isinstance(env[key], list): 44 | contents += "env['%s'] = %s\n" % (key, env[key]) 45 | elif isinstance(env[key], int): 46 | contents += "env['%s'] = %s\n" % (key, env[key]) 47 | elif env[key] is None: 48 | contents += "env['%s'] = %s\n" % (key, env[key]) 49 | elif isinstance(env[key], SCons.Util.CLVar): 50 | contents += "env['%s'] = SCons.Util.CLVar(['%s'])\n" % (key, env[key]) 51 | else: 52 | print("Unknown type '%s' with value '%s' for key '%s'" % (type(env[key]), env[key], key)) 53 | exit(0) 54 | 55 | with open(target[0].abspath, 'w+') as f: 56 | f.write(contents) 57 | 58 | env.AlwaysBuild(env.Command('env.py', [], [writeEnv])) 59 | -------------------------------------------------------------------------------- /site_scons/Target.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import platform 4 | import SCons 5 | import copy 6 | 7 | defaultOptions = { 8 | "platform": sys.platform, 9 | "machine": platform.machine(), 10 | "architecture": platform.architecture()[0], 11 | "processor": platform.processor(), 12 | "docs": "True", 13 | } 14 | 15 | class Target: 16 | def __init__(self, name, options=None): 17 | global __defaultOptions 18 | self.name = name 19 | 20 | df = copy.deepcopy(defaultOptions) 21 | if options: 22 | self.options = options 23 | 24 | for key in df.keys(): 25 | if key not in self.options: 26 | self.options[key] = df[key] 27 | else: 28 | self.options = copy.deepcopy(defaultOptions) 29 | 30 | self.originalTargetString = None 31 | 32 | def path(self) -> str: 33 | ops = [os.path.split(self.name)[1]] 34 | for key in self.options.keys(): 35 | val = key + "@" + self.options[key] 36 | val = val.lower().replace("/", '') 37 | ops.append(val) 38 | return "+".join(ops) 39 | 40 | def uniqueName(self) ->str: 41 | return str(hex(hash(self.path()))).replace('0x', self.name+'_').replace('-','') 42 | 43 | def variantDir(self) -> str: 44 | ''' 45 | The build directory 46 | ''' 47 | return os.path.join("#/build", self.path()) 48 | buildDir = variantDir 49 | 50 | def exportDir(self): 51 | return os.path.join("#/export", self.path()) 52 | 53 | def hasOption(self, option): 54 | try: 55 | self.options[option] 56 | return True 57 | except: 58 | return False 59 | 60 | def getOption(self, option, fallback=None): 61 | try: 62 | return self.options[option] 63 | except: 64 | return fallback 65 | 66 | def SConscript(self, exports=None): 67 | if exports == None: 68 | exports = [] 69 | if isinstance(exports, list): 70 | exports.append('self') 71 | elif isinstance(exports, str): 72 | exports = exports + ' self' 73 | elif isinstance(exports, dict): 74 | exports['self'] = self 75 | return SCons.Script.SConscript(os.path.join(SCons.Script.GetLaunchDir(), self.name, 'SConscript'), variant_dir=self.variantDir(), exports=exports) 76 | 77 | def PARSE_TARGET(target: str) -> Target: 78 | try: 79 | s = target.split("+") 80 | key = s[0] 81 | name = key 82 | values = {} 83 | 84 | s = s[1:] 85 | if s: 86 | for sv in s: 87 | svv = sv.split("@") 88 | if len(svv) == 1: 89 | values[svv[0]] = "True" 90 | else: 91 | values[svv[0]] = svv[1] 92 | options = values 93 | except: 94 | name = target 95 | options = None 96 | 97 | t = Target(name=name, options=options) 98 | t.originalTargetString = target 99 | return t 100 | -------------------------------------------------------------------------------- /site_scons/site_init.py: -------------------------------------------------------------------------------- 1 | from Target import * 2 | from Debug import * 3 | 4 | def SpecialGlob(target, path, inFolder=''): 5 | originalFiles = Glob(os.path.join(inFolder, path)) 6 | files = originalFiles 7 | 8 | def lookupFolder(folder): 9 | DebugPrint("looking in ", os.path.join(inFolder, folder, path)) 10 | newFiles = Glob(os.path.join(inFolder, folder, path)) 11 | for f in newFiles: 12 | replace = False 13 | for (i, orig_f) in enumerate(files): 14 | if os.path.splitext(f.name)[0] == os.path.splitext(orig_f.name)[0]: 15 | DebugPrint("Replacing", orig_f.name, "with", f.name) 16 | files[i] = f 17 | replace = True 18 | break 19 | if not replace: 20 | files.append(f) 21 | 22 | for firstKey in target.options.keys(): 23 | firstPath = firstKey + "@" + target.options[firstKey] 24 | firstPath = firstPath.lower().replace('/', '') 25 | lookupFolder(firstPath) 26 | for secondKey in target.options.keys(): 27 | secondPath = secondKey + "@" + target.options[secondKey] 28 | secondPath = secondPath.lower().replace('/', '') 29 | if secondPath == firstPath: 30 | continue 31 | lookupFolder('+'.join([firstPath, secondPath])) 32 | for thirdKey in target.options.keys(): 33 | thirdPath = thirdKey + "@" + target.options[thirdKey] 34 | thirdPath = thirdPath.lower().replace('/', '') 35 | if thirdPath == secondPath: 36 | continue 37 | if thirdPath == firstPath: 38 | continue 39 | lookupFolder('+'.join([firstPath, secondPath, thirdPath])) 40 | DebugPrint("files for ", target.name, ":") 41 | for f in files: 42 | DebugPrint("\t", f.path) 43 | return files 44 | 45 | def SpecialEnvironment(): 46 | env = Environment() 47 | 48 | def Include( 49 | _env, _target, _source, inFolder="includes/", *args, **kwargs 50 | ): 51 | originPath = os.path.join(GetLaunchDir(), _source, inFolder) 52 | files = SpecialGlob(_target, '*.h', inFolder=originPath) 53 | 54 | basePath = os.path.join('#/build', _target.path(), "headers") 55 | path = os.path.join(basePath, _source) 56 | 57 | DebugPrint("header files for", _source, originPath, path, files) 58 | for f in files: 59 | DebugPrint("\t", f.path) 60 | 61 | i = _env.Install(path, files) 62 | Default(i) 63 | 64 | _env.PrependUnique(CPPPATH=[basePath]) 65 | 66 | return i 67 | 68 | AddMethod(Environment, Include) 69 | 70 | def ImportLibrary( 71 | _env, _target, _source, *args, **kwargs 72 | ): 73 | _env.Include(_target, _source, *args, **kwargs) 74 | newTarget = Target(name=_source, options=_target.options) 75 | newTarget.parent = _target 76 | self = newTarget 77 | self.env = _target.env.Clone() 78 | i = SConscript(dirs=[os.path.join("#", _source)], variant_dir=os.path.join('#/build', _target.path(), _source), exports=['self']) 79 | return i 80 | 81 | AddMethod(Environment, ImportLibrary) 82 | 83 | return env --------------------------------------------------------------------------------