├── .github └── workflows │ ├── ci.yml │ └── yapf.yml ├── .gitignore ├── .style.yapf ├── .yapfignore ├── LICENSE ├── README.md ├── ambuild ├── __init__.py ├── cache.py ├── command.py ├── cpp.py ├── job.py ├── osutil.py ├── runner.py └── worker.py ├── ambuild2 ├── __init__.py ├── builder.py ├── context.py ├── damage.py ├── database.py ├── frontend │ ├── __init__.py │ ├── amb2_gen.py │ ├── base_generator.py │ ├── cloneable.py │ ├── cloneable_test.py │ ├── context_manager.py │ ├── cpp │ │ ├── __init__.py │ │ ├── cpp_rules.py │ │ ├── cpp_rules_test.py │ │ ├── cpp_utils.py │ │ ├── msvc_utils.py │ │ └── verify.py │ ├── paths.py │ ├── paths_test.py │ ├── proxy.py │ ├── proxy_test.py │ ├── system.py │ ├── v2_0 │ │ ├── __init__.py │ │ ├── amb2_gen.py │ │ ├── context.py │ │ ├── context_manager.py │ │ ├── cpp │ │ │ ├── __init__.py │ │ │ ├── builders.py │ │ │ ├── compilers.py │ │ │ ├── detect.py │ │ │ └── vendors.py │ │ ├── prep.py │ │ └── vs │ │ │ ├── __init__.py │ │ │ ├── cxx.py │ │ │ ├── export_vcxproj.py │ │ │ └── gen.py │ ├── v2_1 │ │ ├── __init__.py │ │ ├── amb2_gen.py │ │ ├── context.py │ │ ├── context_manager.py │ │ ├── cpp │ │ │ ├── __init__.py │ │ │ ├── builders.py │ │ │ ├── compiler.py │ │ │ ├── detect.py │ │ │ ├── gcc.py │ │ │ ├── msvc.py │ │ │ ├── sunpro.py │ │ │ └── vendor.py │ │ ├── prep.py │ │ ├── tools │ │ │ ├── __init__.py │ │ │ └── fxc.py │ │ └── vs │ │ │ ├── __init__.py │ │ │ ├── cxx.py │ │ │ ├── export_vcxproj.py │ │ │ ├── gen.py │ │ │ └── nodes.py │ ├── v2_2 │ │ ├── __init__.py │ │ ├── amb2_gen.py │ │ ├── context.py │ │ ├── context_manager.py │ │ ├── cpp │ │ │ ├── __init__.py │ │ │ ├── builders.py │ │ │ ├── compiler.py │ │ │ ├── deptypes.py │ │ │ ├── detect.py │ │ │ ├── gcc.py │ │ │ ├── msvc.py │ │ │ └── vendor.py │ │ ├── prep.py │ │ ├── tools │ │ │ ├── __init__.py │ │ │ ├── fxc.py │ │ │ └── protoc.py │ │ └── vs │ │ │ ├── __init__.py │ │ │ ├── custom_tools.py │ │ │ ├── cxx.py │ │ │ ├── export_vcxproj.py │ │ │ ├── gen.py │ │ │ └── nodes.py │ ├── version.py │ └── vs │ │ ├── __init__.py │ │ ├── gen.py │ │ ├── nodes.py │ │ └── xmlbuilder.py ├── graph.py ├── make_parser.py ├── nodetypes.py ├── process_manager.py ├── run.py ├── task.py └── util.py ├── scripts ├── ambuild_dsymutil_wrapper.sh └── ambuild_objcopy_wrapper.sh ├── setup.py └── tests ├── always_dirty ├── AMBuildScript ├── configure.py └── generate.py ├── api2_2 ├── AMBuildScript ├── configure.py └── core │ ├── AMBuild │ └── main.cc ├── autoinclude ├── AMBuildScript ├── activate.txt ├── configure.py ├── generate_header.py └── main.cpp ├── cx_paths ├── AMBuildScript ├── configure.py ├── helper │ └── helper.ambuild ├── main.cpp └── program.ambuild ├── dsymutil ├── AMBuildScript ├── configure.py └── main.cpp ├── invalid_symlink ├── AMBuildScript ├── configure.py └── main.cpp ├── modules ├── AMBuildScript ├── configure.py └── core │ ├── AMBuild │ ├── m2 │ ├── AMBuild │ └── m2.cc │ ├── main.cc │ └── main2.cc ├── multiarch ├── AMBuildScript ├── configure.py └── core │ ├── AMBuild │ └── main.cpp ├── originalcwd ├── AMBuildScript └── configure.py ├── precompiled-headers ├── AMBuildScript ├── configure.py └── main.cpp ├── resource_dll ├── AMBuildScript ├── configure.py └── project_resource.rc ├── shaders ├── AMBuildScript ├── code │ ├── common.hlsl │ ├── image_common.hlsl │ ├── image_ps.hlsl │ ├── image_vs.hlsl │ ├── include-shaders.cc │ └── vs_common.hlsl └── configure.py ├── shared_outputs ├── basic │ ├── AMBuildScript │ ├── configure.py │ └── generate_header.py ├── duplicates │ ├── AMBuildScript │ ├── configure.py │ └── generate_header.py ├── mixup │ ├── AMBuildScript │ ├── configure.py │ └── generate_header.py └── multiples │ ├── AMBuildScript │ ├── configure.py │ └── generate_header.py ├── staticlib ├── AMBuildScript ├── configure.py └── main.cpp └── vcfiles ├── AMBuildScript ├── configure.py └── main.cpp /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Python Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | python-version: ['3.12'] 11 | os: [ubuntu-24.04, macos-latest, windows-2019] 12 | 13 | steps: 14 | - name: Set up Python ${{ matrix.python-version }} 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: ${{ matrix.python-version }} 18 | - name: Install Deps 19 | if: runner.os == 'Linux' 20 | run: | 21 | sudo dpkg --add-architecture i386 22 | sudo apt-get install lib32stdc++-14-dev lib32z1-dev libc6-dev-i386 23 | sudo apt-get install g++-multilib 24 | - uses: actions/checkout@v3 25 | - name: Unit Tests 26 | run: | 27 | python -m unittest discover ambuild2 "*_test.py" 28 | - name: Smoke Tests 1 29 | if: runner.os != 'Windows' # no choco vcvars detection on older APIs 30 | run: | 31 | python -m pip install . 32 | mkdir objdir1 33 | cd objdir1 34 | python ../tests/staticlib/configure.py 35 | ambuild 36 | mkdir ../objdir2/ 37 | cd ../objdir2/ 38 | python ../tests/modules/configure.py 39 | ambuild 40 | - name: Smoke Tests 2 41 | run: | 42 | python -m pip install . 43 | mkdir objdir3 44 | cd objdir3 45 | python ../tests/multiarch/configure.py 46 | ambuild 47 | mkdir ../objdir4/ 48 | cd ../objdir4/ 49 | python ../tests/precompiled-headers/configure.py 50 | ambuild 51 | -------------------------------------------------------------------------------- /.github/workflows/yapf.yml: -------------------------------------------------------------------------------- 1 | name: Style and Formatting Checks 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: [3.12] 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Python ${{ matrix.python-version }} 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: ${{ matrix.python-version }} 18 | - name: Install Dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install yapf 22 | - name: Style and Formatting Checks 23 | run: | 24 | yapf --diff --recursive . 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build directories 2 | build/ 3 | tests/*/obj-*/ 4 | *.pyc 5 | *.*~ 6 | objdir 7 | *.o 8 | -------------------------------------------------------------------------------- /.style.yapf: -------------------------------------------------------------------------------- 1 | [style] 2 | based_on_style = google 3 | column_limit = 100 4 | spaces_around_default_or_named_assign = True 5 | blank_line_before_nested_class_or_def = False 6 | blank_lines_around_top_level_definition = 1 7 | -------------------------------------------------------------------------------- /.yapfignore: -------------------------------------------------------------------------------- 1 | ambuild/* 2 | ambuild2/database.py 3 | ambuild2/third_party/* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 AlliedModders LLC 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AMBuild is a lightweight build system designed for performance and accuracy. It is geared toward C/C++ projects which require programmatic flexibility in their builds and precise control over C/C++ compiler flags. 2 | 3 | AMBuild requires Python 3.3 or higher. 4 | 5 | For more information, see: https://wiki.alliedmods.net/AMBuild 6 | 7 | # Installation 8 | 9 | ``` 10 | git clone https://github.com/alliedmodders/ambuild 11 | pip install ./ambuild 12 | ``` 13 | 14 | # AMBuild 2 15 | 16 | AMBuild 2 is a highly efficient build system designed to replace ["Alpha"-generation tools][1], such as SCons or Make. It is not a replacement for IDE project files, nor is it a front-end tool for generating other build system files, such as CMake. AMBuild is designed with three features in mind: 17 | 18 | * Accuracy. AMBuild guarantees that you never need to "clean" a build. Incremental builds should always produce the same exact result as a clean build; anything less is asking for trouble, and rebuilds are a waste of developer time. 19 | * Speed. Many build systems need to traverse the entire dependency graph. AMBuild only needs to find which files have changed. In addition, AMBuild will parallelize any independent tasks. 20 | * Programmability. Build scripts are written in Python, affording a great deal of flexibility for describing the build process. 21 | 22 | Build scripts for AMBuild are parsed once upon configuration, and are responsible for defining tasks. If build scripts change, the build is automatically reconfigured. Out of box, build scripts support the following actions: 23 | * C/C++ compilation, linking, .rc compilation, and producing symbol files for symstore/breakpad. 24 | * File copying or symlinking for packaging. 25 | * Arbitrary shell commands. 26 | 27 | # AMBuild 1 28 | 29 | AMBuild 1 was intended as a replacement for build systems such as SCons or Make. Its syntax is easier than Make and handles C/C++ dependencies automatically. Like most build systems, it performs a full recursive search for outdated files, which can make it slower for dependency graphs with many edges. It has no multiprocess support. Also unlike AMBuild 2, the dependency graph is not saved in between builds, which greatly reduces its incremental build accuracy and speed. 30 | C 31 | AMBuild 1 is installed alongside AMBuild 2 for backward compatibility, however it resides in an older namespace and has a completely separate API. 32 | 33 | # Contributing 34 | 35 | AMBuild is written in Python. 36 | 37 | Code is formatted using YAPF. If GitHub tells you there are style issues, you can use "yapf -r -i ." to fix them. You can get YAPF with pip ("pip install yapf"). 38 | 39 | Bugfixes are welcome, including to older API versions. New features are only added to the newest API. 40 | 41 | AlliedModders developers can often be found in IRC (irc.gamesurge.net, #smdevs) if you have questions. 42 | 43 | # References 44 | 45 | [1]: "Build System Rules and Algorithms by Mike Shal" 46 | -------------------------------------------------------------------------------- /ambuild/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild/__init__.py -------------------------------------------------------------------------------- /ambuild/cache.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 sw=2 tw=99 noet: 2 | import os 3 | import pickle 4 | 5 | class Cache: 6 | def __init__(self, path): 7 | self.vars = { } 8 | self.path = path 9 | 10 | def CacheVariable(self, name, value): 11 | self.vars[name] = value 12 | 13 | def WriteCache(self): 14 | f = open(self.path, 'wb') 15 | try: 16 | pickle.dump(self.vars, f) 17 | except Exception as e: 18 | raise e 19 | finally: 20 | f.close() 21 | 22 | def LoadCache(self): 23 | f = open(self.path, 'rb') 24 | try: 25 | self.vars = pickle.load(f) 26 | except Exception as e: 27 | f.close() 28 | raise e 29 | f.close() 30 | 31 | def HasVariable(self, key): 32 | return key in self.vars 33 | 34 | def __getitem__(self, key): 35 | return self.vars[key] 36 | 37 | -------------------------------------------------------------------------------- /ambuild/command.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 sw=2 tw=99 noet: 2 | import os 3 | import sys 4 | import shutil 5 | import subprocess 6 | import ambuild.osutil as osutil 7 | 8 | class Command: 9 | def __init__(self): 10 | self.stderr = None 11 | self.stdout = None 12 | self.failureIsFatal = True 13 | def run(self, master, job): 14 | pass 15 | def spew(self, runner): 16 | if self.stdout != None and len(self.stdout) > 0: 17 | runner.PrintOut(self.stdout) 18 | if self.stderr != None and len(self.stderr) > 0: 19 | runner.PrintOut(self.stderr) 20 | 21 | class SymlinkCommand(Command): 22 | def __init__(self, link, target): 23 | Command.__init__(self) 24 | self.link = link 25 | self.target = target 26 | def run(self, master, job): 27 | master.PrintOut('symlinking {0} as {1}'.format(self.target, self.link)) 28 | try: 29 | os.symlink(self.target, self.link) 30 | except: 31 | master.PrintOut('symlinking failed; copying instead, {0} as {1}'.format(self.target, self.link)) 32 | shutil.copyfile(self.target, self.link) 33 | 34 | class ShellCommand(Command): 35 | def __init__(self, cmdstring, failureIsFatal = True): 36 | Command.__init__(self) 37 | self.cmdstring = cmdstring 38 | self.failureIsFatal = failureIsFatal 39 | def run(self, master, job): 40 | master.PrintOut(self.cmdstring) 41 | args = { 'args': self.cmdstring, 42 | 'stdout': subprocess.PIPE, 43 | 'stderr': subprocess.PIPE, 44 | 'shell': True } 45 | p = subprocess.Popen(**args) 46 | stdout, stderr = p.communicate() 47 | self.stdout = osutil.DecodeConsoleText(sys.stdout, stdout) 48 | self.stderr = osutil.DecodeConsoleText(sys.stderr, stderr) 49 | if p.returncode != 0: 50 | raise Exception('terminated with non-zero exitcode {0}'.format(p.returncode)) 51 | 52 | class DirectCommand(Command): 53 | def __init__(self, argv, exe = None, failureIsFatal = True, env = None): 54 | Command.__init__(self) 55 | self.exe = exe 56 | self.argv = argv 57 | self.failureIsFatal = failureIsFatal 58 | self.env = env 59 | def run(self, runner, job): 60 | runner.PrintOut(' '.join(['"' + i + '"' for i in self.argv])) 61 | args = { 'args': self.argv, 62 | 'stdout': subprocess.PIPE, 63 | 'stderr': subprocess.PIPE, 64 | 'shell': False } 65 | if self.env != None: 66 | args['env'] = self.env 67 | if self.exe != None: 68 | args['executable'] = self.exe 69 | p = subprocess.Popen(**args) 70 | stdout, stderr = p.communicate() 71 | self.stdout = osutil.DecodeConsoleText(sys.stdout, stdout) 72 | self.stderr = osutil.DecodeConsoleText(sys.stderr, stderr) 73 | if p.returncode != 0: 74 | raise Exception('failure: program terminated with non-zero exitcode {0}'.format(p.returncode)) 75 | 76 | def RunDirectCommand(runner, argv, exe = None): 77 | runner.PrintOut(' '.join([i for i in argv])) 78 | args = {'args': argv, 79 | 'stdout': subprocess.PIPE, 80 | 'stderr': subprocess.PIPE, 81 | 'shell': False} 82 | if exe != None: 83 | argv['executable'] = exe 84 | p = subprocess.Popen(**args) 85 | stdout, stderr = p.communicate() 86 | p.stdoutText = osutil.DecodeConsoleText(sys.stdout, stdout) 87 | p.stderrText = osutil.DecodeConsoleText(sys.stderr, stderr) 88 | p.realout = stdout 89 | p.realerr = stderr 90 | return p 91 | 92 | -------------------------------------------------------------------------------- /ambuild/job.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 sw=2 tw=99 noet: 2 | import os 3 | import traceback 4 | import ambuild.worker as worker 5 | from ambuild.cache import Cache 6 | from ambuild.command import Command 7 | 8 | class TaskGroup: 9 | def __init__(self, cmds, mustBeSerial = True): 10 | self.cmds = cmds 11 | self.mustBeSerial = mustBeSerial 12 | 13 | class AsyncRun: 14 | def __init__(self, master, job, task): 15 | self.master = master 16 | self.task = task 17 | self.job = job 18 | def run(self): 19 | spewed = False 20 | try: 21 | self.task.run(self.master, self.job) 22 | spewed = True 23 | self.task.spew(self.master) 24 | except Exception as e: 25 | try: 26 | if not spewed: 27 | self.task.spew(self.master) 28 | except: 29 | pass 30 | raise Exception(str(e) + '\n' + traceback.format_exc()) 31 | 32 | class Job: 33 | def __init__(self, runner, name, workFolder = None): 34 | self.tasks = [] 35 | self.name = name 36 | self.runner = runner 37 | if workFolder == None: 38 | self.workFolder = name 39 | else: 40 | self.workFolder = workFolder 41 | self.cache = Cache(os.path.join(runner.outputFolder, '.ambuild', name + '.cache')) 42 | #ignore if cache file doesnt exist yet 43 | try: 44 | self.cache.LoadCache() 45 | except: 46 | pass 47 | 48 | def CacheVariable(self, key, value): 49 | self.cache.CacheVariable(key, value) 50 | 51 | def HasVariable(self, key): 52 | return self.cache.HasVariable(key) 53 | 54 | def GetVariable(self, key): 55 | return self.cache[key] 56 | 57 | def AddCommand(self, cmd): 58 | if not isinstance(cmd, Command): 59 | raise Exception('task is not a Command') 60 | self.tasks.append(TaskGroup([cmd])) 61 | 62 | def AddCommandGroup(self, cmds, mustBeSerial = True): 63 | if not isinstance(cmds, list): 64 | raise Exception('tasks are not in a list') 65 | self.tasks.append(TaskGroup(cmds, mustBeSerial)) 66 | 67 | def run(self, master): 68 | for group in self.tasks: 69 | if 1: #group.mustBeSerial: 70 | for task in group.cmds: 71 | r = AsyncRun(master, self, task) 72 | try: 73 | r.run() 74 | except Exception as e: 75 | self.cache.WriteCache() 76 | raise e 77 | self.cache.WriteCache() 78 | else: 79 | pool = worker.WorkerPool(master.numCPUs * 4) 80 | tasks = [AsyncRun(master, self, task) for task in group.cmds] 81 | failed = pool.RunJobs(tasks) 82 | self.cache.WriteCache() 83 | if len(failed) > 0: 84 | raise failed[0]['e'] 85 | 86 | -------------------------------------------------------------------------------- /ambuild/osutil.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 sw=2 tw=99 noet: 2 | import os 3 | import sys 4 | import locale 5 | import subprocess 6 | import multiprocessing 7 | 8 | def IsWindows(): 9 | return sys.platform == 'win32' or sys.platform == 'cygwin' 10 | 11 | def IsMac(): 12 | return sys.platform == 'darwin' 13 | 14 | def IsUnixy(): 15 | return sys.platform[0:5] == 'linux' or IsMac() 16 | 17 | def ExecutableSuffix(): 18 | if IsWindows(): 19 | return '.exe' 20 | else: 21 | return '' 22 | 23 | def SharedLibSuffix(): 24 | if IsWindows(): 25 | return '.dll' 26 | elif sys.platform == 'darwin': 27 | return '.dylib' 28 | else: 29 | return '.so' 30 | 31 | def StaticLibSuffix(): 32 | if IsUnixy(): 33 | return '.a' 34 | return '.lib' 35 | 36 | def StaticLibPrefix(): 37 | if IsWindows(): 38 | return '' 39 | else: 40 | return 'lib' 41 | 42 | def DecodeConsoleText(origin, text): 43 | try: 44 | if origin.encoding: 45 | return text.decode(origin.encoding, 'replace') 46 | except: 47 | pass 48 | try: 49 | return text.decode(locale.getpreferredencoding(), 'replace') 50 | except: 51 | pass 52 | return text.decode('utf8', 'replace') 53 | 54 | def WaitForProcess(process): 55 | out, err = process.communicate() 56 | process.stdoutText = DecodeConsoleText(sys.stdout, out) 57 | process.stderrText = DecodeConsoleText(sys.stderr, err) 58 | return process.returncode 59 | 60 | def CreateProcess(argv, executable = None): 61 | pargs = { 'args': argv } 62 | pargs['stdout'] = subprocess.PIPE 63 | pargs['stderr'] = subprocess.PIPE 64 | if executable != None: 65 | pargs['executable'] = executable 66 | try: 67 | process = subprocess.Popen(**pargs) 68 | except: 69 | return None 70 | return process 71 | 72 | def MakePath(*list): 73 | path = os.path.join(*list) 74 | if IsWindows(): 75 | path = path.replace('\\\\', '\\') 76 | return path 77 | 78 | def RemoveFolderAndContents(path): 79 | for file in os.listdir(path): 80 | subpath = os.path.join(path, file) 81 | try: 82 | if os.path.isfile(subpath) or os.path.islink(subpath): 83 | os.unlink(subpath) 84 | elif os.path.isdir(subpath): 85 | RemoveFolderAndContents(subpath) 86 | except: 87 | pass 88 | os.rmdir(path) 89 | 90 | Folders = [] 91 | def PushFolder(path): 92 | Folders.append(os.path.abspath(os.getcwd())) 93 | os.chdir(path) 94 | 95 | def PopFolder(): 96 | os.chdir(Folders.pop()) 97 | 98 | def NumberOfCPUs(): 99 | return multiprocessing.cpu_count() 100 | 101 | def FileExists(file): 102 | if os.path.isfile(file): 103 | GetFileTime(file) 104 | return True 105 | return False 106 | 107 | def GetFileTime(file): 108 | time = os.path.getmtime(file) 109 | return time 110 | 111 | def IsFileNewer(this, that): 112 | this = GetFileTime(this) 113 | if type(that) == str: 114 | that = GetFileTime(that) 115 | return this > that 116 | 117 | -------------------------------------------------------------------------------- /ambuild/runner.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 sw=2 tw=99 noet: 2 | from __future__ import print_function 3 | import sys 4 | import os 5 | import ambuild.osutil as osutil 6 | from ambuild.job import Job 7 | import ambuild.cache as cache 8 | import ambuild.cpp as cpp 9 | from optparse import OptionParser 10 | 11 | def _execfile(file, globals): 12 | exec(compile(open(file).read(), file, 'exec'), globals) 13 | 14 | class Runner: 15 | def __init__(self): 16 | self.jobs = [] 17 | self.options = OptionParser() 18 | self.target = { } 19 | self.numCPUs = osutil.NumberOfCPUs() 20 | self.quietAdd = False 21 | if osutil.IsWindows(): 22 | self.target['platform'] = 'windows' 23 | elif sys.platform.startswith('linux'): 24 | self.target['platform'] = 'linux' 25 | elif sys.platform.startswith('darwin'): 26 | self.target['platform'] = 'darwin' 27 | 28 | def PrintOut(self, text): 29 | print(text) 30 | 31 | def AddJob(self, name, workFolder = None): 32 | if not self.quietAdd: 33 | print('Adding job {0}.'.format(name)) 34 | job = Job(self, name, workFolder) 35 | self.jobs.append(job) 36 | return job 37 | 38 | def ListJobs(self): 39 | print("Listing {0} jobs...".format(len(self.jobs))) 40 | for job in self.jobs: 41 | print(job.name) 42 | 43 | def RunJobs(self, jobs): 44 | for job in jobs: 45 | print('Running job: {0}...'.format(job.name)) 46 | if job.workFolder != None: 47 | workFolder = os.path.join(self.outputFolder, job.workFolder) 48 | if not os.path.isdir(workFolder): 49 | os.makedirs(workFolder) 50 | osutil.PushFolder(workFolder) 51 | try: 52 | job.run(self) 53 | except Exception as e: 54 | print('Job failed: {0}'.format(str(e))) 55 | sys.exit(1) 56 | if job.workFolder != None: 57 | osutil.PopFolder() 58 | print('Completed job: {0}.'.format(job.name)) 59 | 60 | def CallerScript(self, num = 1): 61 | return sys._getframe(num).f_code.co_filename 62 | 63 | def Build(self): 64 | self.mode = 'build' 65 | (options, args) = self.options.parse_args() 66 | argn = len(args) 67 | self.quietAdd = options.list == True or argn > 0 68 | self.outputFolder = os.path.abspath(os.getcwd()) 69 | cacheFolder = os.path.join(self.outputFolder, '.ambuild') 70 | if not os.path.isdir(cacheFolder): 71 | raise Exception('could not find .ambuild folder') 72 | cacheFile = os.path.join(cacheFolder, 'cache') 73 | if not os.path.isfile(cacheFile): 74 | raise Exception('could not find .ambuild cache file') 75 | self.cache = cache.Cache(cacheFile) 76 | self.cache.LoadCache() 77 | self.sourceFolder = self.cache['sourceFolder'] 78 | self.LoadFile(os.path.join(self.sourceFolder, 'AMBuildScript')) 79 | if options.list: 80 | self.ListJobs() 81 | return 82 | jobs = [] 83 | if argn > 0: 84 | # Run jobs in order specified on command line 85 | for j in args: 86 | validJob = False 87 | for job in self.jobs: 88 | if job.name == j: 89 | jobs.append(job) 90 | validJob = True 91 | break 92 | if not validJob: 93 | raise Exception('{0} is not a valid job name'.format(j)) 94 | else: 95 | jobs = self.jobs 96 | self.RunJobs(jobs) 97 | 98 | def Configure(self, folder): 99 | self.mode = 'config' 100 | (options, args) = self.options.parse_args() 101 | self.options = options 102 | self.args = args 103 | self.sourceFolder = os.path.abspath(folder) 104 | self.outputFolder = os.path.abspath(os.getcwd()) 105 | cacheFolder = os.path.join(self.outputFolder, '.ambuild') 106 | if os.path.isdir(cacheFolder): 107 | osutil.RemoveFolderAndContents(cacheFolder) 108 | os.mkdir(cacheFolder) 109 | if not os.path.isdir(cacheFolder): 110 | raise Exception('could not create .ambuild folder') 111 | self.cache = cache.Cache(os.path.join(cacheFolder, 'cache')) 112 | self.cache.CacheVariable('sourceFolder', self.sourceFolder) 113 | self.LoadFile(os.path.join(self.sourceFolder, 'AMBuildScript')) 114 | self.cache.WriteCache() 115 | f = open(os.path.join(self.outputFolder, 'build.py'), 'w') 116 | f.write(""" 117 | # vim: set ts=2 sw=2 tw=99 noet: 118 | import sys 119 | import ambuild.runner as runner 120 | 121 | run = runner.Runner() 122 | run.options.usage = '%prog [options] [job list]' 123 | run.options.add_option('-l', '--list-jobs', action='store_true', dest='list', help='print list of jobs') 124 | run.Build() 125 | """) 126 | 127 | def Include(self, path, xtras = None): 128 | self.LoadFile(os.path.join(self.sourceFolder, path), xtras) 129 | 130 | def LoadFile(self, path, xtras = None): 131 | globals = { 132 | 'AMBuild': self, 133 | 'Cpp': cpp 134 | } 135 | if xtras != None: 136 | globals.update(xtras) 137 | _execfile(path, globals) 138 | 139 | -------------------------------------------------------------------------------- /ambuild/worker.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 sw=2 tw=99 noet: 2 | import threading 3 | 4 | class Worker: 5 | def __call__(self): 6 | while len(self.jobs): 7 | try: 8 | job = self.jobs.pop() 9 | job.run() 10 | except KeyboardInterrupt as ki: 11 | return 12 | except Exception as e: 13 | self.e = e 14 | self.failedJob = job 15 | 16 | class WorkerPool: 17 | def __init__(self, numWorkers): 18 | self.numWorkers = numWorkers 19 | self.workers = [] 20 | for i in range(0, self.numWorkers): 21 | self.workers.append(Worker()) 22 | 23 | def RunJobs(self, jobs): 24 | for w in self.workers: 25 | w.failedJob = None 26 | w.e = None 27 | w.jobs = [] 28 | w.thread = threading.Thread(target = w) 29 | 30 | #Divvy up jobs 31 | num = 0 32 | for i in jobs: 33 | self.workers[num].jobs.append(i) 34 | num = num + 1 35 | if num == self.numWorkers: 36 | num = 0 37 | 38 | #Start up each thread 39 | for w in self.workers: 40 | w.thread.start() 41 | 42 | #Wait for threads to finish 43 | failed = [] 44 | for w in self.workers: 45 | w.thread.join() 46 | if w.failedJob != None or w.e != None: 47 | failed.append({'job': w.failedJob, 'e': w.e}) 48 | 49 | return failed 50 | 51 | -------------------------------------------------------------------------------- /ambuild2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/__init__.py -------------------------------------------------------------------------------- /ambuild2/damage.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | import sys 19 | from ambuild2 import nodetypes 20 | from ambuild2.graph import Graph 21 | 22 | def ComputeSourceDirty(node): 23 | if not os.path.exists(node.path): 24 | return True 25 | 26 | return os.path.getmtime(node.path) != node.stamp 27 | 28 | def ComputeOutputDirty(node): 29 | if not os.path.exists(node.path): 30 | return True 31 | 32 | # If the timestamp on the object file has changed, then one of two things 33 | # happened: 34 | # (1) The build command completed, but the build process crashed, and we 35 | # never got a chance to update the timestamp. 36 | # (2) The file was modified behind our back. 37 | # 38 | # In the first case, our preceding command node will not have been undirtied, 39 | # so we should be able to find our incoming command in the graph. However, 40 | # case #2 breaks that guarantee. To be safe, if the timestamp has changed, 41 | # we mark the node as dirty. 42 | stamp = os.path.getmtime(node.path) 43 | return stamp != node.stamp 44 | 45 | def ComputeDirty(node): 46 | if node.type == nodetypes.Source: 47 | dirty = ComputeSourceDirty(node) 48 | elif node.type == nodetypes.Output: 49 | dirty = ComputeOutputDirty(node) 50 | else: 51 | raise Exception('cannot compute dirty bit for node type: ' + node.type) 52 | return dirty 53 | 54 | def ComputeDamageGraph(database, only_changed = False): 55 | graph = Graph(database) 56 | 57 | def maybe_mkdir(node): 58 | if not os.path.exists(node.path): 59 | graph.create.append(node) 60 | 61 | database.query_mkdir(maybe_mkdir) 62 | 63 | dirty = [] 64 | 65 | def maybe_add_dirty(node): 66 | if ComputeDirty(node): 67 | database.mark_dirty(node) 68 | dirty.append(node) 69 | 70 | database.query_known_dirty(lambda node: dirty.append(node)) 71 | database.query_maybe_dirty(maybe_add_dirty) 72 | 73 | if only_changed: 74 | return dirty 75 | 76 | for entry in dirty: 77 | if entry.type == nodetypes.Output: 78 | # Ensure that our command has been marked as dirty. 79 | incoming = database.query_strong_inputs(entry) 80 | incoming |= database.query_dynamic_inputs(entry) 81 | 82 | # There should really only be one command to generate an output. 83 | if len(incoming) != 1: 84 | sys.stderr.write('Error in dependency graph: an output has multiple inputs.') 85 | sys.stderr.write('Output: {0}'.format(entry.format())) 86 | return None 87 | 88 | for cmd in incoming: 89 | graph.addEntry(cmd) 90 | else: 91 | graph.addEntry(entry) 92 | 93 | graph.finish() 94 | 95 | # Find all leaf commands in the graph and mark them as dirty. This ensures 96 | # that we'll include them in the next damage graph. In theory, all leaf 97 | # commands should *already* be dirty, so this is just in case. 98 | def finish_mark_dirty(entry): 99 | if entry.dirty == nodetypes.NOT_DIRTY: 100 | # Mark this node as dirty in the DB so we don't have to check the 101 | # filesystem next time. 102 | database.mark_dirty(entry) 103 | 104 | graph.for_each_leaf_command(finish_mark_dirty) 105 | 106 | return graph 107 | -------------------------------------------------------------------------------- /ambuild2/frontend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/frontend/__init__.py -------------------------------------------------------------------------------- /ambuild2/frontend/base_generator.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | class BaseGenerator(object): 18 | def __init__(self, cm): 19 | self.cm = cm 20 | 21 | @property 22 | def backend(self): 23 | raise Exception('Must be implemented!') 24 | 25 | def addSymlink(self, context, source, output_path): 26 | raise Exception('Must be implemented!') 27 | 28 | def addFolder(self, context, folder): 29 | raise Exception('Must be implemented!') 30 | 31 | def addCopy(self, context, source, output_path): 32 | raise Exception('Must be implemented!') 33 | 34 | def addShellCommand(self, 35 | context, 36 | inputs, 37 | argv, 38 | outputs, 39 | folder = -1, 40 | dep_type = None, 41 | weak_inputs = [], 42 | shared_outputs = [], 43 | env_data = None): 44 | raise Exception('Must be implemented!') 45 | 46 | def addConfigureFile(self, context, path): 47 | raise Exception('Must be implemented!') 48 | 49 | # The following methods are only needed to implement v2.2 generators. 50 | def newProgramProject(self, context, name): 51 | raise NotImplementedError() 52 | 53 | def newLibraryProject(self, context, name): 54 | raise NotImplementedError() 55 | 56 | def newStaticLibraryProject(self, context, name): 57 | raise NotImplementedError() 58 | -------------------------------------------------------------------------------- /ambuild2/frontend/cloneable.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import collections 18 | import copy 19 | 20 | # An object inheriting from Cloneable will be automatically shallow-copied 21 | # when constructing child build contexts. Use copy.deepcopy to clone. 22 | class Cloneable(object): 23 | def __init__(self): 24 | pass 25 | 26 | class CloneableDict(collections.OrderedDict, Cloneable): 27 | def __init__(self, *args, **kwargs): 28 | super(CloneableDict, self).__init__(*args, **kwargs) 29 | 30 | class CloneableList(list, Cloneable): 31 | def __init__(self, *args, **kwargs): 32 | super(CloneableList, self).__init__(*args, **kwargs) 33 | -------------------------------------------------------------------------------- /ambuild2/frontend/cloneable_test.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=4 ts=8 sw=4 tw=99 et: 2 | import collections 3 | import copy 4 | import unittest 5 | from ambuild2.frontend.cloneable import Cloneable 6 | from ambuild2.frontend.cloneable import CloneableDict 7 | 8 | class CloneableDictTests(unittest.TestCase): 9 | def runTest(self): 10 | obj = CloneableDict() 11 | self.assertTrue(isinstance(obj, Cloneable)) 12 | self.assertTrue(isinstance(obj, collections.OrderedDict)) 13 | 14 | obj['blah'] = [1, 2, 3, 4, 5] 15 | 16 | clone = copy.deepcopy(obj) 17 | self.assertTrue(isinstance(clone, Cloneable)) 18 | self.assertTrue(isinstance(clone, collections.OrderedDict)) 19 | self.assertIsNot(obj, clone) 20 | self.assertIn('blah', clone) 21 | self.assertIsNot(obj['blah'], clone['blah']) 22 | -------------------------------------------------------------------------------- /ambuild2/frontend/context_manager.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import copy 18 | import os 19 | import sys 20 | from ambuild2 import util 21 | 22 | class ContextManager(object): 23 | def __init__(self, sourcePath, buildPath, originalCwd, options, args): 24 | super(ContextManager, self).__init__() 25 | self.sourcePath = sourcePath 26 | self.buildPath = os.path.normpath(buildPath) 27 | self.originalCwd = originalCwd 28 | self.options = options 29 | self.args = args 30 | self.configure_failed = False 31 | self.contextStack_ = [] 32 | self.generator = None 33 | self.db = None 34 | self.refactoring = getattr(options, 'refactor', False) 35 | 36 | # Nonce. 37 | ALWAYS_DIRTY = object() 38 | 39 | @property 40 | def apiVersion(self): 41 | raise Exception('Implement me!') 42 | 43 | def setBackend(self, backend): 44 | self.backend_ = backend 45 | 46 | def pushContext(self, cx): 47 | self.contextStack_.append(cx) 48 | 49 | def popContext(self): 50 | self.contextStack_.pop() 51 | 52 | def compileScript(self, path): 53 | with open(path) as fp: 54 | chars = fp.read() 55 | 56 | # Python 2.6 can't compile() with Windows line endings?!?!!? 57 | chars = chars.replace('\r\n', '\n') 58 | chars = chars.replace('\r', '\n') 59 | 60 | return compile(chars, path, 'exec') 61 | 62 | def Context(self, name): 63 | return AutoContext(self, self.contextStack_[-1], name) 64 | 65 | def generateBuildFiles(self): 66 | build_py = os.path.join(self.buildPath, 'build.py') 67 | with open(build_py, 'w') as fp: 68 | fp.write(""" 69 | #!{exe} 70 | # vim set: ts=8 sts=2 sw=2 tw=99 et: 71 | import sys 72 | from ambuild2 import run 73 | 74 | if not run.CompatBuild(r"{build}"): 75 | sys.exit(1) 76 | """.format(exe = sys.executable, build = self.buildPath)) 77 | 78 | with open(os.path.join(self.buildPath, 'Makefile'), 'w') as fp: 79 | fp.write(""" 80 | all: 81 | "{exe}" "{py}" 82 | """.format(exe = sys.executable, py = build_py)) 83 | 84 | def createGenerator(self, name): 85 | sys.stderr.write('Unrecognized build generator: {}\n'.format(name)) 86 | sys.exit(1) 87 | 88 | # name is None when a generator is already set. 89 | def generate(self, name = None): 90 | if self.generator is None: 91 | self.createGenerator(name) 92 | self.generator.preGenerate() 93 | self.parseBuildScripts() 94 | self.generator.postGenerate() 95 | if self.options.make_scripts: 96 | self.generateBuildFiles() 97 | return True 98 | 99 | @property 100 | def backend(self): 101 | return self.backend_.backend() 102 | -------------------------------------------------------------------------------- /ambuild2/frontend/cpp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/frontend/cpp/__init__.py -------------------------------------------------------------------------------- /ambuild2/frontend/cpp/cpp_rules.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import copy 18 | 19 | DEFAULT_RULES = { 20 | 'family==gcc': { 21 | 'arch==x86': { 22 | 'CFLAGS': ['-m32'], 23 | 'platform==mac': { 24 | 'CFLAGS': ['-arch', 'i386'], 25 | }, 26 | }, 27 | 'arch==x86_64': { 28 | 'CFLAGS': ['-m64'], 29 | 'platform==mac': { 30 | 'CFLAGS': ['-arch', 'x86_64'], 31 | }, 32 | }, 33 | }, 34 | } 35 | 36 | class RulesParser(object): 37 | def __init__(self): 38 | self.rules = copy.deepcopy(DEFAULT_RULES) 39 | self.inputs_ = None 40 | self.props_ = None 41 | 42 | def parse(self, inputs): 43 | self.inputs_ = inputs 44 | self.props_ = {} 45 | for key, value in self.rules.items(): 46 | self.parse_property(key, value) 47 | return self.props_ 48 | 49 | def parse_property(self, key, value): 50 | if isinstance(value, dict): 51 | self.parse_section(key, value) 52 | else: 53 | self.add_prop(key, value) 54 | 55 | def add_prop(self, key, value): 56 | if key not in self.props_: 57 | self.props_[key] = value 58 | return 59 | if isinstance(value, list): 60 | self.props_[key].extend(value) 61 | else: 62 | self.props_[key] = value 63 | 64 | def parse_section(self, key, value): 65 | if '==' in key: 66 | op = lambda x, y: x == y 67 | parts = key.split('==') 68 | elif '!=' in key: 69 | op = lambda x, y: x != y 70 | parts = key.split('!=') 71 | else: 72 | raise Exception('Subsections must have an == or != operator') 73 | 74 | parts = [part.strip() for part in parts] 75 | if len(parts) != 2 or not len(parts[0]) or not len(parts[1]): 76 | raise Exception('Invalid operator or multiple operators, expected two components') 77 | 78 | if parts[0] not in self.inputs_: 79 | raise Exception('Unknown rule variable "{}"'.format(parts[0])) 80 | if not op(self.inputs_[parts[0]], parts[1]): 81 | return 82 | 83 | for sub_key, sub_value in sorted(value.items()): 84 | self.parse_property(sub_key, sub_value) 85 | -------------------------------------------------------------------------------- /ambuild2/frontend/cpp/cpp_rules_test.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import unittest 18 | from ambuild2.frontend.cpp.cpp_rules import RulesParser 19 | 20 | TestRules = { 21 | 'family==gcc': { 22 | 'arch==x86': { 23 | 'CFLAGS': ['-m32'], 24 | 'platform==mac': { 25 | 'CFLAGS': ['-arch', 'i386'], 26 | }, 27 | }, 28 | 'arch==x86_64': { 29 | 'CFLAGS': ['-m64'], 30 | }, 31 | } 32 | } 33 | 34 | class IsSubPathTests(unittest.TestCase): 35 | def runTest(self): 36 | rp = RulesParser() 37 | rp.rules = TestRules 38 | props = rp.parse({ 39 | 'family': 'gcc', 40 | 'arch': 'x86_64', 41 | }) 42 | self.assertIn('CFLAGS', props) 43 | self.assertEqual(props['CFLAGS'], ['-m64']) 44 | 45 | props = rp.parse({ 46 | 'family': 'msvc', 47 | 'arch': 'x86_64', 48 | }) 49 | self.assertEqual(props, {}) 50 | 51 | props = rp.parse({ 52 | 'family': 'gcc', 53 | 'arch': 'x86', 54 | 'platform': 'mac', 55 | }) 56 | self.assertIn('CFLAGS', props) 57 | self.assertEqual(props['CFLAGS'], ['-m32', '-arch', 'i386']) 58 | -------------------------------------------------------------------------------- /ambuild2/frontend/cpp/cpp_utils.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import uuid 18 | 19 | def CreateUnifiedHeader(header_guard, sources): 20 | text = "" 21 | text += "/* AUTO-GENERATED: DO NOT EDIT */\n" 22 | text += "#ifndef {}\n".format(header_guard) 23 | text += "#define {}\n".format(header_guard) 24 | for source in sources: 25 | text += "# include <{}>\n".format(source) 26 | text += "#endif /* {} */\n".format(header_guard) 27 | return text 28 | 29 | def CreateSingleIncludeSource(header_name): 30 | text = "" 31 | text += "/* AUTO-GENERATED: DO NOT EDIT */\n" 32 | text += "#include \"{}\"\n".format(header_name) 33 | return text -------------------------------------------------------------------------------- /ambuild2/frontend/paths.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | from ambuild2 import util 19 | 20 | # Given an optional contextual folder and a folder path, compute the full 21 | # relative path, erroring if it's outside the build folder. 22 | # 23 | # The parent folder and resolved folder are returned as a tuple. 24 | def ResolveFolder(parent, folder): 25 | parent_path = '' 26 | if parent: 27 | parent_path = parent.path 28 | path = os.path.normpath(os.path.join(parent_path, folder)) 29 | 30 | if path.startswith('..'): 31 | util.con_err(util.ConsoleRed, 'Output path ', util.ConsoleBlue, path, util.ConsoleRed, 32 | ' is outside the build folder!', util.ConsoleNormal) 33 | raise Exception('Cannot generate folders outside the build folder') 34 | 35 | return parent_path, path 36 | 37 | def Join(*nodes): 38 | paths = [] 39 | for node in nodes: 40 | if node is None: 41 | continue 42 | if util.IsString(node): 43 | paths.append(node) 44 | else: 45 | paths.append(node.path) 46 | return os.path.join(*paths) 47 | 48 | def IsSubPath(other, folder, path_module = os.path): 49 | other = path_module.abspath(other) 50 | folder = path_module.abspath(folder) 51 | 52 | drive1, _ = path_module.splitdrive(other) 53 | drive2, _ = path_module.splitdrive(folder) 54 | if drive1 != drive2: 55 | return False 56 | 57 | relative = path_module.relpath(other, folder) 58 | if relative.startswith( 59 | os.pardir 60 | ): # Short-circuit on '..' -OR- '../', this is always pointing outside of "folder". 61 | return False 62 | elif relative.startswith( 63 | os.curdir): # Short-circuit on '.' -OR- './', this is always pointing to a child item. 64 | return True 65 | 66 | return other.startswith(folder) # In most cases, we shouldn't ever arrive to this point. 67 | -------------------------------------------------------------------------------- /ambuild2/frontend/paths_test.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=4 ts=8 sw=4 tw=99 et: 2 | import ntpath 3 | import unittest 4 | from ambuild2.frontend import paths 5 | 6 | class IsSubPathTests(unittest.TestCase): 7 | def runTest(self): 8 | self.assertEqual(paths.IsSubPath("/a/b/c", "/a"), True) 9 | self.assertEqual(paths.IsSubPath("/t/b/c", "/a"), False) 10 | self.assertEqual(paths.IsSubPath("t", "./"), True) 11 | self.assertEqual(paths.IsSubPath(r"C:\blah", "C:\\", ntpath), True) 12 | self.assertEqual(paths.IsSubPath(r"C:\blah", "D:\\", ntpath), False) 13 | -------------------------------------------------------------------------------- /ambuild2/frontend/proxy.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | # AttributeProxy objects will appear to have all the attributes of an inner 19 | # object, as well as their own attributes. Locally set attributes will 20 | # override wrapped ones. This is basically how JavaScript prototype-based 21 | # inheritance works. 22 | class AttributeProxy(object): 23 | def __init__(self, wrapped_obj): 24 | self._wrapped_obj = wrapped_obj 25 | self._own_attrs = set() 26 | 27 | def __getattr__(self, name): 28 | return getattr(self._wrapped_obj, name) 29 | 30 | def __setattr__(self, name, value): 31 | if name in ['_own_attrs_', '_wrapped_obj']: 32 | return object.__setattr__(self, name, value) 33 | 34 | own_attrs = getattr(self, '_own_attrs', None) 35 | if own_attrs is not None: 36 | own_attrs.add(name) 37 | return object.__setattr__(self, name, value) 38 | 39 | def __dir__(self): 40 | me = set(dir(super(AttributeProxy, self))) | set(self.__dict__.keys()) 41 | wrapped = set(dir(self._wrapped_obj)) 42 | return sorted(list(me | wrapped)) 43 | -------------------------------------------------------------------------------- /ambuild2/frontend/proxy_test.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=4 ts=8 sw=4 tw=99 et: 2 | import unittest 3 | from ambuild2.frontend.proxy import AttributeProxy 4 | 5 | class InnerObject(object): 6 | def __init__(self): 7 | self.x = 10 8 | self.y = 20 9 | 10 | class AttributeProxyTests(unittest.TestCase): 11 | def runTest(self): 12 | inner = InnerObject() 13 | outer = AttributeProxy(inner) 14 | 15 | self.assertEqual(outer.x, 10) 16 | self.assertEqual(outer.y, 20) 17 | 18 | inner.z = 30 19 | self.assertEqual(outer.z, 30) 20 | 21 | outer.a = 10 22 | self.assertFalse(hasattr(inner, 'a')) 23 | 24 | self.assertTrue(hasattr(outer, 'a')) 25 | self.assertTrue(hasattr(outer, 'x')) 26 | self.assertTrue(hasattr(outer, 'y')) 27 | self.assertTrue(hasattr(outer, 'z')) 28 | 29 | self.assertIn('a', dir(outer)) 30 | self.assertIn('x', dir(outer)) 31 | self.assertIn('y', dir(outer)) 32 | self.assertIn('z', dir(outer)) 33 | -------------------------------------------------------------------------------- /ambuild2/frontend/system.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | from ambuild2 import util 18 | 19 | class System(object): 20 | def __init__(self, platform, arch, subarch = '', abi = ''): 21 | super(System, self).__init__() 22 | self.platform_ = platform 23 | self.arch_ = arch 24 | self.subarch_ = subarch 25 | self.abi_ = abi 26 | 27 | @property 28 | def platform(self): 29 | return self.platform_ 30 | 31 | @property 32 | def arch(self): 33 | return self.arch_ 34 | 35 | @property 36 | def subarch(self): 37 | return self.subarch_ 38 | 39 | @property 40 | def abi(self): 41 | return self.abi_ 42 | 43 | @property 44 | def triple(self): 45 | suffix = '' 46 | if self.abi: 47 | suffix += '-' + self.abi 48 | return '{}-{}{}{}'.format(self.platform, self.arch, self.subarch, suffix) 49 | 50 | System.Host = System(util.Platform(), util.Architecture, util.SubArch, util.DetectHostAbi()) 51 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/frontend/v2_0/__init__.py -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/amb2_gen.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | from ambuild2 import nodetypes 19 | from ambuild2 import util 20 | from ambuild2.frontend import amb2_gen 21 | from ambuild2.frontend.v2_0.cpp import detect 22 | 23 | class Generator(amb2_gen.Generator): 24 | def __init__(self, cm): 25 | super(Generator, self).__init__(cm) 26 | self.compiler = None 27 | 28 | def copyBuildVars(self, vars): 29 | if not self.compiler: 30 | return 31 | 32 | # Save any environment variables that are relevant to the build. 33 | compilers = [ 34 | ('cc', self.compiler.cc), 35 | ('cxx', self.compiler.cxx), 36 | ] 37 | for prefix, comp in compilers: 38 | for prop_name in comp.extra_props: 39 | key = '{0}_{1}'.format(prefix, prop_name) 40 | vars[key] = comp.extra_props[prop_name] 41 | 42 | def detectCompilers(self): 43 | if not self.compiler: 44 | with util.FolderChanger(self.cacheFolder): 45 | self.base_compiler = detect.DetectCxx(os.environ, self.cm.options) 46 | self.compiler = self.base_compiler.clone() 47 | 48 | return self.compiler 49 | 50 | def addCxxObjTask(self, cx, shared_outputs, folder, obj): 51 | cxxData = {'argv': obj.argv, 'type': obj.behavior} 52 | _, (cxxNode,) = self.addCommand(context = cx, 53 | weak_inputs = obj.sourcedeps, 54 | inputs = [obj.inputObj], 55 | outputs = [obj.outputFile], 56 | node_type = nodetypes.Cxx, 57 | folder = folder, 58 | data = cxxData, 59 | shared_outputs = shared_outputs, 60 | env_data = obj.env_data) 61 | return cxxNode 62 | 63 | def addCxxRcTask(self, cx, folder, obj): 64 | rcData = { 65 | 'cl_argv': obj.cl_argv, 66 | 'rc_argv': obj.rc_argv, 67 | } 68 | _, (_, rcNode) = self.addCommand(context = cx, 69 | weak_inputs = obj.sourcedeps, 70 | inputs = [obj.inputObj], 71 | outputs = [obj.preprocFile, obj.outputFile], 72 | node_type = nodetypes.Rc, 73 | folder = folder, 74 | data = rcData, 75 | env_data = obj.env_data) 76 | return rcNode 77 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/context.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | import sys, copy 19 | from ambuild2 import util 20 | 21 | # AMBuild 2 scripts are parsed recursively. Each script is supplied with a 22 | # "builder" object, which maps to a Context object. Each script gets its own 23 | # context. The context describes the parent build file generator, the local 24 | # input and output folders, and the global compiler that was detected in the 25 | # root script (if any). 26 | # 27 | # Contexts form a tree that matches the build script hierarchy. This can be 28 | # utilized by backends for minimal reparsing and DAG updates when build 29 | # scripts change. 30 | 31 | class ConfigureException(Exception): 32 | def __init__(self, *args, **kwargs): 33 | super(ConfigureException, self).__init__(*args, **kwargs) 34 | 35 | class Context(object): 36 | def __init__(self, cm, parent, script): 37 | self.cm = cm 38 | self.generator = cm.generator 39 | self.parent = parent 40 | self.script = script 41 | self.compiler = None 42 | 43 | if parent: 44 | self.compiler = parent.compiler.clone() 45 | 46 | # By default, all generated files for a build script are placed in a path 47 | # matching its layout in the source tree. 48 | path, name = os.path.split(script) 49 | if parent: 50 | self.currentSourcePath = os.path.join(parent.currentSourcePath, path) 51 | self.currentSourceFolder = os.path.join(parent.currentSourceFolder, path) 52 | self.buildFolder = os.path.join(parent.buildFolder, path) 53 | else: 54 | self.currentSourcePath = self.cm.sourcePath 55 | self.currentSourceFolder = '' 56 | self.buildFolder = '' 57 | self.buildScript = os.path.join(self.currentSourceFolder, name) 58 | self.localFolder_ = self.buildFolder 59 | 60 | # Root source folder. 61 | @property 62 | def sourcePath(self): 63 | return self.cm.sourcePath 64 | 65 | @property 66 | def options(self): 67 | return self.cm.options 68 | 69 | @property 70 | def buildPath(self): 71 | return self.cm.buildPath 72 | 73 | # In build systems with dependency graphs, this can return a node 74 | # representing buildFolder. Otherwise, it returns buildFolder. 75 | @property 76 | def localFolder(self): 77 | return self.generator.getLocalFolder(self) 78 | 79 | @property 80 | def target_platform(self): 81 | return self.cm.target_platform 82 | 83 | @property 84 | def host_platform(self): 85 | return self.cm.host_platform 86 | 87 | @property 88 | def originalCwd(self): 89 | return self.cm.originalCwd 90 | 91 | @property 92 | def backend(self): 93 | return self.generator.backend 94 | 95 | def SetBuildFolder(self, folder): 96 | if folder == '/' or folder == '.' or folder == './': 97 | self.buildFolder = '' 98 | else: 99 | self.buildFolder = os.path.normpath(folder) 100 | self.localFolder_ = self.buildFolder 101 | 102 | def DetectCompilers(self): 103 | if not self.compiler: 104 | self.compiler = self.generator.detectCompilers().clone() 105 | return self.compiler 106 | 107 | def ImportScript(self, file, vars = None): 108 | return self.cm.importScript(self, file, vars or {}) 109 | 110 | def RunScript(self, file, vars = None): 111 | return self.cm.evalScript(file, vars or {}) 112 | 113 | def RunBuildScripts(self, files, vars = None): 114 | if util.IsString(files): 115 | self.cm.evalScript(files, vars or {}) 116 | else: 117 | for script in files: 118 | self.cm.evalScript(script, vars) 119 | 120 | def Add(self, taskbuilder): 121 | taskbuilder.finish(self) 122 | return taskbuilder.generate(self.generator, self) 123 | 124 | def AddSource(self, source_path): 125 | return self.generator.addSource(self, source_path) 126 | 127 | def AddSymlink(self, source, output_path): 128 | return self.generator.addSymlink(self, source, output_path) 129 | 130 | def AddFolder(self, folder): 131 | return self.generator.addFolder(self, folder) 132 | 133 | def AddCopy(self, source, output_path): 134 | return self.generator.addCopy(self, source, output_path) 135 | 136 | def AddCommand(self, 137 | inputs, 138 | argv, 139 | outputs, 140 | folder = -1, 141 | dep_type = None, 142 | weak_inputs = [], 143 | shared_outputs = []): 144 | return self.generator.addShellCommand(self, 145 | inputs, 146 | argv, 147 | outputs, 148 | folder = folder, 149 | dep_type = dep_type, 150 | weak_inputs = weak_inputs, 151 | shared_outputs = shared_outputs) 152 | 153 | def AddConfigureFile(self, path): 154 | return self.generator.addConfigureFile(self, path) 155 | 156 | def Context(self, name): 157 | return self.cm.Context(name) 158 | 159 | class AutoContext(Context): 160 | def __init__(self, gen, parent, file): 161 | super(AutoContext, self).__init__(gen, parent, file) 162 | 163 | def __enter__(self): 164 | self.cm.pushContext(self) 165 | return self 166 | 167 | def __exit__(self, type, value, traceback): 168 | self.cm.popContext() 169 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/context_manager.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | import sys, copy 19 | from ambuild2 import util 20 | from ambuild2.frontend import context_manager 21 | from ambuild2.frontend.v2_0.context import AutoContext 22 | from ambuild2.frontend.v2_0.context import Context 23 | from ambuild2.frontend.version import Version 24 | 25 | class ContextManager(context_manager.ContextManager): 26 | def __init__(self, sourcePath, buildPath, originalCwd, options, args): 27 | super(ContextManager, self).__init__(sourcePath, buildPath, originalCwd, options, args) 28 | self.contextStack_.append(None) 29 | 30 | # This is a hack... if we ever do cross-compiling or something, we'll have 31 | # to change this. 32 | self.host_platform = util.Platform() 33 | self.target_platform = util.Platform() 34 | 35 | @property 36 | def apiVersion(self): 37 | return Version('2.0') 38 | 39 | def parseBuildScripts(self): 40 | root = os.path.join(self.sourcePath, 'AMBuildScript') 41 | self.evalScript(root) 42 | 43 | def importScript(self, context, file, vars = None): 44 | path = os.path.normpath(os.path.join(context.sourcePath, file)) 45 | self.generator.addConfigureFile(context, path) 46 | 47 | new_vars = copy.copy(vars or {}) 48 | new_vars['builder'] = context 49 | 50 | code = self.compileScript(path) 51 | exec(code, new_vars) 52 | 53 | obj = util.Expando() 54 | for key in new_vars: 55 | setattr(obj, key, new_vars[key]) 56 | return obj 57 | 58 | def Context(self, name): 59 | return AutoContext(self, self.contextStack_[-1], name) 60 | 61 | def evalScript(self, file, vars = None): 62 | file = os.path.normpath(file) 63 | 64 | cx = Context(self, self.contextStack_[-1], file) 65 | self.pushContext(cx) 66 | 67 | full_path = os.path.join(self.sourcePath, cx.buildScript) 68 | 69 | self.generator.addConfigureFile(cx, full_path) 70 | 71 | new_vars = copy.copy(vars or {}) 72 | new_vars['builder'] = cx 73 | 74 | # Run it. 75 | rvalue = None 76 | code = self.compileScript(full_path) 77 | 78 | exec(code, new_vars) 79 | 80 | if 'rvalue' in new_vars: 81 | rvalue = new_vars['rvalue'] 82 | del new_vars['rvalue'] 83 | 84 | self.popContext() 85 | return rvalue 86 | 87 | def getLocalFolder(self, context): 88 | return context.localFolder_ 89 | 90 | def createGenerator(self, name): 91 | if name == 'vs': 92 | from ambuild2.frontend.v2_0.vs.gen import Generator 93 | self.generator = Generator(self) 94 | elif name == 'ambuild2': 95 | from ambuild2.frontend.v2_0.amb2_gen import Generator 96 | self.generator = Generator(self) 97 | else: 98 | return super(ContextManager, self).createGenerator(name) 99 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/cpp/__init__.py: -------------------------------------------------------------------------------- 1 | from ambuild2.frontend.v2_0.cpp.compilers import Compiler 2 | from ambuild2.frontend.v2_0.cpp.builders import CppNodes 3 | from ambuild2.frontend.v2_0.cpp.builders import Dep 4 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/cpp/compilers.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import copy 18 | import subprocess 19 | from ambuild2.frontend.v2_0.cpp import builders 20 | 21 | # Base compiler object. 22 | class Compiler(object): 23 | EnvVars = ['CFLAGS', 'CXXFLAGS', 'CC', 'CXX'] 24 | 25 | attrs = [ 26 | 'includes', # C and C++ include paths 27 | 'cxxincludes', # C++-only include paths 28 | 'cflags', # C and C++ compiler flags 29 | 'cxxflags', # C++-only compiler flags 30 | 'defines', # C and C++ #defines 31 | 'cxxdefines', # C++-only #defines 32 | 'c_only_flags', # Flags for C but not C++. 33 | 'rcdefines', # Resource Compiler (RC) defines 34 | 35 | # Link flags. If any members are not strings, they will be interpreted as 36 | # Dep entries created from BinaryBuilder. 37 | 'linkflags', 38 | 39 | # An array of objects to link, after all link flags have been specified. 40 | # Entries may either be strings containing a path, or Dep entries created 41 | # from BinaryBuilder. 42 | 'postlink', 43 | 44 | # An array of nodes which should be weak dependencies on each source 45 | # compilation command. 46 | 'sourcedeps', 47 | ] 48 | 49 | def __init__(self, options = None): 50 | if getattr(options, 'symbol_files', False): 51 | self.debuginfo = 'separate' 52 | else: 53 | self.debuginfo = 'bundled' 54 | for attr in self.attrs: 55 | setattr(self, attr, []) 56 | 57 | def inherit(self, other): 58 | self.debuginfo = other.debuginfo 59 | for attr in self.attrs: 60 | setattr(self, attr, copy.copy(getattr(other, attr))) 61 | 62 | def clone(self): 63 | raise Exception('Must be implemented!') 64 | 65 | def like(self, name): 66 | raise Exception('Must be implemented!') 67 | 68 | @property 69 | def vendor(self): 70 | raise Exception('Must be implemented!') 71 | 72 | @property 73 | def version(self): 74 | raise Exception('Must be implemented!') 75 | 76 | def Program(self, name): 77 | raise Exception('Must be implemented!') 78 | 79 | def Library(self, name): 80 | raise Exception('Must be implemented!') 81 | 82 | def StaticLibrary(self, name): 83 | raise Exception('Must be implemented!') 84 | 85 | @staticmethod 86 | def Dep(text, node = None): 87 | return builders.Dep(text, node) 88 | 89 | class CxxCompiler(Compiler): 90 | def __init__(self, cc, cxx, options = None): 91 | super(CxxCompiler, self).__init__(options) 92 | 93 | # Accesssing these attributes through the API is deprecated. 94 | self.cc = cc 95 | self.cxx = cxx 96 | self.found_pkg_config_ = False 97 | 98 | def clone(self): 99 | cc = CxxCompiler(self.cc, self.cxx) 100 | cc.inherit(self) 101 | return cc 102 | 103 | def Program(self, name): 104 | return builders.Program(self.clone(), name) 105 | 106 | def Library(self, name): 107 | return builders.Library(self.clone(), name) 108 | 109 | def StaticLibrary(self, name): 110 | return builders.StaticLibrary(self.clone(), name) 111 | 112 | def ProgramProject(self, name): 113 | return builders.Project(builders.Program, self.clone(), name) 114 | 115 | def LibraryProject(self, name): 116 | return builders.Project(builders.Library, self.clone(), name) 117 | 118 | def StaticLibraryProject(self, name): 119 | return builders.Project(builders.StaticLibrary, self.clone(), name) 120 | 121 | # These functions use |cxx|, because we expect the vendors to be the same 122 | # across |cc| and |cxx|. 123 | 124 | # Returns whether this compiler acts like another compiler. Available names 125 | # are: msvc, gcc, icc, sun, clang 126 | def like(self, name): 127 | return self.cxx.like(name) 128 | 129 | # Returns the vendor name (msvc, gcc, icc, sun, clang) 130 | @property 131 | def vendor(self): 132 | return self.cxx.name 133 | 134 | # Returns the version of the compiler. The return value is an object that 135 | # can be compared against other versions, for example: 136 | # 137 | # compiler.version >= '4.8.3' 138 | # 139 | @property 140 | def version(self): 141 | return self.cxx.versionObject 142 | 143 | # Returns a list containing the program name and arguments used to invoke the compiler. 144 | @property 145 | def argv(self): 146 | return self.cxx.command.split(' ') 147 | 148 | # Returns the debuginfo modulo what the underlying vendor's compiler supports. 149 | @property 150 | def debug_symbols(self): 151 | return self.cxx.parse_debuginfo(self.debuginfo) 152 | 153 | # Internal API. 154 | def nameForStaticLibrary(self, name): 155 | return self.cxx.nameForStaticLibrary(name) 156 | 157 | def nameForSharedLibrary(self, name): 158 | return self.cxx.nameForSharedLibrary(name) 159 | 160 | def nameForExecutable(self, name): 161 | return self.cxx.nameForExecutable(name) 162 | 163 | def run_pkg_config(self, argv): 164 | output = subprocess.check_output(args = ['pkg-config'] + argv) 165 | return [item.strip() for item in output.strip().split(' ') if item.strip() != ''] 166 | 167 | # Helper for running pkg-config. 168 | def pkg_config(self, pkg, link = 'dynamic'): 169 | if not self.found_pkg_config_: 170 | try: 171 | self.run_pkg_config(['--version']) 172 | self.found_pkg_config = True 173 | except: 174 | raise Exception('failed to find pkg-config!') 175 | 176 | for include in self.run_pkg_config(['--cflags-only-I', pkg]): 177 | if include.startswith('-I'): 178 | self.includes += [include[2:].strip()] 179 | else: 180 | self.cflags += [include] 181 | self.cflags += self.run_pkg_config(['--cflags-only-other', pkg]) 182 | 183 | if link == 'dynamic': 184 | self.linkflags += self.run_pkg_config(['--libs', pkg]) 185 | elif link == 'static': 186 | self.linkflags += self.run_pkg_config(['--libs', '--static', pkg]) 187 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/cpp/vendors.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | import os 19 | from ambuild2 import util 20 | from ambuild2.frontend.version import Version 21 | 22 | class Vendor(object): 23 | def __init__(self, name, version, behavior, command, objSuffix): 24 | self.name = name 25 | self.version = version 26 | self.behavior = behavior 27 | self.command = command 28 | self.objSuffix = objSuffix 29 | self.debuginfo_argv = [] 30 | self.extra_props = {} 31 | self.versionObject = Version('{0}-{1}'.format(name, self.version)) 32 | 33 | def nameForExecutable(self, name): 34 | return name + util.ExecutableSuffix 35 | 36 | def nameForSharedLibrary(self, name): 37 | return name + util.SharedLibSuffix 38 | 39 | def nameForStaticLibrary(self, name): 40 | return util.StaticLibPrefix + name + util.StaticLibSuffix 41 | 42 | class MSVC(Vendor): 43 | def __init__(self, command, version): 44 | super(MSVC, self).__init__('msvc', version, 'msvc', command, '.obj') 45 | self.definePrefix = '/D' 46 | self.debuginfo_argv = ['/Zi'] 47 | if int(self.version) >= 1800: 48 | self.debuginfo_argv += ['/FS'] 49 | 50 | def like(self, name): 51 | return name == 'msvc' 52 | 53 | def parse_debuginfo(self, debuginfo): 54 | if debuginfo == 'bundled': 55 | return 'separate' 56 | return debuginfo 57 | 58 | @staticmethod 59 | def IncludePath(outputPath, includePath): 60 | # Hack - try and get a relative path because CL, with either 61 | # /Zi or /ZI, combined with subprocess, apparently tries and 62 | # looks for paths like c:\bleh\"c:\bleh" <-- wtf 63 | # .. this according to Process Monitor 64 | outputPath = os.path.normcase(outputPath) 65 | includePath = os.path.normcase(includePath) 66 | outputDrive = os.path.splitdrive(outputPath)[0] 67 | includeDrive = os.path.splitdrive(includePath)[0] 68 | if outputDrive == includeDrive: 69 | return os.path.relpath(includePath, outputPath) 70 | return includePath 71 | 72 | def formatInclude(self, outputPath, includePath): 73 | return ['/I', self.IncludePath(outputPath, includePath)] 74 | 75 | def preprocessArgs(self, sourceFile, outFile): 76 | return ['/showIncludes', '/nologo', '/P', '/c', sourceFile, '/Fi' + outFile] 77 | 78 | def objectArgs(self, sourceFile, objFile): 79 | return ['/showIncludes', '/nologo', '/c', sourceFile, '/Fo' + objFile] 80 | 81 | class CompatGCC(Vendor): 82 | def __init__(self, name, command, version): 83 | super(CompatGCC, self).__init__(name, version, 'gcc', command, '.o') 84 | parts = version.split('.') 85 | self.majorVersion = int(parts[0]) 86 | self.minorVersion = int(parts[1]) 87 | self.definePrefix = '-D' 88 | 89 | def formatInclude(self, outputPath, includePath): 90 | return ['-I', os.path.normpath(includePath)] 91 | 92 | def objectArgs(self, sourceFile, objFile): 93 | return ['-H', '-c', sourceFile, '-o', objFile] 94 | 95 | def parse_debuginfo(self, debuginfo): 96 | return debuginfo 97 | 98 | class GCC(CompatGCC): 99 | def __init__(self, command, version): 100 | super(GCC, self).__init__('gcc', command, version) 101 | self.debuginfo_argv = ['-g3', '-ggdb3'] 102 | 103 | def like(self, name): 104 | return name == 'gcc' 105 | 106 | class Clang(CompatGCC): 107 | def __init__(self, vendor_name, command, version): 108 | super(Clang, self).__init__(vendor_name, command, version) 109 | self.name = 'clang' # Rewrite name to just 'clang' to make things easier. 110 | self.vendor_name = vendor_name 111 | self.debuginfo_argv = ['-g3'] 112 | 113 | def like(self, name): 114 | return name == 'gcc' or name == 'clang' or name == self.vendor_name 115 | 116 | class Emscripten(Clang): 117 | def __init__(self, command, version): 118 | super(Emscripten, self).__init__('emscripten', command, version) 119 | self.name = 'emscripten' 120 | 121 | def like(self, name): 122 | if name == 'emscripten': 123 | return True 124 | return super(Emscripten, self).like(name) 125 | 126 | def nameForExecutable(self, name): 127 | return name + '.js' 128 | 129 | class SunPro(Vendor): 130 | def __init__(self, command, version): 131 | super(SunPro, self).__init__('sun', version, 'sun', command, '.o') 132 | self.definePrefix = '-D' 133 | self.debuginfo_argv = ['-g3'] 134 | 135 | def formatInclude(self, outputPath, includePath): 136 | return ['-I', os.path.normpath(includePath)] 137 | 138 | def objectArgs(self, sourceFile, objFile): 139 | return ['-H', '-c', sourceFile, '-o', objFile] 140 | 141 | def like(self, name): 142 | return name == 'sun' 143 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/prep.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can Headeristribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os, sys 18 | import platform 19 | from ambuild2 import util 20 | from optparse import OptionParser, Values, SUPPRESS_HELP 21 | 22 | class Preparer(object): 23 | def __init__(self, sourcePath, buildPath): 24 | self.sourcePath = sourcePath 25 | self.buildPath = buildPath 26 | self.host_platform = util.Platform() 27 | self.target_platform = util.Platform() 28 | 29 | self.options = OptionParser("usage: %prog [options]") 30 | self.options.add_option("-g", 31 | "--gen", 32 | type = "string", 33 | dest = "generator", 34 | default = "ambuild2", 35 | help = "Build system generator to use. See --list-gen") 36 | self.options.add_option("--list-gen", 37 | action = "store_true", 38 | dest = "list_gen", 39 | default = False, 40 | help = "List available build system generators, then exit.") 41 | self.options.add_option( 42 | "--make-scripts", 43 | action = "store_true", 44 | dest = "make_scripts", 45 | default = False, 46 | help = "Generate extra command-line files for building (build.py, Makefile).") 47 | self.options.add_option("--no-color", 48 | action = "store_true", 49 | dest = "no_color", 50 | default = False, 51 | help = "Disable color output in the terminal.") 52 | self.options.add_option( 53 | "--symbol-files", 54 | action = "store_true", 55 | dest = "symbol_files", 56 | default = False, 57 | help = "Split debugging symbols from binaries into separate symbol files.") 58 | 59 | # Generator specific options. 60 | self.options.add_option("--vs-version", 61 | type = "string", 62 | dest = "vs_version", 63 | default = "12", 64 | help = SUPPRESS_HELP) 65 | self.options.add_option("--vs-split", 66 | action = 'store_true', 67 | dest = "vs_split", 68 | default = False, 69 | help = SUPPRESS_HELP) 70 | 71 | @staticmethod 72 | def default_build_folder(prep): 73 | return 'obj-' + util.Platform() + '-' + platform.machine() 74 | 75 | def Configure(self): 76 | v_options, args = self.options.parse_args() 77 | 78 | # In order to support pickling, we need to rewrite |options| to not use 79 | # optparse.Values, since its implementation changes across Python versions. 80 | options = util.Expando() 81 | ignore_attrs = set(dir(Values)) 82 | for attr in dir(v_options): 83 | if attr in ignore_attrs: 84 | continue 85 | setattr(options, attr, getattr(v_options, attr)) 86 | 87 | if options.list_gen: 88 | print('Available build system generators:') 89 | print(' {0:24} - AMBuild 2 (default)'.format('ambuild2')) 90 | print(' {0:24} - Visual Studio'.format('vs')) 91 | print('') 92 | print('Extra options:') 93 | print(' --vs-version=N Visual Studio: IDE version (2010 or 10 default)') 94 | print( 95 | ' --vs-split Visual Studio: generate one project file per configuration' 96 | ) 97 | sys.exit(0) 98 | 99 | if options.no_color: 100 | util.DisableConsoleColors() 101 | 102 | source_abspath = os.path.normpath(os.path.abspath(self.sourcePath)) 103 | build_abspath = os.path.normpath(os.path.abspath(self.buildPath)) 104 | if source_abspath == build_abspath: 105 | if util.IsString(self.default_build_folder): 106 | objfolder = self.default_build_folder 107 | else: 108 | objfolder = self.default_build_folder(self) 109 | new_buildpath = os.path.join(self.buildPath, objfolder) 110 | 111 | util.con_err(util.ConsoleHeader, 112 | 'Warning: build is being configured in the source tree.', 113 | util.ConsoleNormal) 114 | if os.path.exists(os.path.join(new_buildpath)): 115 | has_amb2 = os.path.exists(os.path.join(new_buildpath, '.ambuild2')) 116 | if not has_amb2 and len( 117 | os.listdir(new_buildpath)) and options.generator == 'ambuild2': 118 | util.con_err(util.ConsoleRed, 'Tried to use ', util.ConsoleBlue, objfolder, 119 | util.ConsoleRed, ' as a build folder, but it is not empty!', 120 | util.ConsoleNormal) 121 | raise Exception('build folder has unrecognized files') 122 | 123 | util.con_err(util.ConsoleHeader, 'Re-using build folder: ', util.ConsoleBlue, 124 | '{0}'.format(objfolder), util.ConsoleNormal) 125 | else: 126 | util.con_err(util.ConsoleHeader, 'Creating "', util.ConsoleBlue, 127 | '{0}'.format(objfolder), util.ConsoleHeader, '" as a build folder.', 128 | util.ConsoleNormal) 129 | os.mkdir(new_buildpath) 130 | self.buildPath = new_buildpath 131 | 132 | from ambuild2.frontend.v2_0.context_manager import ContextManager 133 | 134 | cm = ContextManager(self.sourcePath, self.buildPath, os.getcwd(), options, args) 135 | 136 | with util.FolderChanger(self.buildPath): 137 | if not cm.generate(options.generator): 138 | sys.stderr.write('Configure failed.\n') 139 | sys.exit(1) 140 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/vs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/frontend/v2_0/vs/__init__.py -------------------------------------------------------------------------------- /ambuild2/frontend/v2_0/vs/gen.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | from ambuild2.frontend.vs import gen as vs_gen 19 | from ambuild2.frontend.v2_0.vs import cxx 20 | 21 | class Generator(vs_gen.Generator): 22 | def __init__(self, cm): 23 | super(Generator, self).__init__(cm) 24 | 25 | def detectCompilers(self): 26 | if not self.compiler: 27 | self.base_compiler = cxx.Compiler(cxx.Compiler.GetVersionFromVS(self.vs_version)) 28 | self.compiler = self.base_compiler.clone() 29 | return self.compiler 30 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/__init__.py: -------------------------------------------------------------------------------- 1 | from ambuild2.frontend.v2_1.prep import Preparer 2 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/amb2_gen.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | from ambuild2 import nodetypes 19 | from ambuild2 import util 20 | from ambuild2.frontend import amb2_gen 21 | from ambuild2.frontend.v2_1.cpp import detect 22 | 23 | class Generator(amb2_gen.Generator): 24 | def __init__(self, cm): 25 | super(Generator, self).__init__(cm) 26 | self.compiler = None 27 | 28 | def detectCompilers(self, options): 29 | if options is None: 30 | options = {} 31 | if not self.compiler: 32 | with util.FolderChanger(self.cacheFolder): 33 | self.base_compiler = detect.AutoDetectCxx(self.cm.target, self.cm.options, options) 34 | if self.base_compiler is None: 35 | raise Exception('Could not detect a suitable C/C++ compiler') 36 | self.compiler = self.base_compiler.clone() 37 | 38 | return self.compiler 39 | 40 | def copyBuildVars(self, vars): 41 | if not self.compiler: 42 | return 43 | for prop_name in self.compiler.vendor.extra_props: 44 | key = '{0}_{1}'.format(self.compiler.vendor.name, prop_name) 45 | vars[key] = self.compiler.vendor.extra_props[prop_name] 46 | 47 | def addCxxObjTask(self, cx, shared_outputs, folder, obj): 48 | cxxData = {'argv': obj.argv, 'type': obj.behavior} 49 | _, (cxxNode,) = self.addCommand(context = cx, 50 | weak_inputs = obj.sourcedeps, 51 | inputs = [obj.inputObj], 52 | outputs = [obj.outputFile], 53 | node_type = nodetypes.Cxx, 54 | folder = folder, 55 | data = cxxData, 56 | shared_outputs = shared_outputs, 57 | env_data = obj.env_data) 58 | return cxxNode 59 | 60 | def addCxxRcTask(self, cx, folder, obj): 61 | rcData = { 62 | 'cl_argv': obj.cl_argv, 63 | 'rc_argv': obj.rc_argv, 64 | } 65 | _, (_, rcNode) = self.addCommand(context = cx, 66 | weak_inputs = obj.sourcedeps, 67 | inputs = [obj.inputObj], 68 | outputs = [obj.preprocFile, obj.outputFile], 69 | node_type = nodetypes.Rc, 70 | folder = folder, 71 | data = rcData, 72 | env_data = obj.env_data) 73 | return rcNode 74 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/cpp/__init__.py: -------------------------------------------------------------------------------- 1 | from ambuild2.frontend.v2_1.cpp.compiler import Compiler 2 | from ambuild2.frontend.v2_1.cpp.builders import CppNodes 3 | from ambuild2.frontend.v2_1.cpp.builders import Dep 4 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/cpp/compiler.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import copy 18 | import subprocess 19 | import sys 20 | from ambuild2.frontend.v2_1.cpp import builders 21 | from ambuild2 import util 22 | 23 | # Base compiler object. 24 | class Compiler(object): 25 | attrs_ = [ 26 | 'includes', # C and C++ include paths 27 | 'cxxincludes', # C++-only include paths 28 | 'cflags', # C and C++ compiler flags 29 | 'cxxflags', # C++-only compiler flags 30 | 'defines', # C and C++ #defines 31 | 'cxxdefines', # C++-only #defines 32 | 'c_only_flags', # Flags for C but not C++. 33 | 'rcdefines', # Resource Compiler (RC) defines 34 | 35 | # Link flags. If any members are not strings, they will be interpreted as 36 | # Dep entries created from BinaryBuilder. 37 | 'linkflags', 38 | 39 | # An array of objects to link, after all link flags have been specified. 40 | # Entries may either be strings containing a path, or Dep entries created 41 | # from BinaryBuilder. 42 | 'postlink', 43 | 44 | # An array of nodes which should be weak dependencies on each source 45 | # compilation command. 46 | 'sourcedeps', 47 | 48 | # An array of nodes which should be weak dependencies on each linker 49 | # command. 50 | 'weaklinkdeps', 51 | ] 52 | 53 | def __init__(self, vendor, options = None): 54 | self.vendor = vendor 55 | for attr in self.attrs_: 56 | setattr(self, attr, []) 57 | if getattr(options, 'symbol_files', False): 58 | self.symbol_files = 'separate' 59 | else: 60 | self.symbol_files = 'bundled' 61 | 62 | def inherit(self, other): 63 | for attr in self.attrs_: 64 | setattr(self, attr, copy.copy(getattr(other, attr))) 65 | 66 | self.symbol_files_ = other.symbol_files_ 67 | 68 | def clone(self): 69 | raise Exception('Must be implemented!') 70 | 71 | # Returns whether this compiler acts like another compiler. Current responses 72 | # are: 73 | # 74 | # msvc: msvc 75 | # gcc: gcc 76 | # clang: gcc, clang 77 | # apple-clang: gcc, clang, apple-clang 78 | # sun: sun 79 | # 80 | def like(self, name): 81 | return self.vendor.like(name) 82 | 83 | # Returns the meta family the compiler belongs to. The meta family is the 84 | # most generic compiler that this compiler aims to emulate. 85 | # Responses are one of: msvc, gcc, sun 86 | @property 87 | def behavior(self): 88 | return self.vendor.behavior 89 | 90 | # Returns the family the compiler belongs to. 91 | # Responses are one of: msvc, gcc, clang, sun 92 | @property 93 | def family(self): 94 | return self.vendor.family 95 | 96 | # Returns a version object representing the compiler. The version is 97 | # prefixed by the compiler name. 98 | @property 99 | def version(self): 100 | return self.vendor.version 101 | 102 | # Returns how symbol files are generated, either 'bundled' or 'separate'. 103 | @property 104 | def symbol_files(self): 105 | return self.symbol_files_ 106 | 107 | # Sets how symbol files are generated. Must be 'bundled' or 'separate'. 108 | # Default is 'bundled' if the underlying compiler supports it. If the vendor 109 | # does not support the requested symbol file type, the value remains 110 | # unchanged. 111 | @symbol_files.setter 112 | def symbol_files(self, value): 113 | if value not in ['bundled', 'separate']: 114 | raise Exception("Symbol files value must be 'bundled' or 'separate'") 115 | self.symbol_files_ = self.vendor.parseDebugInfoType(value) 116 | 117 | def Program(self, name): 118 | raise Exception('Must be implemented!') 119 | 120 | def Library(self, name): 121 | raise Exception('Must be implemented!') 122 | 123 | def StaticLibrary(self, name): 124 | raise Exception('Must be implemented!') 125 | 126 | def ProgramProject(self, name): 127 | raise Exception('Must be implemented!') 128 | 129 | def LibraryProject(self, name): 130 | raise Exception('Must be implemented!') 131 | 132 | def StaticLibraryProject(self, name): 133 | raise Exception('Must be implemented!') 134 | 135 | @staticmethod 136 | def Dep(text, node = None): 137 | return builders.Dep(text, node) 138 | 139 | class CliCompiler(Compiler): 140 | def __init__(self, vendor, cc_argv, cxx_argv, options = None, env_data = None): 141 | super(CliCompiler, self).__init__(vendor, options) 142 | self.cc_argv = cc_argv 143 | self.cxx_argv = cxx_argv 144 | self.found_pkg_config_ = False 145 | self.env_data = env_data 146 | 147 | def clone(self): 148 | cc = CliCompiler(self.vendor, self.cc_argv, self.cxx_argv) 149 | cc.inherit(self) 150 | return cc 151 | 152 | def inherit(self, other): 153 | super(CliCompiler, self).inherit(other) 154 | self.env_data = other.env_data 155 | 156 | def Program(self, name): 157 | return builders.Program(self.clone(), name) 158 | 159 | def Library(self, name): 160 | return builders.Library(self.clone(), name) 161 | 162 | def StaticLibrary(self, name): 163 | return builders.StaticLibrary(self.clone(), name) 164 | 165 | def ProgramProject(self, name): 166 | return builders.Project(builders.Program, self.clone(), name) 167 | 168 | def LibraryProject(self, name): 169 | return builders.Project(builders.Library, self.clone(), name) 170 | 171 | def StaticLibraryProject(self, name): 172 | return builders.Project(builders.StaticLibrary, self.clone(), name) 173 | 174 | @staticmethod 175 | def run_pkg_config(argv): 176 | output = subprocess.check_output(args = ['pkg-config'] + argv) 177 | output = util.DecodeConsoleText(sys.stdout, output) 178 | return [item.strip() for item in output.strip().split(' ') if item.strip() != ''] 179 | 180 | # Helper for running pkg-config. 181 | def pkg_config(self, pkg, link = 'dynamic'): 182 | if not self.found_pkg_config_: 183 | try: 184 | self.run_pkg_config(['--version']) 185 | self.found_pkg_config = True 186 | except: 187 | raise Exception('failed to find pkg-config!') 188 | 189 | for include in self.run_pkg_config(['--cflags-only-I', pkg]): 190 | if include.startswith('-I'): 191 | self.includes += [include[2:].strip()] 192 | else: 193 | self.cflags += [include] 194 | self.cflags += self.run_pkg_config(['--cflags-only-other', pkg]) 195 | 196 | if link == 'dynamic': 197 | self.linkflags += self.run_pkg_config(['--libs', pkg]) 198 | elif link == 'static': 199 | self.linkflags += self.run_pkg_config(['--libs', '--static', pkg]) 200 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/cpp/gcc.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | from ambuild2 import util 19 | from ambuild2.frontend.v2_1.cpp.vendor import Vendor 20 | 21 | class GCCLookalike(Vendor): 22 | def __init__(self, version): 23 | super(GCCLookalike, self).__init__(version) 24 | 25 | @property 26 | def behavior(self): 27 | return 'gcc' 28 | 29 | @property 30 | def definePrefix(self): 31 | return '-D' 32 | 33 | @property 34 | def objSuffix(self): 35 | return '.o' 36 | 37 | @property 38 | def debugInfoArgv(self): 39 | return [] 40 | 41 | def parseDebugInfoType(self, debuginfo): 42 | return debuginfo 43 | 44 | def formatInclude(self, outputPath, includePath): 45 | return ['-I', os.path.normpath(includePath)] 46 | 47 | def objectArgs(self, sourceFile, objFile): 48 | return ['-H', '-c', sourceFile, '-o', objFile] 49 | 50 | def staticLinkArgv(self, files, outputFile): 51 | return ['ar', 'rcs', outputFile] + files 52 | 53 | def programLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 54 | return cmd_argv + files + linkFlags + ['-o', outputFile] 55 | 56 | def libLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 57 | argv = cmd_argv + files + linkFlags 58 | if util.IsMac(): 59 | argv += ['-dynamiclib'] 60 | else: 61 | argv += ['-shared'] 62 | argv += ['-o', outputFile] 63 | return argv 64 | 65 | def preprocessArgv(self, sourceFile, outFile): 66 | return ['-H', '-E', sourceFile, '-o', outFile] 67 | 68 | @staticmethod 69 | def IncludePath(outputPath, includePath): 70 | return includePath 71 | 72 | class GCC(GCCLookalike): 73 | def __init__(self, version): 74 | super(GCC, self).__init__(version) 75 | 76 | @property 77 | def name(self): 78 | return 'gcc' 79 | 80 | @property 81 | def family(self): 82 | return 'gcc' 83 | 84 | def like(self, name): 85 | return name == 'gcc' 86 | 87 | class Clang(GCCLookalike): 88 | def __init__(self, version, vendor_prefix = None): 89 | # Set this first, since the constructor will need it. 90 | self.vendor_name = 'clang' 91 | if vendor_prefix is not None: 92 | self.vendor_name = '{0}-clang'.format(vendor_prefix) 93 | super(Clang, self).__init__(version) 94 | 95 | @property 96 | def name(self): 97 | return self.vendor_name 98 | 99 | @property 100 | def family(self): 101 | return 'clang' 102 | 103 | def like(self, name): 104 | return name == 'gcc' or name == 'clang' or name == self.name 105 | 106 | @property 107 | def debugInfoArgv(self): 108 | return ['-g3'] 109 | 110 | class Emscripten(Clang): 111 | def __init__(self, version): 112 | # Set this first, since the constructor will need it. 113 | super(Emscripten, self).__init__(version, 'emscripten') 114 | 115 | def nameForExecutable(self, name): 116 | return name + '.js' 117 | 118 | def nameForSharedLibrary(self, name): 119 | return name + '.bc' 120 | 121 | def nameForStaticLibrary(self, name): 122 | return util.StaticLibPrefix + name + '.a' 123 | 124 | @property 125 | def name(self): 126 | return 'emscripten' 127 | 128 | @property 129 | def family(self): 130 | return 'emscripten' 131 | 132 | def like(self, name): 133 | return name == 'gcc' or name == 'clang' or name == 'emscripten-clang' or name == 'emscripten' 134 | 135 | @property 136 | def debugInfoArgv(self): 137 | return [] 138 | 139 | def staticLinkArgv(self, files, outputFile): 140 | return ['emar', 'rcs', outputFile] + files 141 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/cpp/msvc.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | import re 19 | from ambuild2 import util 20 | from ambuild2.frontend.v2_1.cpp.vendor import Vendor 21 | 22 | # Microsoft Visual C++ 23 | class MSVC(Vendor): 24 | def __init__(self, version): 25 | super(MSVC, self).__init__(version) 26 | 27 | @property 28 | def name(self): 29 | return 'msvc' 30 | 31 | @property 32 | def behavior(self): 33 | return 'msvc' 34 | 35 | @property 36 | def family(self): 37 | return 'msvc' 38 | 39 | def like(self, name): 40 | return name == 'msvc' 41 | 42 | @property 43 | def definePrefix(self): 44 | return '/D' 45 | 46 | @property 47 | def objSuffix(self): 48 | return '.obj' 49 | 50 | @property 51 | def debugInfoArgv(self): 52 | if int(self.version_string) >= 1800: 53 | return ['/Zi', '/FS'] 54 | return ['/Zi'] 55 | 56 | def parseDebugInfoType(self, debuginfo): 57 | if debuginfo == 'bundled': 58 | return 'separate' 59 | return debuginfo 60 | 61 | def objectArgs(self, sourceFile, objFile): 62 | return ['/showIncludes', '/nologo', '/c', sourceFile, '/Fo' + objFile] 63 | 64 | def staticLinkArgv(self, files, outputFile): 65 | return ['lib', '/OUT:' + outputFile] + files 66 | 67 | def programLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 68 | argv = cmd_argv + files 69 | argv += ['/link'] 70 | argv += linkFlags 71 | argv += [ 72 | '/OUT:' + outputFile, 73 | '/nologo', 74 | ] 75 | if symbolFile: 76 | argv += ['/DEBUG', '/PDB:"' + symbolFile + '.pdb"'] 77 | return argv 78 | 79 | def libLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 80 | argv = cmd_argv + files 81 | argv += ['/link'] 82 | argv += linkFlags 83 | argv += [ 84 | '/OUT:' + outputFile, 85 | '/nologo', 86 | '/DLL', 87 | ] 88 | if symbolFile: 89 | argv += ['/DEBUG', '/PDB:"' + symbolFile + '.pdb"'] 90 | return argv 91 | 92 | def preprocessArgv(self, sourceFile, outFile): 93 | return ['/showIncludes', '/nologo', '/P', '/c', sourceFile, '/Fi' + outFile] 94 | 95 | @staticmethod 96 | def IncludePath(outputPath, includePath): 97 | # Hack - try and get a relative path because CL, with either 98 | # /Zi or /ZI, combined with subprocess, apparently tries and 99 | # looks for paths like c:\bleh\"c:\bleh" <-- wtf 100 | # .. this according to Process Monitor 101 | outputPath = os.path.normcase(outputPath) 102 | includePath = os.path.normcase(includePath) 103 | outputDrive = os.path.splitdrive(outputPath)[0] 104 | includeDrive = os.path.splitdrive(includePath)[0] 105 | if outputDrive == includeDrive: 106 | return os.path.relpath(includePath, outputPath) 107 | return includePath 108 | 109 | def formatInclude(self, outputPath, includePath): 110 | return ['/I', MSVC.IncludePath(outputPath, includePath)] 111 | 112 | ## 113 | # MSVC-specific properties. 114 | ## 115 | @property 116 | def shared_pdb_name(self): 117 | cl_version = int(self.version_string) 118 | 119 | # Truncate down to the major version then correct the offset 120 | # There is some evidence that the first digit of the minor version can be used for the PDB, but I can't reproduce it 121 | cl_version = int(cl_version / 100) - 6 122 | 123 | # Microsoft introduced a discontinuity with vs2015 124 | if cl_version >= 13: 125 | cl_version += 1 126 | 127 | # Pad it back out again 128 | cl_version *= 10 129 | 130 | return 'vc{0}.pdb'.format(cl_version) 131 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/cpp/sunpro.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | from ambuild2 import util 19 | from ambuild2.frontend.v2_1.cpp.vendor import Vendor 20 | 21 | class SunPro(Vendor): 22 | def __init__(self, version): 23 | super(SunPro, self).__init__(version) 24 | 25 | @property 26 | def name(self): 27 | return 'sun' 28 | 29 | @property 30 | def behavior(self): 31 | return 'sun' 32 | 33 | @property 34 | def family(self): 35 | return 'sun' 36 | 37 | def like(self, name): 38 | return name == 'sun' 39 | 40 | @property 41 | def definePrefix(self): 42 | return '/D' 43 | 44 | @property 45 | def objSuffix(self): 46 | return '.o' 47 | 48 | @property 49 | def debugInfoArgv(self): 50 | return ['-g3'] 51 | 52 | def parseDebugInfoType(self, debuginfo): 53 | return debuginfo 54 | 55 | def objectArgs(self, sourceFile, objFile): 56 | return ['-H', '-c', sourceFile, '-o', objFile] 57 | 58 | def formatInclude(self, outputPath, includePath): 59 | return ['-I', os.path.normpath(includePath)] 60 | 61 | def staticLinkArgv(self, files, outputFile): 62 | return ['ar', 'rcs', outputFile] + files 63 | 64 | def programLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 65 | return cmd_argv + files + linkFlags + ['-o', outputFile] 66 | 67 | def libLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 68 | return cmd_argv + files + linkFlags + ['-o', outputFile] 69 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/cpp/vendor.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | import os 19 | from ambuild2 import util 20 | from ambuild2.frontend.version import Version 21 | 22 | class Vendor(object): 23 | def __init__(self, version): 24 | super(Vendor, self).__init__() 25 | self.version_string = version 26 | self.version = Version('{0}-{1}'.format(self.name, version)) 27 | self.extra_props = {} 28 | 29 | def nameForExecutable(self, name): 30 | return name + util.ExecutableSuffix 31 | 32 | def nameForSharedLibrary(self, name): 33 | return name + util.SharedLibSuffix 34 | 35 | def nameForStaticLibrary(self, name): 36 | return util.StaticLibPrefix + name + util.StaticLibSuffix 37 | 38 | def equals(self, other): 39 | return self.name == other.name and \ 40 | self.version == other.version and \ 41 | self.extra_props == other.extra_props 42 | 43 | def __str__(self): 44 | return '{0}-{1}'.format(self.name, self.version_string) 45 | 46 | @property 47 | def behavior(self): 48 | raise Exception("Must be implemented") 49 | 50 | @property 51 | def name(self): 52 | raise Exception("Must be implemented") 53 | 54 | @property 55 | def family(self): 56 | raise Exception("Must be implemented") 57 | 58 | def like(self, name): 59 | raise Exception("Must be implemented") 60 | 61 | @property 62 | def definePrefix(self): 63 | raise Exception("Must be implemented") 64 | 65 | @property 66 | def objSuffix(self): 67 | raise Exception("Must be implemented") 68 | 69 | @property 70 | def debugInfoArgv(self): 71 | raise Exception("Must be implemented") 72 | 73 | def parseDebugInfoType(self, debuginfo): 74 | raise Exception("Must be implemented") 75 | 76 | def formatInclude(self, outputPath, includePath): 77 | raise Exception("Must be implemented") 78 | 79 | def objectArgs(self, sourceFile, objFile): 80 | raise Exception("Must be implemented") 81 | 82 | # Note: this should return a complete initial argv, not partial. 83 | # AMBuild does not detect AR/LIB separately yet. 84 | def staticLinkArgv(self, files, outputFile): 85 | raise Exception("Must be implemented") 86 | 87 | # For this and libLinkArgv(), the symbolFile should not have an extension. 88 | # The vendor chooses the extension if it supports symbol files at all. 89 | def programLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 90 | raise Exception("Must be implemented") 91 | 92 | def libLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 93 | raise Exception("Must be implemented") 94 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 sw=2 tw=99 et: 2 | 3 | from ambuild2.frontend.v2_1.tools.fxc import FxcJob as FXC 4 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/tools/fxc.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can Headeristribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os, re 18 | import argparse 19 | 20 | class FxcTool(object): 21 | def __init__(self): 22 | super(FxcTool, self).__init__() 23 | self.output_files = [] 24 | self.output_nodes = [] 25 | 26 | def evaluate(self, cmd): 27 | for shader in cmd.data.shaders: 28 | self.evaluate_shader(cmd, shader) 29 | 30 | argv = [ 31 | 'python', 32 | __file__, 33 | '--prefix', 34 | cmd.data.output, 35 | ] 36 | if cmd.data.namespace: 37 | argv += ['--namespace', cmd.data.namespace] 38 | if cmd.data.listDefineName: 39 | argv += ['--list-define-name', cmd.data.listDefineName] 40 | 41 | argv += self.output_files 42 | 43 | _, (cxx_file, 44 | h_file) = cmd.context.AddCommand(inputs = [__file__], 45 | argv = argv, 46 | outputs = [ 47 | '{0}-bytecode.cxx'.format(cmd.data.output), 48 | '{0}-include.h'.format(cmd.data.output), 49 | ], 50 | folder = cmd.localFolderNode) 51 | 52 | cmd.sources += [cmd.CustomSource(source = cxx_file, weak_deps = self.output_nodes)] 53 | cmd.sourcedeps += [h_file] 54 | 55 | def evaluate_shader(self, cmd, shader): 56 | source = shader['source'] 57 | var_prefix = shader['variable'] 58 | profile = shader['profile'] 59 | entrypoint = shader.get('entry', 'main') 60 | 61 | output_file = '{0}.{1}.{2}.h'.format(cmd.NameForObjectFile(source), var_prefix, entrypoint) 62 | 63 | sourceFile = cmd.ComputeSourcePath(source) 64 | 65 | argv = [ 66 | 'fxc', 67 | '/T', 68 | profile, 69 | '/E', 70 | entrypoint, 71 | '/Fh', 72 | output_file, 73 | '/Vn', 74 | '{0}_Bytes_Impl'.format(var_prefix), 75 | '/Vi', 76 | '/nologo', 77 | sourceFile, 78 | ] 79 | outputs = [output_file] 80 | folder = cmd.localFolderNode 81 | 82 | _, (output_node,) = cmd.context.AddCommand(inputs = [sourceFile], 83 | argv = argv, 84 | outputs = [output_file], 85 | folder = cmd.localFolderNode, 86 | dep_type = 'fxc') 87 | 88 | self.output_files += [output_file] 89 | self.output_nodes += [output_node] 90 | 91 | class FxcJob(object): 92 | def __init__(self, output, namespace): 93 | super(FxcJob, self).__init__() 94 | self.tool = FxcTool() 95 | self.output = output 96 | self.shaders = [] 97 | self.namespace = namespace 98 | self.listDefineName = None 99 | 100 | def fxc_helper_tool(): 101 | parser = argparse.ArgumentParser() 102 | parser.add_argument('--prefix', type = str, required = True, help = 'Prefix for prefix files') 103 | parser.add_argument('--namespace', type = str, help = 'Optional fully-qualified namespace') 104 | parser.add_argument('--list-define-name', 105 | type = str, 106 | help = 'Optional name for shader list define') 107 | parser.add_argument('sources', type = str, nargs = '+', help = 'Source list') 108 | 109 | args = parser.parse_args() 110 | 111 | bytecode_fp = open('{0}-bytecode.cxx'.format(args.prefix), 'w') 112 | include_fp = open('{0}-include.h'.format(args.prefix), 'w') 113 | 114 | header = '// Auto-generated by {0}\n'.format(__file__) 115 | bytecode_fp.write(header) 116 | include_fp.write(header) 117 | 118 | bytecode_fp.write("#define WIN32_LEAN_AND_MEAN\n") 119 | bytecode_fp.write("#include \n") 120 | bytecode_fp.write("#include \n") 121 | bytecode_fp.write("#include \n") 122 | bytecode_fp.write('\n') 123 | 124 | include_fp.write("#pragma once\n") 125 | include_fp.write("#include \n") 126 | include_fp.write("#include \n") 127 | include_fp.write('\n') 128 | 129 | fqns = args.namespace.split('::') if args.namespace else [] 130 | if fqns: 131 | for part in fqns: 132 | bytecode_fp.write('namespace {0} {{\n'.format(part)) 133 | include_fp.write('namespace {0} {{\n'.format(part)) 134 | bytecode_fp.write('\n') 135 | include_fp.write('\n') 136 | 137 | var_prefixes = [] 138 | 139 | for source_file in args.sources: 140 | m = re.match('([^.]+)\.([^.]+)\.([^.]+)\.h', source_file) 141 | var_prefix = m.group(2) 142 | if m is None: 143 | raise Exception('Sources must be in objname.varprefix.entrypoint.h form') 144 | 145 | bytecode_line = """#include "{0}" 146 | extern const uint8_t* {1}_Bytes = {1}_Bytes_Impl; 147 | extern const size_t {1}_Length = sizeof({1}_Bytes_Impl); 148 | """.format(source_file, var_prefix) 149 | bytecode_fp.write(bytecode_line) 150 | 151 | include_fp.write("""extern const uint8_t* {0}_Bytes; 152 | extern const size_t {0}_Length; 153 | """.format(var_prefix)) 154 | 155 | var_prefixes.append(var_prefix) 156 | 157 | bytecode_fp.write('\n') 158 | include_fp.write('\n') 159 | 160 | if args.list_define_name: 161 | include_fp.write('#define {0}(_) \\\n'.format(args.list_define_name)) 162 | for var_prefix in var_prefixes: 163 | include_fp.write(' _({0}) \\\n'.format(var_prefix)) 164 | include_fp.write(' // terminator\n') 165 | include_fp.write('\n') 166 | 167 | for part in fqns: 168 | bytecode_fp.write('}} // namespace {0}\n'.format(part)) 169 | include_fp.write('}} // namespace {0}\n'.format(part)) 170 | 171 | bytecode_fp.close() 172 | include_fp.close() 173 | 174 | if __name__ == '__main__': 175 | fxc_helper_tool() 176 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/vs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/frontend/v2_1/vs/__init__.py -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/vs/gen.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import ambuild2.frontend.vs.gen as vs_gen 18 | from ambuild2.frontend.v2_1.vs import cxx 19 | 20 | class Generator(vs_gen.Generator): 21 | def __init__(self, cm): 22 | super(Generator, self).__init__(cm) 23 | 24 | # Overridden. 25 | def detectCompilers(self, options): 26 | if not self.compiler: 27 | version = cxx.Compiler.GetVersionFromVS(self.vs_version) 28 | vendor = cxx.VisualStudio(version) 29 | self.base_compiler = cxx.Compiler(vendor) 30 | self.compiler = self.base_compiler.clone() 31 | return self.compiler 32 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_1/vs/nodes.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | class Node(object): 19 | def __init__(self, context, path): 20 | super(Node, self).__init__() 21 | self.context = context 22 | self.path = path 23 | self.children = set() 24 | self.parents = set() 25 | 26 | def addParent(self, parent): 27 | self.parents.add(parent) 28 | parent.children.add(self) 29 | 30 | class FolderNode(Node): 31 | def __init__(self, path): 32 | super(FolderNode, self).__init__(None, path) 33 | 34 | @property 35 | def kind(self): 36 | return 'folder' 37 | 38 | class ContainerNode(Node): 39 | def __init__(self, cx): 40 | super(ContainerNode, self).__init__(cx, None) 41 | 42 | @property 43 | def kind(self): 44 | return 'container' 45 | 46 | class OutputNode(Node): 47 | def __init__(self, context, path, parent): 48 | super(OutputNode, self).__init__(context, path) 49 | self.addParent(parent) 50 | 51 | @property 52 | def kind(self): 53 | return 'output' 54 | 55 | class ProjectNode(Node): 56 | def __init__(self, context, path, project): 57 | super(ProjectNode, self).__init__(context, path) 58 | self.project = project 59 | self.uuid = None 60 | 61 | @property 62 | def kind(self): 63 | return 'project' 64 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/frontend/v2_2/__init__.py -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/amb2_gen.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | from ambuild2 import util 19 | from ambuild2.frontend import amb2_gen 20 | from ambuild2.frontend.v2_2.cpp import builders 21 | from ambuild2.frontend.v2_2.cpp import detect 22 | 23 | class Generator(amb2_gen.Generator): 24 | def __init__(self, cm): 25 | super(Generator, self).__init__(cm) 26 | 27 | def detectCompilers(self, **kwargs): 28 | with util.FolderChanger(self.cacheFolder): 29 | return detect.AutoDetectCxx(self.cm.host, self.cm.options, **kwargs) 30 | 31 | def newProgramProject(self, context, name): 32 | return builders.Project(builders.Program, name) 33 | 34 | def newLibraryProject(self, context, name): 35 | return builders.Project(builders.Library, name) 36 | 37 | def newStaticLibraryProject(self, context, name): 38 | return builders.Project(builders.StaticLibrary, name) -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/cpp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/frontend/v2_2/cpp/__init__.py -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/cpp/deptypes.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | class CppNodes(object): 19 | def __init__(self, output, debug_outputs, type, target, static_binary = None): 20 | self.binary = output 21 | self.debug = debug_outputs 22 | self.type = type 23 | self.target = target 24 | self.static_binary = static_binary 25 | 26 | class PchNodes(object): 27 | def __init__(self, folder, header_file, pch_file, object_file, source_type): 28 | self.folder = folder 29 | self.header_file = header_file 30 | self.pch_file = pch_file 31 | self.object_file = object_file 32 | self.source_type = source_type 33 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/cpp/gcc.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | from ambuild2 import util 19 | from ambuild2.frontend.v2_2.cpp.deptypes import PchNodes 20 | from ambuild2.frontend.v2_2.cpp.vendor import Archiver, Linker, Vendor 21 | 22 | class GCCLookalike(Vendor): 23 | def __init__(self, version): 24 | super(GCCLookalike, self).__init__(version) 25 | 26 | @property 27 | def behavior(self): 28 | return 'gcc' 29 | 30 | @property 31 | def definePrefix(self): 32 | return '-D' 33 | 34 | @property 35 | def objSuffix(self): 36 | return '.o' 37 | 38 | @property 39 | def debugInfoArgv(self): 40 | return [] 41 | 42 | def parseDebugInfoType(self, debuginfo): 43 | return debuginfo 44 | 45 | @property 46 | def pch_needs_source_file(self): 47 | return False 48 | 49 | def formatInclude(self, build_root, output_path, include): 50 | return ['-I', os.path.normpath(include)] 51 | 52 | # We could use -MF -, but trying to parse something as idiosyncratic as Make 53 | # is probably best done when absolutely nothing else might be interspersed. 54 | def emits_dependency_file(self): 55 | return True 56 | 57 | def dependencyArgv(self, out_file): 58 | return ['-MD', '-MF', out_file] 59 | 60 | def objectArgs(self, sourceFile, objFile): 61 | return ['-c', sourceFile, '-o', objFile] 62 | 63 | def makePchArgv(self, source_file, obj_file, source_type): 64 | return ['-c', '-x', source_type + '-header', source_file, '-o', obj_file] 65 | 66 | def programLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 67 | return cmd_argv + files + linkFlags + ['-o', outputFile] 68 | 69 | def libLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 70 | argv = cmd_argv + files + linkFlags 71 | if util.IsMac(): 72 | argv += ['-dynamiclib'] 73 | else: 74 | argv += ['-shared'] 75 | argv += ['-o', outputFile] 76 | return argv 77 | 78 | def preprocessArgv(self, sourceFile, outFile): 79 | return ['-H', '-E', sourceFile, '-o', outFile] 80 | 81 | @staticmethod 82 | def IncludePath(outputPath, includePath): 83 | return includePath 84 | 85 | class GCC(GCCLookalike): 86 | def __init__(self, version): 87 | super(GCC, self).__init__(version) 88 | 89 | @property 90 | def name(self): 91 | return 'gcc' 92 | 93 | @property 94 | def family(self): 95 | return 'gcc' 96 | 97 | def like(self, name): 98 | return name == 'gcc' 99 | 100 | @property 101 | def pch_needs_strong_deps(self): 102 | return False 103 | 104 | def nameForPch(self, source_file): 105 | return source_file + '.gch' 106 | 107 | def formatPchInclude(self, build_root, output_path, pch): 108 | local_path = os.path.relpath(pch.header_file.path, output_path) 109 | return ['-include', local_path, '-I', os.path.split(local_path)[0]] 110 | 111 | class Clang(GCCLookalike): 112 | def __init__(self, version, vendor_prefix = None): 113 | # Set this first, since the constructor will need it. 114 | self.vendor_name = 'clang' 115 | if vendor_prefix is not None: 116 | self.vendor_name = '{0}-clang'.format(vendor_prefix) 117 | super(Clang, self).__init__(version) 118 | 119 | @property 120 | def name(self): 121 | return self.vendor_name 122 | 123 | @property 124 | def family(self): 125 | return 'clang' 126 | 127 | def like(self, name): 128 | return name == 'gcc' or name == 'clang' or name == self.name 129 | 130 | @property 131 | def debugInfoArgv(self): 132 | return ['-g3'] 133 | 134 | def nameForPch(self, source_file): 135 | return source_file + '.pch' 136 | 137 | def formatPchInclude(self, build_root, output_path, pch): 138 | pch_path = os.path.relpath(pch.pch_file.path, output_path) 139 | return ['-include-pch', pch_path, '-I', os.path.split(pch_path)[0]] 140 | 141 | @property 142 | def pch_needs_strong_deps(self): 143 | return True 144 | 145 | class Emscripten(Clang): 146 | def __init__(self, version): 147 | # Set this first, since the constructor will need it. 148 | super(Emscripten, self).__init__(version, 'emscripten') 149 | 150 | def nameForExecutable(self, name): 151 | return name + '.js' 152 | 153 | def nameForSharedLibrary(self, name): 154 | return name + '.bc' 155 | 156 | def nameForStaticLibrary(self, name): 157 | return util.StaticLibPrefix + name + '.a' 158 | 159 | @property 160 | def name(self): 161 | return 'emscripten' 162 | 163 | @property 164 | def family(self): 165 | return 'emscripten' 166 | 167 | def like(self, name): 168 | return name == 'gcc' or name == 'clang' or name == 'emscripten-clang' or name == 'emscripten' 169 | 170 | @property 171 | def debugInfoArgv(self): 172 | return [] 173 | 174 | class GccLinker(Linker): 175 | def __init__(self): 176 | super(GccLinker, self).__init__() 177 | 178 | def like(self, name): 179 | return name == 'gcc' 180 | 181 | class GccArchiver(Archiver): 182 | def __init__(self): 183 | super(GccArchiver, self).__init__() 184 | 185 | def like(self, name): 186 | return name == 'gcc' 187 | 188 | def makeArgv(self, base_argv, files, outputFile): 189 | return base_argv + ['rcs', outputFile] + files 190 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/cpp/msvc.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os 18 | import re 19 | from ambuild2 import util 20 | from ambuild2.frontend.v2_2.cpp.deptypes import PchNodes 21 | from ambuild2.frontend.v2_2.cpp.vendor import Archiver, Linker, Vendor 22 | 23 | # Microsoft Visual C++ 24 | class MSVC(Vendor): 25 | def __init__(self, version): 26 | super(MSVC, self).__init__(version) 27 | 28 | @property 29 | def name(self): 30 | return 'msvc' 31 | 32 | @property 33 | def behavior(self): 34 | return 'msvc' 35 | 36 | @property 37 | def family(self): 38 | return 'msvc' 39 | 40 | def like(self, name): 41 | return name == 'msvc' 42 | 43 | @property 44 | def definePrefix(self): 45 | return '/D' 46 | 47 | @property 48 | def objSuffix(self): 49 | return '.obj' 50 | 51 | @property 52 | def debugInfoArgv(self): 53 | return ['/Z7'] 54 | 55 | def makePchArgv(self, source_file, pch_file, source_type): 56 | return ['/showIncludes', '/nologo', '/Yc', '/c', source_file, '/Fp' + pch_file] 57 | 58 | def parseDebugInfoType(self, debuginfo): 59 | if debuginfo == 'bundled': 60 | return 'separate' 61 | return debuginfo 62 | 63 | def objectArgs(self, sourceFile, objFile): 64 | return ['/showIncludes', '/nologo', '/c', sourceFile, '/Fo' + objFile] 65 | 66 | def programLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 67 | argv = cmd_argv + files 68 | argv += ['/link'] 69 | argv += linkFlags 70 | argv += [ 71 | '/OUT:' + outputFile, 72 | '/nologo', 73 | ] 74 | if symbolFile: 75 | argv += ['/DEBUG', '/PDB:"' + symbolFile + '.pdb"'] 76 | return argv 77 | 78 | def libLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 79 | argv = cmd_argv + files 80 | argv += ['/link'] 81 | argv += linkFlags 82 | argv += [ 83 | '/OUT:' + outputFile, 84 | '/nologo', 85 | '/DLL', 86 | ] 87 | if symbolFile: 88 | argv += ['/DEBUG', '/PDB:"' + symbolFile + '.pdb"'] 89 | return argv 90 | 91 | def preprocessArgv(self, sourceFile, outFile): 92 | return ['/showIncludes', '/nologo', '/P', '/c', sourceFile, '/Fi' + outFile] 93 | 94 | @staticmethod 95 | def IncludePath(output_path, include_path): 96 | assert os.path.isabs(output_path) 97 | 98 | output_path = os.path.normcase(output_path) 99 | 100 | if not os.path.isabs(include_path): 101 | abs_include_path = os.path.join(output_path, include_path) 102 | else: 103 | abs_include_path = include_path 104 | abs_include_path = os.path.normcase(abs_include_path) 105 | 106 | # Hack - try and get a relative path because CL, with either 107 | # /Zi or /ZI, combined with subprocess, apparently tries and 108 | # looks for paths like c:\bleh\"c:\bleh" <-- wtf 109 | # .. this according to Process Monitor 110 | output_drive, _ = os.path.splitdrive(output_path) 111 | include_drive, _ = os.path.splitdrive(abs_include_path) 112 | if output_drive != include_drive: 113 | return os.path.normcase(include_path) 114 | return os.path.relpath(abs_include_path, output_path) 115 | 116 | def formatInclude(self, build_root, output_path, include): 117 | return ['/I', MSVC.IncludePath(output_path, include)] 118 | 119 | def formatPchInclude(self, build_root, output_path, pch): 120 | folder, header_name = os.path.split(pch.header_file.path) 121 | 122 | # Include path calculation expects a path relative to output_path, so 123 | # we need to transform it. 124 | pch_rel_folder = os.path.relpath(os.path.join(build_root, pch.pch_file.path), output_path) 125 | argv = [ 126 | '/Fp' + MSVC.IncludePath(output_path, pch_rel_folder), 127 | '/Yu' + header_name, 128 | '/I', 129 | MSVC.IncludePath(output_path, folder), 130 | ] 131 | return argv 132 | 133 | ## 134 | # MSVC-specific properties. 135 | ## 136 | @property 137 | def shared_pdb_name(self): 138 | cl_version = int(self.version_string) 139 | 140 | # Truncate down to the major version then correct the offset 141 | # There is some evidence that the first digit of the minor version can be used for the PDB, but I can't reproduce it 142 | cl_version = int(cl_version / 100) - 6 143 | 144 | # Microsoft introduced a discontinuity with vs2015 145 | if cl_version >= 13: 146 | cl_version += 1 147 | 148 | # Pad it back out again 149 | cl_version *= 10 150 | 151 | return 'vc{0}.pdb'.format(cl_version) 152 | 153 | # cl.exe /showIncludes does not show anything at all for precompiled headers, 154 | # so the only way we can build a proper dependency is by rebuilding every 155 | # source file that *might* use the PCH, whether or not it actually does. 156 | @property 157 | def pch_needs_strong_deps(self): 158 | return True 159 | 160 | # cl.exe precompiles source files, technically, not headers. So we need to 161 | # link against something. 162 | @property 163 | def pch_needs_source_file(self): 164 | return True 165 | 166 | @property 167 | def shared_pdb_flags(self): 168 | return set(['/Zi', '/ZI']) 169 | 170 | def nameForPch(self, source_file): 171 | return os.path.splitext(source_file)[0] + '.pch' 172 | 173 | @property 174 | def emits_dependency_file(self): 175 | return False 176 | 177 | class MsvcLinker(Linker): 178 | def __init__(self): 179 | super(MsvcLinker, self).__init__() 180 | 181 | def like(self, name): 182 | return name == 'msvc' 183 | 184 | class MsvcArchiver(Archiver): 185 | def __init__(self): 186 | super(MsvcArchiver, self).__init__() 187 | 188 | def like(self, name): 189 | return name == 'msvc' 190 | 191 | def makeArgv(self, base_argv, files, outputFile): 192 | return base_argv + ['/OUT:' + outputFile] + files 193 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/cpp/vendor.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | import os 19 | from ambuild2 import util 20 | from ambuild2.frontend.version import Version 21 | 22 | class Vendor(object): 23 | def __init__(self, version): 24 | super(Vendor, self).__init__() 25 | self.version_string = version 26 | self.version = Version('{0}-{1}'.format(self.name, version)) 27 | self.extra_props = {} 28 | 29 | def nameForExecutable(self, name): 30 | return name + util.ExecutableSuffix 31 | 32 | def nameForSharedLibrary(self, name): 33 | return name + util.SharedLibSuffix 34 | 35 | def nameForStaticLibrary(self, name): 36 | return util.StaticLibPrefix + name + util.StaticLibSuffix 37 | 38 | def equals(self, other): 39 | return self.name == other.name and \ 40 | self.version == other.version and \ 41 | self.extra_props == other.extra_props 42 | 43 | def __str__(self): 44 | return '{0}-{1}'.format(self.name, self.version_string) 45 | 46 | @property 47 | def behavior(self): 48 | raise Exception("Must be implemented") 49 | 50 | @property 51 | def name(self): 52 | raise Exception("Must be implemented") 53 | 54 | @property 55 | def family(self): 56 | raise Exception("Must be implemented") 57 | 58 | def like(self, name): 59 | raise Exception("Must be implemented") 60 | 61 | @property 62 | def definePrefix(self): 63 | raise Exception("Must be implemented") 64 | 65 | @property 66 | def objSuffix(self): 67 | raise Exception("Must be implemented") 68 | 69 | @property 70 | def debug_info_argv(self): 71 | raise Exception("Must be implemented") 72 | 73 | def parseDebugInfoType(self, debuginfo): 74 | raise Exception("Must be implemented") 75 | 76 | def formatInclude(self, outputPath, includePath): 77 | raise Exception("Must be implemented") 78 | 79 | def formatPchInclude(self, output_path, pch): 80 | raise Exception("Must be implemented") 81 | 82 | def objectArgs(self, sourceFile, objFile): 83 | raise Exception("Must be implemented") 84 | 85 | # For this and libLinkArgv(), the symbolFile should not have an extension. 86 | # The vendor chooses the extension if it supports symbol files at all. 87 | def programLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 88 | raise Exception("Must be implemented") 89 | 90 | def libLinkArgv(self, cmd_argv, files, linkFlags, symbolFile, outputFile): 91 | raise Exception("Must be implemented") 92 | 93 | def nameForPrecompiledHeader(self, name): 94 | raise Exception("Must be implemented") 95 | 96 | @property 97 | def pch_needs_source_file(self): 98 | raise Exception("Must be implemented") 99 | 100 | @property 101 | def shared_pdb_flags(self): 102 | return set() 103 | 104 | def nameForPch(self, source_file): 105 | raise Exception("Must be implemented") 106 | 107 | @property 108 | def emits_dependency_file(self): 109 | raise Exception("Must be implemented") 110 | 111 | # If emits_dependency_file is True, this must be implemented. 112 | def dependencyArgv(self, out_file): 113 | raise Exception("Must be implemented") 114 | 115 | class Linker(object): 116 | def __init__(self): 117 | super(Linker, self).__init__() 118 | 119 | def like(self, name): 120 | raise Exception("Must be implemented") 121 | 122 | class Archiver(object): 123 | def __init__(self): 124 | super(Archiver, self).__init__() 125 | 126 | def like(self, name): 127 | raise Exception("Must be implemented") 128 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 sw=2 tw=99 et: 2 | 3 | from ambuild2.frontend.v2_2.tools.fxc import FxcJob as FXC 4 | from ambuild2.frontend.v2_2.tools.protoc import DetectProtoc as DetectProtoc 5 | from ambuild2.frontend.v2_2.tools.protoc import ProtocJob as Protoc 6 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/tools/fxc.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can Headeristribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os, re 18 | import argparse 19 | 20 | class FxcTool(object): 21 | def __init__(self): 22 | super(FxcTool, self).__init__() 23 | self.output_files = [] 24 | self.output_nodes = [] 25 | 26 | def evaluate(self, cmd): 27 | for shader in cmd.data.shaders: 28 | self.evaluate_shader(cmd, shader) 29 | 30 | argv = [ 31 | 'python', 32 | __file__, 33 | '--prefix', 34 | cmd.data.output, 35 | ] 36 | if cmd.data.namespace: 37 | argv += ['--namespace', cmd.data.namespace] 38 | if cmd.data.listDefineName: 39 | argv += ['--list-define-name', cmd.data.listDefineName] 40 | 41 | argv += self.output_files 42 | 43 | _, (cxx_file, 44 | h_file) = cmd.context.AddCommand(inputs = [__file__], 45 | argv = argv, 46 | outputs = [ 47 | '{0}-bytecode.cxx'.format(cmd.data.output), 48 | '{0}-include.h'.format(cmd.data.output), 49 | ], 50 | folder = cmd.localFolderNode) 51 | 52 | cmd.sources += [cmd.CustomSource(source = cxx_file, weak_deps = self.output_nodes)] 53 | cmd.sourcedeps += [h_file] 54 | 55 | def evaluate_shader(self, cmd, shader): 56 | source = shader['source'] 57 | var_prefix = shader['variable'] 58 | profile = shader['profile'] 59 | entrypoint = shader.get('entry', 'main') 60 | 61 | output_file = '{0}.{1}.{2}.h'.format(cmd.NameForObjectFile(source), var_prefix, entrypoint) 62 | 63 | sourceFile = cmd.ComputeSourcePath(source) 64 | 65 | argv = [ 66 | 'fxc', 67 | '/T', 68 | profile, 69 | '/E', 70 | entrypoint, 71 | '/Fh', 72 | output_file, 73 | '/Vn', 74 | '{0}_Bytes_Impl'.format(var_prefix), 75 | '/Vi', 76 | '/nologo', 77 | sourceFile, 78 | ] 79 | outputs = [output_file] 80 | folder = cmd.localFolderNode 81 | 82 | _, (output_node,) = cmd.context.AddCommand(inputs = [sourceFile], 83 | argv = argv, 84 | outputs = [output_file], 85 | folder = cmd.localFolderNode, 86 | dep_type = 'fxc') 87 | 88 | self.output_files += [output_file] 89 | self.output_nodes += [output_node] 90 | 91 | class FxcJob(object): 92 | def __init__(self, output, namespace): 93 | super(FxcJob, self).__init__() 94 | self.tool = FxcTool() 95 | self.output = output 96 | self.shaders = [] 97 | self.namespace = namespace 98 | self.listDefineName = None 99 | 100 | def fxc_helper_tool(): 101 | parser = argparse.ArgumentParser() 102 | parser.add_argument('--prefix', type = str, required = True, help = 'Prefix for prefix files') 103 | parser.add_argument('--namespace', type = str, help = 'Optional fully-qualified namespace') 104 | parser.add_argument('--list-define-name', 105 | type = str, 106 | help = 'Optional name for shader list define') 107 | parser.add_argument('sources', type = str, nargs = '+', help = 'Source list') 108 | 109 | args = parser.parse_args() 110 | 111 | bytecode_fp = open('{0}-bytecode.cxx'.format(args.prefix), 'w') 112 | include_fp = open('{0}-include.h'.format(args.prefix), 'w') 113 | 114 | header = '// Auto-generated by {0}\n'.format(__file__) 115 | bytecode_fp.write(header) 116 | include_fp.write(header) 117 | 118 | bytecode_fp.write("#define WIN32_LEAN_AND_MEAN\n") 119 | bytecode_fp.write("#include \n") 120 | bytecode_fp.write("#include \n") 121 | bytecode_fp.write("#include \n") 122 | bytecode_fp.write('\n') 123 | 124 | include_fp.write("#pragma once\n") 125 | include_fp.write("#include \n") 126 | include_fp.write("#include \n") 127 | include_fp.write('\n') 128 | 129 | fqns = args.namespace.split('::') if args.namespace else [] 130 | if fqns: 131 | for part in fqns: 132 | bytecode_fp.write('namespace {0} {{\n'.format(part)) 133 | include_fp.write('namespace {0} {{\n'.format(part)) 134 | bytecode_fp.write('\n') 135 | include_fp.write('\n') 136 | 137 | var_prefixes = [] 138 | 139 | for source_file in args.sources: 140 | m = re.match(r'([^.]+)\.([^.]+)\.([^.]+)\.h', source_file) 141 | var_prefix = m.group(2) 142 | if m is None: 143 | raise Exception('Sources must be in objname.varprefix.entrypoint.h form') 144 | 145 | bytecode_line = """#include "{0}" 146 | extern const uint8_t* {1}_Bytes = {1}_Bytes_Impl; 147 | extern const size_t {1}_Length = sizeof({1}_Bytes_Impl); 148 | """.format(source_file, var_prefix) 149 | bytecode_fp.write(bytecode_line) 150 | 151 | include_fp.write("""extern const uint8_t* {0}_Bytes; 152 | extern const size_t {0}_Length; 153 | """.format(var_prefix)) 154 | 155 | var_prefixes.append(var_prefix) 156 | 157 | bytecode_fp.write('\n') 158 | include_fp.write('\n') 159 | 160 | if args.list_define_name: 161 | include_fp.write('#define {0}(_) \\\n'.format(args.list_define_name)) 162 | for var_prefix in var_prefixes: 163 | include_fp.write(' _({0}) \\\n'.format(var_prefix)) 164 | include_fp.write(' // terminator\n') 165 | include_fp.write('\n') 166 | 167 | for part in fqns: 168 | bytecode_fp.write('}} // namespace {0}\n'.format(part)) 169 | include_fp.write('}} // namespace {0}\n'.format(part)) 170 | 171 | bytecode_fp.close() 172 | include_fp.close() 173 | 174 | if __name__ == '__main__': 175 | fxc_helper_tool() 176 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/vs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/frontend/v2_2/vs/__init__.py -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/vs/gen.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import ambuild2.frontend.vs.gen as vs_gen 18 | from ambuild2.frontend.v2_2.vs import cxx 19 | 20 | class Generator(vs_gen.Generator): 21 | def __init__(self, cm): 22 | super(Generator, self).__init__(cm) 23 | self.vs_version_number = cxx.Compiler.GetVersionFromVS(self.vs_version) 24 | self.vs_vendor = cxx.VisualStudio(self.vs_version_number) 25 | 26 | # Overridden. 27 | def detectCompilers(self, **kwargs): 28 | return cxx.Compiler(self.vs_vendor, kwargs.pop('target_arch', 'x86')) 29 | 30 | def newProgramProject(self, context, name): 31 | return cxx.Project(cxx.Program, name) 32 | 33 | def newLibraryProject(self, context, name): 34 | return cxx.Project(cxx.Library, name) 35 | 36 | def newStaticLibraryProject(self, context, name): 37 | return cxx.Project(cxx.StaticLibrary, name) 38 | -------------------------------------------------------------------------------- /ambuild2/frontend/v2_2/vs/nodes.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | class Node(object): 19 | def __init__(self, context, path): 20 | super(Node, self).__init__() 21 | self.context = context 22 | self.path = path 23 | self.children = set() 24 | self.parents = set() 25 | 26 | def addParent(self, parent): 27 | self.parents.add(parent) 28 | parent.children.add(self) 29 | 30 | class FolderNode(Node): 31 | def __init__(self, path): 32 | super(FolderNode, self).__init__(None, path) 33 | 34 | @property 35 | def kind(self): 36 | return 'folder' 37 | 38 | class ContainerNode(Node): 39 | def __init__(self, cx): 40 | super(ContainerNode, self).__init__(cx, None) 41 | 42 | @property 43 | def kind(self): 44 | return 'container' 45 | 46 | class OutputNode(Node): 47 | def __init__(self, context, path, parent): 48 | super(OutputNode, self).__init__(context, path) 49 | self.addParent(parent) 50 | 51 | @property 52 | def kind(self): 53 | return 'output' 54 | 55 | class ProjectNode(Node): 56 | def __init__(self, context, path, project): 57 | super(ProjectNode, self).__init__(context, path) 58 | self.project = project 59 | self.uuid = None 60 | 61 | @property 62 | def kind(self): 63 | return 'project' 64 | -------------------------------------------------------------------------------- /ambuild2/frontend/version.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | from ambuild2 import util 18 | 19 | class Version(object): 20 | def __init__(self, string): 21 | super(Version, self).__init__() 22 | if type(string) is int: 23 | self.string = str(string) 24 | self.vendor_name = None 25 | self.components = [string] 26 | else: 27 | self.string = string 28 | self.vendor_name, self.components = Version.split(string) 29 | 30 | def __str__(self): 31 | return self.string 32 | 33 | @staticmethod 34 | def split(string): 35 | vendor_pos = string.rfind('-') 36 | if vendor_pos != -1: 37 | vendor_name = string[:vendor_pos] 38 | version = string[vendor_pos + 1:] 39 | else: 40 | vendor_name = None 41 | version = string 42 | return vendor_name, [int(part) for part in version.split('.')] 43 | 44 | def __eq__(self, other): 45 | result = self.cmp_base(other) 46 | return result is not None and result == 0 47 | 48 | def __ne__(self, other): 49 | result = self.cmp_base(other) 50 | return result is None or result != 0 51 | 52 | def __le__(self, other): 53 | result = self.cmp_base(other) 54 | return result is not None and result <= 0 55 | 56 | def __lt__(self, other): 57 | result = self.cmp_base(other) 58 | return result is not None and result < 0 59 | 60 | def __gt__(self, other): 61 | result = self.cmp_base(other) 62 | return result is not None and result > 0 63 | 64 | def __ge__(self, other): 65 | result = self.cmp_base(other) 66 | return result is not None and result >= 0 67 | 68 | @staticmethod 69 | def parse(other): 70 | if hasattr(other, 'components'): 71 | components = other.components 72 | return getattr(other, 'vendor', None), other.components 73 | if type(other) is int: 74 | components = [other] 75 | return None, components 76 | return Version.split(str(other)) 77 | 78 | def cmp_base(self, other): 79 | vendor_name, components = Version.parse(other) 80 | 81 | # If this version or the other version doesn't care about the vendor name, 82 | # then return an ok comparison for compatibility. Otherwise, the vendor 83 | # names must match. 84 | if vendor_name is not None and self.vendor_name is not None: 85 | if vendor_name != self.vendor_name: 86 | return None 87 | return util.compare(self.components, components) 88 | -------------------------------------------------------------------------------- /ambuild2/frontend/vs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alliedmodders/ambuild/341e8be03aad91b7b81774fdcd7ffa1f34452552/ambuild2/frontend/vs/__init__.py -------------------------------------------------------------------------------- /ambuild2/frontend/vs/gen.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | import os, errno 18 | import uuid as uuids 19 | from ambuild2 import util 20 | from ambuild2 import nodetypes 21 | from ambuild2.frontend import paths 22 | from ambuild2.frontend.vs import nodes 23 | from ambuild2.frontend.base_generator import BaseGenerator 24 | 25 | SupportedVersions = ['10', '11', '12', '14', '15', '16', '17'] 26 | YearMap = { 27 | '2010': 10, 28 | '2012': 11, 29 | '2013': 12, 30 | '2015': 14, 31 | '2017': 15, 32 | '2019': 16, 33 | '2022': 17, 34 | } 35 | 36 | class Generator(BaseGenerator): 37 | def __init__(self, cm): 38 | super(Generator, self).__init__(cm) 39 | self.compiler = None 40 | self.vs_version = None 41 | self.files_ = {} 42 | self.projects_ = set() 43 | 44 | if self.cm.options.vs_version in SupportedVersions: 45 | self.vs_version = int(self.cm.options.vs_version) 46 | else: 47 | if self.cm.options.vs_version not in YearMap: 48 | util.con_err( 49 | util.ConsoleRed, 50 | 'Unsupported Visual Studio version: {0}'.format(self.cm.options.vs_version), 51 | util.ConsoleNormal) 52 | raise Exception('Unsupported Visual Studio version: {0}'.format( 53 | self.cm.options.vs_version)) 54 | self.vs_version = YearMap[self.cm.options.vs_version] 55 | 56 | self.cacheFile = os.path.join(self.cm.buildPath, '.cache') 57 | try: 58 | with open(self.cacheFile, 'rb') as fp: 59 | self.vars_ = util.pickle.load(fp) 60 | except: 61 | self.vars_ = {} 62 | 63 | if 'uuids' not in self.vars_: 64 | self.vars_['uuids'] = {} 65 | 66 | self.target_platform = 'windows' 67 | 68 | # Overridden. 69 | @property 70 | def backend(self): 71 | return 'vs' 72 | 73 | # Overridden. 74 | def preGenerate(self): 75 | pass 76 | 77 | # Overriden. 78 | def postGenerate(self): 79 | self.generateProjects() 80 | with open(self.cacheFile, 'wb') as fp: 81 | util.DiskPickle(self.vars_, fp) 82 | 83 | def generateProjects(self): 84 | for node in self.projects_: 85 | # We cache uuids across runs to keep them consistent. 86 | node.uuid = self.vars_['uuids'].get(node.path) 87 | if node.uuid is None: 88 | node.uuid = str(uuids.uuid1()).upper() 89 | self.vars_['uuids'][node.path] = node.uuid 90 | node.project.export(self.cm, node) 91 | 92 | # Overridden. 93 | # 94 | # We don't support reconfiguring in this frontend. 95 | def addConfigureFile(self, cx, path): 96 | pass 97 | 98 | def detectCompilers(self): 99 | raise Exception('Implement me!') 100 | 101 | # Overridden. 102 | def enterContext(self, cx): 103 | cx.vs_nodes = [] 104 | 105 | # Overridden. 106 | def leaveContext(self, cx): 107 | pass 108 | 109 | def ensureUnique(self, path): 110 | if path in self.files_: 111 | entry = self.files_[path] 112 | util.con_err(util.ConsoleRed, 113 | 'Path {0} already exists as: {1}'.format(path, 114 | entry.kind), util.ConsoleNormal) 115 | raise Exception('Path {0} already exists as: {1}'.format(path, entry.kind)) 116 | 117 | # Overridden. 118 | def getLocalFolder(self, context): 119 | if self.cm.apiVersion < '2.1': 120 | if type(context.localFolder_) is nodetypes.Entry or context.localFolder_ is None: 121 | return context.localFolder_ 122 | 123 | if len(context.buildFolder): 124 | context.localFolder_ = self.generateFolder(None, context.buildFolder) 125 | else: 126 | context.localFolder_ = None 127 | return context.localFolder_ 128 | 129 | if len(context.buildFolder): 130 | return self.generateFolder(None, context.buildFolder) 131 | return None 132 | 133 | def generateFolder(self, parent, folder): 134 | _, path = paths.ResolveFolder(parent, folder) 135 | if path in self.files_: 136 | entry = self.files_[path] 137 | if type(entry) is not nodes.FolderNode: 138 | self.ensureUnique(path) # Will always throw. 139 | return entry 140 | 141 | try: 142 | os.makedirs(path) 143 | except OSError as exn: 144 | if not (exn.errno == errno.EEXIST and os.path.isdir(path)): 145 | raise 146 | 147 | obj = nodes.FolderNode(path) 148 | self.files_[path] = obj 149 | return obj 150 | 151 | # Overridden. 152 | def addFolder(self, cx, folder): 153 | parentFolderNode = None 154 | if cx is not None: 155 | parentFolderNode = cx.localFolder 156 | 157 | return self.generateFolder(parentFolderNode, folder) 158 | 159 | # Overridden. 160 | def addCopy(self, context, source, output_path): 161 | return (None, (None,)) 162 | 163 | # Overridden. 164 | def addOutputFile(self, context, path, contents): 165 | return nodes.Node(context, path) 166 | 167 | # Overridden. 168 | def addShellCommand(self, 169 | context, 170 | inputs, 171 | argv, 172 | outputs, 173 | folder = -1, 174 | dep_type = None, 175 | weak_inputs = None, 176 | shared_outputs = None): 177 | print(inputs, argv, outputs, folder, dep_type, weak_inputs, shared_outputs) 178 | 179 | def addOutput(self, context, path, parent): 180 | self.ensureUnique(path) 181 | 182 | node = nodes.OutputNode(context, path, parent) 183 | self.files_[path] = node 184 | return node 185 | 186 | def addProjectNode(self, context, project): 187 | self.ensureUnique(project.path) 188 | self.projects_.add(project) 189 | self.files_[project.path] = project 190 | -------------------------------------------------------------------------------- /ambuild2/frontend/vs/nodes.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | class Node(object): 19 | def __init__(self, context, path): 20 | super(Node, self).__init__() 21 | self.context = context 22 | self.path = path 23 | self.children = set() 24 | self.parents = set() 25 | 26 | def addParent(self, parent): 27 | self.parents.add(parent) 28 | parent.children.add(self) 29 | 30 | class FolderNode(Node): 31 | def __init__(self, path): 32 | super(FolderNode, self).__init__(None, path) 33 | 34 | @property 35 | def kind(self): 36 | return 'folder' 37 | 38 | class ContainerNode(Node): 39 | def __init__(self, cx): 40 | super(ContainerNode, self).__init__(cx, None) 41 | 42 | @property 43 | def kind(self): 44 | return 'container' 45 | 46 | class OutputNode(Node): 47 | def __init__(self, context, path, parent): 48 | super(OutputNode, self).__init__(context, path) 49 | self.addParent(parent) 50 | 51 | @property 52 | def kind(self): 53 | return 'output' 54 | 55 | class ProjectNode(Node): 56 | def __init__(self, context, path, project): 57 | super(ProjectNode, self).__init__(context, path) 58 | self.project = project 59 | self.uuid = None 60 | 61 | @property 62 | def kind(self): 63 | return 'project' 64 | -------------------------------------------------------------------------------- /ambuild2/frontend/vs/xmlbuilder.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | 18 | class XmlScope(object): 19 | def __init__(self, builder, tag, **kwargs): 20 | self.builder_ = builder 21 | self.tag_ = tag 22 | self.kwargs_ = kwargs 23 | 24 | def __enter__(self): 25 | self.builder_.enter(self.tag_, **self.kwargs_) 26 | 27 | def __exit__(self, type, value, traceback): 28 | self.builder_.leave(self.tag_) 29 | 30 | class XmlBuilder(object): 31 | def __init__(self, fp, version = "1.0", encoding = "utf-8"): 32 | super(XmlBuilder, self).__init__() 33 | self.fp_ = fp 34 | self.indent_ = 0 35 | self.write(''.format(version, encoding)) 36 | 37 | def block(self, tag, **kwargs): 38 | return XmlScope(self, tag, **kwargs) 39 | 40 | def tag(self, tag, contents = None, **kwargs): 41 | open = self.build_element(tag, **kwargs) 42 | if contents is None: 43 | self.write('<{0} />'.format(open)) 44 | else: 45 | self.write('<{0}>{1}'.format(open, contents, tag)) 46 | 47 | # Internal. 48 | def enter(self, tag, **kwargs): 49 | elt = self.build_element(tag, **kwargs) 50 | self.write('<{0}>'.format(elt)) 51 | self.indent_ += 1 52 | 53 | def leave(self, tag): 54 | self.indent_ -= 1 55 | self.write(''.format(tag)) 56 | 57 | def build_element(self, tag, **kwargs): 58 | if len(kwargs) == 0: 59 | return '{0}'.format(tag) 60 | 61 | props = [] 62 | for key in kwargs: 63 | props.append('{0}="{1}"'.format(key, kwargs[key])) 64 | attrs = ' '.join(props) 65 | return '{0} {1}'.format(tag, attrs) 66 | 67 | def write(self, line): 68 | self.fp_.write(' ' * self.indent_) 69 | self.fp_.write(line) 70 | self.fp_.write('\n') 71 | -------------------------------------------------------------------------------- /ambuild2/graph.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 sw=2 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | from ambuild2 import nodetypes 18 | 19 | class GraphNode(object): 20 | def __init__(self, entry): 21 | self.entry = entry 22 | self.incoming = set() 23 | self.outgoing = set() 24 | self.outputs = set() 25 | self.is_command = entry.isCommand() 26 | 27 | @property 28 | def type(self): 29 | return self.entry.type 30 | 31 | def isCommand(self): 32 | return self.is_command 33 | 34 | class Graph(object): 35 | def __init__(self, database): 36 | self.db = database 37 | self.node_map = {} 38 | self.node_list = [] 39 | self.worklist = [] 40 | self.create = [] 41 | 42 | def importEntry(self, entry): 43 | assert entry not in self.node_map 44 | 45 | graph_node = GraphNode(entry) 46 | self.node_map[entry] = graph_node 47 | self.node_list.append(graph_node) 48 | self.worklist.append(graph_node) 49 | return graph_node 50 | 51 | def addEntry(self, entry): 52 | if entry in self.node_map: 53 | return self.node_map[entry] 54 | 55 | return self.importEntry(entry) 56 | 57 | def addEdge(self, from_node, to_node): 58 | from_node.outgoing.add(to_node) 59 | to_node.incoming.add(from_node) 60 | 61 | def addEdgeToEntry(self, from_node, to_entry): 62 | if to_entry not in self.node_map: 63 | to_node = self.importEntry(to_entry) 64 | else: 65 | to_node = self.node_map[to_entry] 66 | self.addEdge(from_node, to_node) 67 | 68 | def integrate(self): 69 | while len(self.worklist): 70 | node = self.worklist.pop() 71 | 72 | for child_entry in self.db.query_outgoing(node.entry): 73 | self.addEdgeToEntry(node, child_entry) 74 | 75 | def complete_ordering(self): 76 | for node in self.node_list: 77 | if not node.isCommand(): 78 | continue 79 | 80 | for weak_input in self.db.query_weak_inputs(node.entry): 81 | if weak_input not in self.node_map: 82 | continue 83 | dep = self.node_map[weak_input] 84 | dep.outgoing.add(node) 85 | node.incoming.add(dep) 86 | 87 | # We should try to get rid of this algorithm; it's very expensive and not 88 | # really needed if we just include non-command nodes in the compressed 89 | # version of the graph. 90 | def filter_commands(self): 91 | worklist = [node for node in self.node_list if not node.isCommand()] 92 | for node in worklist: 93 | for output in node.outgoing: 94 | output.incoming.remove(node) 95 | output_delta = node.incoming - output.incoming 96 | output.incoming |= output_delta 97 | for x in output_delta: 98 | x.outgoing.add(output) 99 | 100 | for input in node.incoming: 101 | input.outgoing.remove(node) 102 | input_delta = node.outgoing - input.outgoing 103 | input.outgoing |= input_delta 104 | for x in input_delta: 105 | x.incoming.add(input) 106 | 107 | self.node_list = [node for node in self.node_list if node.isCommand()] 108 | 109 | def finish(self): 110 | self.integrate() 111 | self.complete_ordering() 112 | self.node_map = None 113 | 114 | @property 115 | def leafs(self): 116 | return [node for node in self.node_list if not len(node.incoming)] 117 | 118 | def for_each_child_of(self, node, callback): 119 | for outgoing in node.outgoing: 120 | if outgoing.isCommand(): 121 | callback(outgoing.entry) 122 | else: 123 | self.for_each_child_of(outgoing, callback) 124 | 125 | def for_each_leaf_command(self, callback): 126 | for node in self.leafs: 127 | if node.isCommand(): 128 | callback(node.entry) 129 | else: 130 | self.for_each_child_of(node, callback) 131 | 132 | def printGraph(self): 133 | for entry in self.create: 134 | print(' : ' + entry.format()) 135 | 136 | def printNode(node, indent): 137 | print((' ' * indent) + ' - ' + node.entry.format()) 138 | for incoming in node.incoming: 139 | printNode(incoming, indent + 1) 140 | 141 | for node in [node for node in self.node_list if not len(node.incoming)]: 142 | printNode(node, 0) 143 | -------------------------------------------------------------------------------- /ambuild2/make_parser.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=4 ts=8 sw=4 tw=99 et: 2 | 3 | def LineHasContinuation(line): 4 | num_escapes = 0 5 | 6 | pos = len(line) - 1 7 | while pos >= 0: 8 | if line[pos] != '\\': 9 | break 10 | num_escapes += 1 11 | pos -= 1 12 | 13 | return num_escapes % 2 == 1 14 | 15 | def Preprocess(lines): 16 | chars = '' 17 | 18 | ignoring = False 19 | for line in lines: 20 | if line.endswith('\n'): 21 | line = line[:len(line) - 1] 22 | has_continuation = LineHasContinuation(line) 23 | if has_continuation: 24 | if ignoring: 25 | continue 26 | if line.startswith('#'): 27 | ignoring = True 28 | continue 29 | chars += line[:len(line) - 1] 30 | else: 31 | ignoring = False 32 | if line.startswith('#'): 33 | continue 34 | chars += line 35 | return chars 36 | 37 | class Parser(object): 38 | def __init__(self, fp): 39 | self.chars_ = Preprocess(fp.readlines()) 40 | self.pos_ = 0 41 | 42 | def parse(self): 43 | while True: 44 | tok = self.lex(':') 45 | if tok is None or tok == ':': 46 | break 47 | if tok == '\n' or tok == '\r': 48 | raise Exception('Unexpected newline in make dependency rules') 49 | 50 | if tok is None: 51 | raise Exception('No Makefile dependency rules found') 52 | 53 | self.pos_ += 1 54 | 55 | items = [] 56 | while True: 57 | tok = self.lex() 58 | if tok == '\n' or tok == '\r' or tok is None: 59 | break 60 | if tok.isspace(): 61 | continue 62 | items.append(tok) 63 | return items 64 | 65 | def lex(self, stop_token = None): 66 | token = self.peek() 67 | if token is None: 68 | return None 69 | self.pos_ += 1 70 | 71 | if token.isspace(): 72 | return token 73 | 74 | while True: 75 | c = self.peek() 76 | if c is None or c == stop_token or c.isspace(): 77 | break 78 | self.pos_ += 1 79 | if c == '\\': 80 | next_char = self.peek() 81 | if next_char is None: 82 | token += '\\' 83 | continue 84 | token += next_char 85 | self.pos_ += 1 86 | else: 87 | token += c 88 | 89 | return token 90 | 91 | def skip_newline(self, c): 92 | if c == '\r': 93 | self.pos_ += 1 94 | if self.peek() == '\n': 95 | self.pos_ += 1 96 | return True 97 | if self.peek() == '\n': 98 | self.pos_ += 1 99 | return True 100 | return False 101 | 102 | def peek(self): 103 | if self.pos_ >= len(self.chars_): 104 | return None 105 | return self.chars_[self.pos_] 106 | 107 | def ParseDependencyFile(filename, fp): 108 | p = Parser(fp) 109 | return p.parse() 110 | -------------------------------------------------------------------------------- /ambuild2/run.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=4 sw=4 tw=99 et: 2 | # 3 | # This file is part of AMBuild. 4 | # 5 | # AMBuild is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # AMBuild is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with AMBuild. If not, see . 17 | from __future__ import print_function 18 | import os, sys 19 | from optparse import OptionParser 20 | from ambuild2 import util 21 | from ambuild2.context import Context 22 | 23 | DEFAULT_API = '2.2.4' 24 | CURRENT_API = '2.2.4' 25 | 26 | SampleScript = """# vim: set sts=4 ts=8 sw=4 tw=99 et ft=python: 27 | builder.cxx = builder.DetectCxx() 28 | if builder.cxx.like('gcc'): 29 | builder.cxx.cflags += [ 30 | '-Wall', 31 | '-Werror' 32 | ] 33 | 34 | program = builder.cxx.Program('sample') 35 | program.sources += [ 36 | 'main.cpp', 37 | ] 38 | builder.Add(program) 39 | """ 40 | 41 | SampleConfigure = """# vim: set sts=4 ts=8 sw=4 tw=99 et: 42 | API_VERSION = '{DEFAULT_API}' 43 | 44 | import sys 45 | try: 46 | from ambuild2 import run 47 | if not run.HasAPI(API_VERSION): 48 | raise Exception() 49 | except: 50 | sys.stderr.write('AMBuild {{0}} must be installed to build this project.\\n'.format(API_VERSION)) 51 | sys.stderr.write('http://www.alliedmods.net/ambuild\\n') 52 | sys.exit(1) 53 | 54 | builder = run.BuildParser(sourcePath = sys.path[0], api=API_VERSION) 55 | builder.Configure() 56 | """.format(DEFAULT_API = DEFAULT_API) 57 | 58 | def BuildOptions(): 59 | parser = OptionParser("usage: %prog [options] [path]") 60 | parser.add_option("--no-color", 61 | dest = "no_color", 62 | action = "store_true", 63 | default = False, 64 | help = "Disable console colors.") 65 | parser.add_option("--show-graph", 66 | dest = "show_graph", 67 | action = "store_true", 68 | default = False, 69 | help = "Show the dependency graph and then exit.") 70 | parser.add_option("--show-changed", 71 | dest = "show_changed", 72 | action = "store_true", 73 | default = False, 74 | help = "Show the list of dirty nodes and then exit.") 75 | parser.add_option("--show-damage", 76 | dest = "show_damage", 77 | action = "store_true", 78 | default = False, 79 | help = "Show the computed change graph and then exit.") 80 | parser.add_option("--show-commands", 81 | dest = "show_commands", 82 | action = "store_true", 83 | default = False, 84 | help = "Show the computed command graph and then exit.") 85 | parser.add_option("--show-steps", 86 | dest = "show_steps", 87 | action = "store_true", 88 | default = False, 89 | help = "Show the computed build steps and then exit.") 90 | parser.add_option( 91 | "-j", 92 | "--jobs", 93 | dest = "jobs", 94 | type = "int", 95 | default = 0, 96 | help = "Number of worker processes. Minimum number is 1; default is #cores * 1.25.") 97 | parser.add_option('--refactor', 98 | dest = "refactor", 99 | action = "store_true", 100 | default = False, 101 | help = "Abort the build if the dependency graph would change.") 102 | parser.add_option('--new-project', 103 | dest = "new_project", 104 | action = "store_true", 105 | default = False, 106 | help = "Export a sample AMBuildScript in the current folder.") 107 | 108 | options, argv = parser.parse_args() 109 | 110 | if len(argv) > 1: 111 | parser.error("expected path, found extra arguments") 112 | 113 | if options.new_project: 114 | if os.path.exists('AMBuildScript'): 115 | sys.stderr.write('An AMBuildScript file already exists here; aborting.\n') 116 | sys.exit(1) 117 | if os.path.exists('configure.py'): 118 | sys.stderr.write('A configure.py file already exists here; aborting.\n') 119 | sys.exit(1) 120 | 121 | with open('AMBuildScript', 'w') as fp: 122 | fp.write(SampleScript) 123 | with open('configure.py', 'w') as fp: 124 | fp.write(SampleConfigure) 125 | 126 | sys.stdout.write('Sample AMBuildScript and configure.py scripts generated.\n') 127 | sys.exit(0) 128 | 129 | return options, argv 130 | 131 | def Build(buildPath, options, argv): 132 | with util.FolderChanger(buildPath): 133 | with Context(buildPath, options, argv) as cx: 134 | return cx.Build() 135 | 136 | def CompatBuild(buildPath): 137 | options, argv = BuildOptions() 138 | return Build(buildPath, options, argv) 139 | 140 | def PrepareBuild(sourcePath, buildPath = None): 141 | return BuildParser(sourcePath, '2.0', buildPath) 142 | 143 | class ApiVersionNotFoundException(Exception): 144 | def __init__(self, *args, **kwargs): 145 | super(ApiVersionNotFoundException, self).__init__(*args, **kwargs) 146 | 147 | def PreparerForAPI(api): 148 | if api == '2.0': 149 | from ambuild2.frontend.v2_0.prep import Preparer 150 | elif api == '2.1' or api.startswith('2.1.'): 151 | from ambuild2.frontend.v2_1 import Preparer 152 | elif api == '2.2' or api.startswith('2.2.'): 153 | from ambuild2.frontend.v2_2.prep import Preparer 154 | else: 155 | message = "AMBuild {} not found; {} is installed. Do you need to upgrade?\n".format( 156 | api, CURRENT_API) 157 | raise ApiVersionNotFoundException(message) 158 | 159 | return Preparer 160 | 161 | def HasAPI(api): 162 | try: 163 | if PreparerForAPI(api) is not None: 164 | return True 165 | except: 166 | pass 167 | return False 168 | 169 | def BuildParser(sourcePath, api, buildPath = None): 170 | if buildPath == None: 171 | buildPath = os.path.abspath(os.getcwd()) 172 | 173 | Preparer = PreparerForAPI(api) 174 | return Preparer(sourcePath = sourcePath, buildPath = buildPath) 175 | 176 | def cli_run(): 177 | options, argv = BuildOptions() 178 | 179 | if not len(argv): 180 | folder = '.' 181 | else: 182 | folder = argv[0] 183 | if not os.path.exists(folder): 184 | sys.stderr.write('Error: path does not exist: {0}\n'.format(folder)) 185 | sys.exit(1) 186 | 187 | cache_path = os.path.join(folder, '.ambuild2', 'graph') 188 | if not os.path.exists(cache_path): 189 | sys.stderr.write('Error: folder was not configured for AMBuild.\n') 190 | sys.exit(1) 191 | 192 | if not Build(os.path.abspath(folder), options, argv): 193 | sys.exit(1) 194 | -------------------------------------------------------------------------------- /scripts/ambuild_dsymutil_wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "Usage: <...linker args>"; 5 | exit 1; 6 | fi 7 | 8 | LD_FILE=$1 9 | LD_EXEC=$2 10 | 11 | shift; 12 | shift; 13 | 14 | $LD_EXEC $@ 15 | if [ $? -ne 0 ]; then 16 | exit $? 17 | fi 18 | dsymutil $LD_FILE 19 | if [ $? -ne 0 ]; then 20 | exit $? 21 | fi 22 | strip -S $LD_FILE 23 | if [ $? -ne 0 ]; then 24 | exit $? 25 | fi 26 | 27 | -------------------------------------------------------------------------------- /scripts/ambuild_objcopy_wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "Usage: <...linker args>"; 5 | exit 1; 6 | fi 7 | 8 | LD_FILE=$1 9 | LD_EXEC=$2 10 | 11 | shift; 12 | shift; 13 | 14 | $LD_EXEC $@ 15 | if [ $? -ne 0 ]; then 16 | exit $? 17 | fi 18 | objcopy --only-keep-debug $LD_FILE $LD_FILE.dbg 19 | if [ $? -ne 0 ]; then 20 | exit $? 21 | fi 22 | objcopy --strip-debug $LD_FILE 23 | if [ $? -ne 0 ]; then 24 | exit $? 25 | fi 26 | objcopy --add-gnu-debuglink=$LD_FILE.dbg $LD_FILE 27 | if [ $? -ne 0 ]; then 28 | exit $? 29 | fi 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set ts=4 sw=4 tw=99 et: 3 | 4 | import sys 5 | 6 | def detect_distutils(): 7 | sys.path.pop(0) 8 | try: 9 | import ambuild2.util 10 | try: 11 | val = getattr(ambuild2.util, 'INSTALLED_BY_PIP_OR_SETUPTOOLS') 12 | except AttributeError: 13 | sys.exit(1) 14 | except ImportError: 15 | pass 16 | 17 | sys.exit(0) 18 | 19 | # This if statement is supposedly required by multiprocessing. 20 | if __name__ == '__main__': 21 | import os 22 | import multiprocessing as mp 23 | 24 | # Python 3.10+ appears to not be able to pickle detect_distutils for some reason. 25 | if sys.version_info.major == 3 and sys.version_info.minor < 10: 26 | mp.freeze_support() 27 | proc = mp.Process(target = detect_distutils) 28 | proc.start() 29 | proc.join() 30 | 31 | if proc.exitcode != 0: 32 | sys.stderr.write("You have an older installation of AMBuild. AMBuild must\n") 33 | sys.stderr.write("now be installed using pip (see README.md). To prevent\n") 34 | sys.stderr.write("conflicts, please remove the old distutils version. You can\n") 35 | sys.stderr.write("do this by inspecting the following paths and removing\n") 36 | sys.stderr.write("any ambuild folders:\n") 37 | 38 | for path in sys.path[1:]: 39 | for subdir in ['ambuild', 'ambuild2']: 40 | subpath = os.path.join(path, subdir) 41 | if os.path.exists(subpath): 42 | sys.stderr.write('\t{}\n'.format(subpath)) 43 | 44 | sys.stderr.write('Aborting installation.\n') 45 | sys.stderr.flush() 46 | sys.exit(1) 47 | 48 | from setuptools import setup, find_packages 49 | try: 50 | import sqlite3 51 | except: 52 | raise SystemError('py-sqlite3 must be installed') 53 | 54 | amb_scripts = [] 55 | if sys.platform != 'win32': 56 | if sys.platform == 'darwin': 57 | amb_scripts.append('scripts/ambuild_dsymutil_wrapper.sh') 58 | else: 59 | amb_scripts.append('scripts/ambuild_objcopy_wrapper.sh') 60 | 61 | setup(name = 'AMBuild', 62 | version = '2.0', 63 | description = 'AlliedModders Build System', 64 | author = 'David Anderson', 65 | author_email = 'dvander@alliedmods.net', 66 | url = 'http://www.alliedmods.net/ambuild', 67 | packages = find_packages(), 68 | python_requires = '>=3.3', 69 | entry_points = {'console_scripts': ['ambuild = ambuild2.run:cli_run']}, 70 | scripts = amb_scripts, 71 | zip_safe = False) 72 | -------------------------------------------------------------------------------- /tests/always_dirty/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | import os 3 | 4 | _, outputs = builder.AddCommand( 5 | inputs = builder.ALWAYS_DIRTY, 6 | outputs = ['sample.h'], 7 | argv = ['python', os.path.join(builder.sourcePath, 'generate.py')] 8 | ) 9 | -------------------------------------------------------------------------------- /tests/always_dirty/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | API_VERSION = '2.1' 3 | 4 | import sys 5 | try: 6 | from ambuild2 import run 7 | if not run.HasAPI(API_VERSION): 8 | raise Exception() 9 | except: 10 | sys.stderr.write('AMBuild {0} must be installed to build this project.\n'.format(API_VERSION)) 11 | sys.stderr.write('http://www.alliedmods.net/ambuild\n') 12 | sys.exit(1) 13 | 14 | builder = run.BuildParser(sourcePath = sys.path[0], api = API_VERSION) 15 | builder.Configure() 16 | -------------------------------------------------------------------------------- /tests/always_dirty/generate.py: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 sw=2 tw=99 et: 2 | import datetime 3 | 4 | def main(): 5 | with open('sample.h', 'w') as fp: 6 | fp.write("const char* DATE = {0};\n".format(datetime.datetime.now())) 7 | 8 | if __name__ == '__main__': 9 | main() 10 | -------------------------------------------------------------------------------- /tests/api2_2/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | builder.cxx = builder.DetectCxx() 3 | builder.Build('core/AMBuild') 4 | if '-Wall' in builder.cxx.cflags: 5 | raise Exception('Child build script did not inherit properly') 6 | -------------------------------------------------------------------------------- /tests/api2_2/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | API_VERSION = '2.2' 3 | 4 | import sys 5 | try: 6 | from ambuild2 import run 7 | if not run.HasAPI(API_VERSION): 8 | raise Exception() 9 | except: 10 | sys.stderr.write('AMBuild {0} must be installed to build this project.\n'.format(API_VERSION)) 11 | sys.stderr.write('http://www.alliedmods.net/ambuild\n') 12 | sys.exit(1) 13 | 14 | builder = run.BuildParser(sourcePath = sys.path[0], api = API_VERSION) 15 | builder.Configure() 16 | -------------------------------------------------------------------------------- /tests/api2_2/core/AMBuild: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | 3 | # This should only change the cxx for this builder. 4 | if builder.cxx.like('gcc'): 5 | builder.cxx.cflags += ['-Wall', '-Werror'] 6 | else: 7 | builder.cxx.cflags += ['/WX'] 8 | 9 | program = builder.cxx.Program('sample') 10 | program.sources += [ 11 | 'main.cc', 12 | ] 13 | 14 | builder.Add(program) 15 | -------------------------------------------------------------------------------- /tests/api2_2/core/main.cc: -------------------------------------------------------------------------------- 1 | 2 | int main() 3 | { 4 | return 0; 5 | } 6 | -------------------------------------------------------------------------------- /tests/autoinclude/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | builder.DetectCxx() 5 | 6 | argv = [ 7 | sys.executable, 8 | os.path.join(builder.sourcePath, 'generate_header.py'), 9 | os.path.join(builder.buildPath, 'output.h') 10 | ] 11 | 12 | outputs = [ 13 | os.path.join(builder.buildFolder, 'output.h') 14 | ] 15 | 16 | sources = [ 17 | os.path.join(builder.sourcePath, 'activate.txt'), 18 | argv[1] 19 | ] 20 | cmd_node, (output_header,) = builder.AddCommand( 21 | inputs = sources, 22 | argv = argv, 23 | outputs = outputs 24 | ) 25 | 26 | program = builder.cxx.Library("hello") 27 | program.compiler.includes += [builder.buildPath] 28 | program.compiler.sourcedeps += [output_header] 29 | program.sources = [ 30 | 'main.cpp' 31 | ] 32 | builder.Add(program) 33 | -------------------------------------------------------------------------------- /tests/autoinclude/activate.txt: -------------------------------------------------------------------------------- 1 | I don't do anything. 2 | -------------------------------------------------------------------------------- /tests/autoinclude/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.BuildParser(sourcePath = sys.path[0], api = '2.1') 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/autoinclude/generate_header.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | 4 | with open(sys.argv[1], 'w') as fp: 5 | fp.write(""" 6 | #ifndef HELLO_STRING 7 | # define HELLO_STRING "HELLO!" 8 | #endif 9 | """) 10 | -------------------------------------------------------------------------------- /tests/autoinclude/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | printf("%s\n", HELLO_STRING); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tests/cx_paths/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | builder.DetectCxx() 3 | if builder.cxx.family == 'gcc': 4 | builder.cxx.cflags += [ 5 | '-Wall', 6 | '-Werror' 7 | ] 8 | 9 | builder.Build('helper/helper.ambuild') 10 | -------------------------------------------------------------------------------- /tests/cx_paths/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.BuildParser(sourcePath = sys.path[0], api = "2.1") 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/cx_paths/helper/helper.ambuild: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | 3 | builder.Build('/program.ambuild') 4 | -------------------------------------------------------------------------------- /tests/cx_paths/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char** argv) 4 | { 5 | printf("hello!\n"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/cx_paths/program.ambuild: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | program = builder.cxx.Program('sample') 3 | program.sources += [ 4 | 'main.cpp', 5 | ] 6 | builder.Add(program) 7 | -------------------------------------------------------------------------------- /tests/dsymutil/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | builder.DetectCompilers() 5 | 6 | program = builder.compiler.Program("hello") 7 | program.sources = [ 8 | 'main.cpp' 9 | ] 10 | builder.Add(program) 11 | -------------------------------------------------------------------------------- /tests/dsymutil/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.PrepareBuild(sourcePath = sys.path[0]) 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/dsymutil/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | return printf("hello!\n"); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /tests/invalid_symlink/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | builder.DetectCompilers() 5 | 6 | program = builder.compiler.Program("hello") 7 | program.sources = [ 8 | 'main.cpp' 9 | ] 10 | out = builder.Add(program) 11 | builder.AddSymlink(out.binary, 'hello.bin') 12 | -------------------------------------------------------------------------------- /tests/invalid_symlink/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.PrepareBuild(sourcePath = sys.path[0]) 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/invalid_symlink/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char **argv) 4 | { 5 | return printf("hello!\n"); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /tests/modules/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | builder.DetectCxx() 3 | 4 | builder.Build('core/AMBuild') 5 | -------------------------------------------------------------------------------- /tests/modules/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.BuildParser(sourcePath = sys.path[0], api = "2.1") 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/modules/core/AMBuild: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | 3 | program = builder.cxx.Program('sample') 4 | program.sources += [ 5 | 'main.cc', 6 | ] 7 | 8 | m1 = program.Module(builder, 'm1') 9 | m1.sources += [ 10 | 'main2.cc', 11 | ] 12 | 13 | builder.Build('m2/AMBuild', { 'program': program }) 14 | 15 | builder.Add(program) 16 | -------------------------------------------------------------------------------- /tests/modules/core/m2/AMBuild: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | 3 | m2 = program.Module(builder, 'm2') 4 | m2.compiler.cflags = [] 5 | m2.sources += [ 6 | 'm2.cc', 7 | ] 8 | -------------------------------------------------------------------------------- /tests/modules/core/m2/m2.cc: -------------------------------------------------------------------------------- 1 | int m2() 2 | { 3 | return 0; 4 | } 5 | -------------------------------------------------------------------------------- /tests/modules/core/main.cc: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | extern int m1(); 5 | 6 | int main() 7 | { 8 | return m1(); 9 | } 10 | -------------------------------------------------------------------------------- /tests/modules/core/main2.cc: -------------------------------------------------------------------------------- 1 | 2 | int m1() 3 | { 4 | return 0; 5 | } 6 | -------------------------------------------------------------------------------- /tests/multiarch/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | import platform 3 | from ambuild2.frontend.version import Version 4 | 5 | def has_x86(): 6 | if builder.host.platform == 'mac': 7 | version = Version(platform.mac_ver()[0]) 8 | return version < '10.13' 9 | return True 10 | 11 | if has_x86(): 12 | builder.x86 = builder.DetectCxx(target_arch = 'x86') 13 | else: 14 | builder.x86 = None 15 | builder.x64 = builder.DetectCxx(target_arch = 'x86_64') 16 | 17 | for cxx in [builder.x86, builder.x64]: 18 | if cxx and cxx.like('msvc'): 19 | cxx.cxxflags += ['/EHsc'] 20 | 21 | builder.Build('core/AMBuild') 22 | -------------------------------------------------------------------------------- /tests/multiarch/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | API_VERSION = '2.2' 3 | 4 | import sys 5 | try: 6 | from ambuild2 import run 7 | if not run.HasAPI(API_VERSION): 8 | raise Exception() 9 | except: 10 | sys.stderr.write('AMBuild {0} must be installed to build this project.\n'.format(API_VERSION)) 11 | sys.stderr.write('http://www.alliedmods.net/ambuild\n') 12 | sys.exit(1) 13 | 14 | builder = run.BuildParser(sourcePath = sys.path[0], api = API_VERSION) 15 | builder.Configure() 16 | -------------------------------------------------------------------------------- /tests/multiarch/core/AMBuild: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | 3 | project = builder.ProgramProject('sample') 4 | project.sources += [ 5 | 'main.cpp', 6 | ] 7 | 8 | for cxx in [builder.x86, builder.x64]: 9 | if cxx is None: 10 | continue 11 | 12 | if cxx.like('gcc'): 13 | cxx.cflags += [ 14 | '-Wall', 15 | '-Werror' 16 | ] 17 | elif cxx.like('msvc'): 18 | cxx.cflags += ['/WX'] 19 | 20 | project.Configure(cxx, 'sample', cxx.target.arch) 21 | 22 | builder.Add(project) 23 | -------------------------------------------------------------------------------- /tests/multiarch/core/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | std::cout << sizeof(void*) << std::endl; 5 | } 6 | -------------------------------------------------------------------------------- /tests/originalcwd/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | # This should print the same thing as when configure.py'd, no matter where 5 | # we build from, when AMBuildScript changes. 6 | print(builder.originalCwd) 7 | -------------------------------------------------------------------------------- /tests/originalcwd/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.PrepareBuild(sourcePath = sys.path[0]) 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/precompiled-headers/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | builder.cxx = builder.DetectCxx() 3 | 4 | headers = builder.cxx.PrecompiledHeaders('all-headers', 'c++') 5 | headers.sources += [ 6 | 'vector', 7 | 'string', 8 | 'map', 9 | 'unordered_map', 10 | 'unordered_set', 11 | 'list', 12 | 'utility', 13 | 'functional', 14 | ] 15 | pch = builder.Add(headers) 16 | 17 | program = builder.cxx.Program('sample') 18 | program.compiler.includes += [pch] 19 | program.sources += [ 20 | 'main.cpp', 21 | ] 22 | builder.Add(program) 23 | -------------------------------------------------------------------------------- /tests/precompiled-headers/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | API_VERSION = '2.2' 3 | 4 | import sys 5 | try: 6 | from ambuild2 import run 7 | if not run.HasAPI(API_VERSION): 8 | raise Exception() 9 | except: 10 | sys.stderr.write('AMBuild {0} must be installed to build this project.\n'.format(API_VERSION)) 11 | sys.stderr.write('http://www.alliedmods.net/ambuild\n') 12 | sys.exit(1) 13 | 14 | builder = run.BuildParser(sourcePath = sys.path[0], api = API_VERSION) 15 | builder.Configure() 16 | -------------------------------------------------------------------------------- /tests/precompiled-headers/main.cpp: -------------------------------------------------------------------------------- 1 | #include "all-headers.h" 2 | 3 | int main() 4 | { 5 | std::vector stuff; 6 | return (int)stuff.size(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/resource_dll/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | import os 3 | cxx = builder.DetectCxx() 4 | binary = cxx.Library('project_name') 5 | compiler = binary.compiler 6 | compiler.linkflags += [ 7 | '/NOENTRY', 8 | ] 9 | compiler.includes += [ 10 | os.path.join(builder.currentSourcePath), 11 | ] 12 | 13 | binary.sources += [ 14 | 'project_resource.rc', 15 | ] 16 | 17 | builder.Add(binary) 18 | -------------------------------------------------------------------------------- /tests/resource_dll/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | API_VERSION = '2.1.1' 3 | 4 | import sys 5 | try: 6 | from ambuild2 import run 7 | if not run.HasAPI(API_VERSION): 8 | raise Exception() 9 | except: 10 | sys.stderr.write('AMBuild {0} must be installed to build this project.\n'.format(API_VERSION)) 11 | sys.stderr.write('http://www.alliedmods.net/ambuild\n') 12 | sys.exit(1) 13 | 14 | builder = run.BuildParser(sourcePath = sys.path[0], api = API_VERSION) 15 | builder.Configure() 16 | -------------------------------------------------------------------------------- /tests/resource_dll/project_resource.rc: -------------------------------------------------------------------------------- 1 | #define ID_FILE_EXIT 4001 2 | -------------------------------------------------------------------------------- /tests/shaders/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: 2 | import os 3 | builder.DetectCxx() 4 | 5 | shaders = builder.tools.FXC('shaders', 'my::stuff') 6 | shaders.listDefineName = 'SHADER_MAP' 7 | shaders.shaders += [ 8 | { 9 | 'source': 'code/image_vs.hlsl', 10 | 'variable': 'sImageVS', 11 | 'profile': 'vs_4_0', 12 | }, 13 | { 14 | 'source': 'code/image_ps.hlsl', 15 | 'variable': 'sImagePS', 16 | 'profile': 'ps_4_0', 17 | }, 18 | ] 19 | 20 | program = builder.cxx.Program('sample') 21 | program.sources += [ 22 | 'code/include-shaders.cc', 23 | ] 24 | program.custom += [shaders] 25 | builder.Add(program) 26 | -------------------------------------------------------------------------------- /tests/shaders/code/common.hlsl: -------------------------------------------------------------------------------- 1 | // (C) 2015 AlliedModders LLC 2 | // All rights reserved. 3 | // 4 | -------------------------------------------------------------------------------- /tests/shaders/code/image_common.hlsl: -------------------------------------------------------------------------------- 1 | 2 | struct IMAGE_VS_OUTPUT { 3 | float4 pos : SV_Position; 4 | float2 source_uv : TEXCOORD0; 5 | float4 image_rect : TEXCOORD1; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/shaders/code/image_ps.hlsl: -------------------------------------------------------------------------------- 1 | #include "common.hlsl" 2 | #include "image_common.hlsl" 3 | 4 | Texture2D tImage : register(ps, t0); 5 | sampler sSampler : register(ps, s0); 6 | 7 | float4 main(const IMAGE_VS_OUTPUT v) : SV_Target 8 | { 9 | float2 image_offset = v.image_rect.xy; 10 | float2 image_size = v.image_rect.zw; 11 | float2 uv = image_offset + image_size * frac(v.source_uv); 12 | return tImage.Sample(sSampler, uv); 13 | } 14 | -------------------------------------------------------------------------------- /tests/shaders/code/image_vs.hlsl: -------------------------------------------------------------------------------- 1 | #include "common.hlsl" 2 | #include "vs_common.hlsl" 3 | #include "image_common.hlsl" 4 | 5 | struct Image { 6 | float4 source_uv; 7 | float4 dest; 8 | uint tile_index; 9 | uint3 padding; 10 | }; 11 | 12 | cbuffer imageBuffer : register(b4) { 13 | Image images[1365]; 14 | }; 15 | 16 | IMAGE_VS_OUTPUT main(const VS_INPUT input) 17 | { 18 | Image image = images[input.id]; 19 | 20 | QuadVertexInfo vi = ComputeQuadVertex( 21 | image.tile_index, 22 | image.dest, 23 | input.pos); 24 | 25 | float2 uv = (vi.clipped_pos - vi.dest_rect.xy) / vi.dest_rect.zw; 26 | 27 | IMAGE_VS_OUTPUT v; 28 | v.pos = vi.out_vertex; 29 | v.source_uv = uv; //TexturizeQuadVertex(image.source_uv, input.pos); 30 | v.image_rect = image.source_uv; 31 | return v; 32 | } 33 | -------------------------------------------------------------------------------- /tests/shaders/code/include-shaders.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "shaders-include.h" 4 | 5 | int main() 6 | { 7 | #define _(name) \ 8 | printf("%p %d\n", my::stuff::name##_Bytes, int(my::stuff::name##_Length)); 9 | SHADER_MAP(_) 10 | #undef _ 11 | } 12 | -------------------------------------------------------------------------------- /tests/shaders/code/vs_common.hlsl: -------------------------------------------------------------------------------- 1 | 2 | // Group #1: Updates on Window size changes. 3 | cbuffer worldBuffer : register(b0) { 4 | float4x4 kProjection; 5 | }; 6 | 7 | struct Tile { 8 | float4 screenRect; 9 | }; 10 | 11 | // Group #2: Updated on tile buffer changes. 12 | cbuffer tileBuffer : register(b1) { 13 | Tile tiles[4096]; 14 | }; 15 | 16 | struct VS_INPUT { 17 | float2 pos : POSITION; 18 | uint id : SV_InstanceID; 19 | }; 20 | 21 | struct QuadVertexInfo { 22 | // The vertex to pass to the pixel shader. 23 | float4 out_vertex; 24 | // Destination rect, in screen space. 25 | float4 dest_rect; 26 | // Destination vertex, clipped to tile bounds. 27 | float2 clipped_pos; 28 | }; 29 | 30 | QuadVertexInfo ComputeQuadVertex(uint tile_index, float4 dest_rect, float2 vertex) 31 | { 32 | float4 tile_screen_rect = tiles[tile_index].screenRect; 33 | 34 | // Convert the vertex to screen space. 35 | #if 0 36 | float2 screen_vertex = lerp( 37 | dest_rect.xy, // Top-left vertex. 38 | dest_rect.zw, // Bottom-right vertex. 39 | vertex); 40 | #else 41 | float2 screen_vertex = float2( 42 | dest_rect.x + vertex.x * dest_rect.z, 43 | dest_rect.y + vertex.y * dest_rect.w); 44 | #endif 45 | 46 | // Clamp the vertex to inside the tile. 47 | #if 1 48 | screen_vertex = clamp( 49 | screen_vertex, 50 | tile_screen_rect.xy, 51 | tile_screen_rect.xy + tile_screen_rect.zw); 52 | #endif 53 | 54 | QuadVertexInfo info; 55 | info.out_vertex = mul(kProjection, float4(screen_vertex, 0, 1)); 56 | info.dest_rect = dest_rect; 57 | info.clipped_pos = screen_vertex; 58 | 59 | return info; 60 | } 61 | 62 | float2 TexturizeQuadVertex(float4 uv_rect, float2 pos) 63 | { 64 | return float2( 65 | uv_rect.x + pos.x * uv_rect.z, 66 | uv_rect.y + pos.y * uv_rect.w); 67 | } 68 | -------------------------------------------------------------------------------- /tests/shaders/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | API_VERSION = '2.1' 3 | 4 | import sys 5 | try: 6 | from ambuild2 import run 7 | if not run.HasAPI(API_VERSION): 8 | raise Exception() 9 | except: 10 | sys.stderr.write('AMBuild {0} must be installed to build this project.\n'.format(API_VERSION)) 11 | sys.stderr.write('http://www.alliedmods.net/ambuild\n') 12 | sys.exit(1) 13 | 14 | builder = run.BuildParser(sourcePath = sys.path[0], api = API_VERSION) 15 | builder.Configure() 16 | -------------------------------------------------------------------------------- /tests/shared_outputs/basic/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | argv = [ 5 | sys.executable, 6 | os.path.join(builder.sourcePath, 'generate_header.py'), 7 | 'output.h' 8 | ] 9 | 10 | shared_outputs = [ 11 | # Comment for reconfigure test #1. 12 | os.path.join(builder.buildFolder, 'output.h') 13 | ] 14 | 15 | # Comment all below for reconfigure test #2. 16 | work_folder = builder.AddFolder('work') 17 | 18 | builder.AddCommand( 19 | inputs = [], 20 | argv = argv, 21 | outputs = [], 22 | folder = work_folder, 23 | shared_outputs = shared_outputs 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /tests/shared_outputs/basic/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.PrepareBuild(sourcePath = sys.path[0]) 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/shared_outputs/basic/generate_header.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | 4 | with open(sys.argv[1], 'w') as fp: 5 | fp.write(""" 6 | #ifndef HELLO_STRING 7 | # define HELLO_STRING "HELLO!" 8 | #endif 9 | """) 10 | -------------------------------------------------------------------------------- /tests/shared_outputs/duplicates/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | argv = [ 5 | sys.executable, 6 | os.path.join(builder.sourcePath, 'generate_header.py'), 7 | 'output.h' 8 | ] 9 | 10 | shared_outputs = [ 11 | os.path.join(builder.buildFolder, 'output.h') 12 | ] 13 | 14 | def FailTest1(): 15 | builder.AddCommand( 16 | inputs = [], 17 | argv = argv, 18 | outputs = [], 19 | shared_outputs = shared_outputs + shared_outputs 20 | ) 21 | 22 | def FailTest2(): 23 | builder.AddCommand( 24 | inputs = [], 25 | argv = argv, 26 | outputs = shared_outputs + shared_outputs 27 | ) 28 | 29 | def FailTest3(): 30 | builder.AddCommand( 31 | inputs = [], 32 | argv = argv, 33 | outputs = shared_outputs, 34 | shared_outputs = shared_outputs 35 | ) 36 | 37 | # All of these should fail. 38 | FailTest1() 39 | #FailTest2() 40 | #FailTest3() 41 | -------------------------------------------------------------------------------- /tests/shared_outputs/duplicates/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.PrepareBuild(sourcePath = sys.path[0]) 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/shared_outputs/duplicates/generate_header.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | 4 | with open(sys.argv[1], 'w') as fp: 5 | fp.write(""" 6 | #ifndef HELLO_STRING 7 | # define HELLO_STRING "HELLO!" 8 | #endif 9 | """) 10 | -------------------------------------------------------------------------------- /tests/shared_outputs/mixup/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | argv = [ 5 | sys.executable, 6 | os.path.join(builder.sourcePath, 'generate_header.py'), 7 | 'output.h' 8 | ] 9 | 10 | shared_outputs = [ 11 | os.path.join(builder.buildFolder, 'output.h') 12 | ] 13 | 14 | def Test1(): 15 | builder.AddCommand( 16 | inputs = [], 17 | argv = argv, 18 | outputs = [], 19 | shared_outputs = shared_outputs 20 | ) 21 | 22 | def Test2(): 23 | # Change the output to a normal output. 24 | builder.AddCommand( 25 | inputs = [], 26 | argv = argv, 27 | outputs = shared_outputs 28 | ) 29 | 30 | def Test3(): 31 | # Change back to a shared output. 32 | Test1() 33 | 34 | def Test4(): 35 | # Change to a folder. 36 | builder.AddFolder('output.h') 37 | 38 | def Test5(): 39 | # Change back to a shared output. 40 | Test1() 41 | 42 | def TestFail1(): 43 | Test2() 44 | Test1() 45 | 46 | def TestFail2(): 47 | Test1() 48 | Test2() 49 | 50 | def TestFail3(): 51 | Test1() 52 | Test4() 53 | 54 | def TestFail4(): 55 | Test4() 56 | Test1() 57 | 58 | # Uncomment 1, run, recomment, uncomment 2 to test. Repeat. Each time using 59 | # refactoring mode should fail. 60 | Test1() 61 | #Test2() 62 | #Test3() 63 | #Test4() 64 | #Test5() 65 | 66 | # These tests should all fail. 67 | #TestFail1() 68 | #TestFail2() 69 | #TestFail3() 70 | #TestFail4() 71 | -------------------------------------------------------------------------------- /tests/shared_outputs/mixup/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.PrepareBuild(sourcePath = sys.path[0]) 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/shared_outputs/mixup/generate_header.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | 4 | with open(sys.argv[1], 'w') as fp: 5 | fp.write(""" 6 | #ifndef HELLO_STRING 7 | # define HELLO_STRING "HELLO!" 8 | #endif 9 | """) 10 | -------------------------------------------------------------------------------- /tests/shared_outputs/multiples/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | argv = [ 5 | sys.executable, 6 | os.path.join(builder.sourcePath, 'generate_header.py'), 7 | 'output.h' 8 | ] 9 | 10 | shared_outputs = [ 11 | os.path.join(builder.buildFolder, 'output.h') 12 | ] 13 | 14 | # Comment anything below to test reconfiguring. 15 | builder.AddCommand( 16 | inputs = [], 17 | argv = argv, 18 | outputs = [], 19 | shared_outputs = shared_outputs 20 | ) 21 | 22 | builder.AddCommand( 23 | inputs = [], 24 | argv = argv, 25 | outputs = [], 26 | shared_outputs = shared_outputs 27 | ) 28 | 29 | builder.AddCommand( 30 | inputs = [], 31 | argv = argv, 32 | outputs = [], 33 | shared_outputs = shared_outputs 34 | ) 35 | -------------------------------------------------------------------------------- /tests/shared_outputs/multiples/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.PrepareBuild(sourcePath = sys.path[0]) 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/shared_outputs/multiples/generate_header.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | 4 | with open(sys.argv[1], 'w') as fp: 5 | fp.write(""" 6 | #ifndef HELLO_STRING 7 | # define HELLO_STRING "HELLO!" 8 | #endif 9 | """) 10 | -------------------------------------------------------------------------------- /tests/staticlib/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | builder.DetectCompilers() 5 | 6 | program = builder.compiler.StaticLibrary("hello") 7 | program.sources = [ 8 | 'main.cpp' 9 | ] 10 | builder.Add(program) 11 | -------------------------------------------------------------------------------- /tests/staticlib/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.BuildParser(sourcePath = sys.path[0], api = '2.0') 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/staticlib/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" int egg() 4 | { 5 | return printf("hello!\n"); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /tests/vcfiles/AMBuildScript: -------------------------------------------------------------------------------- 1 | # vim: set ts=8 sts=2 tw=99 et ft=python: 2 | import os, sys 3 | 4 | builder.DetectCompilers() 5 | 6 | program = builder.compiler.Library("hello") 7 | program.sources = [ 8 | 'main.cpp' 9 | ] 10 | 11 | # Comment below to test successful folder removal. 12 | builder.Add(program) 13 | -------------------------------------------------------------------------------- /tests/vcfiles/configure.py: -------------------------------------------------------------------------------- 1 | # vim: set sts=2 ts=8 sw=2 tw=99 et: 2 | import sys 3 | from ambuild2 import run 4 | 5 | builder = run.PrepareBuild(sourcePath = sys.path[0]) 6 | builder.Configure() 7 | -------------------------------------------------------------------------------- /tests/vcfiles/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" int egg() 4 | { 5 | return printf("hello!\n"); 6 | } 7 | 8 | --------------------------------------------------------------------------------