├── .gitignore ├── Doc ├── nvcc.txt └── wave.txt ├── LICENSE ├── Python ├── BuildSystem.py ├── CUDAPlatform.py ├── ComputeBridgePlatform.py ├── CppLanguage.py ├── DirectXPlatform.py ├── Environment.py ├── MSVCGeneration.py ├── MSVCPlatform.py ├── OpenCLPlatform.py ├── Packages.py ├── PiB.bat ├── PiB.py ├── Process.py ├── ShaderCompiler.py ├── Utils.py ├── Wave.py ├── WindowsPlatform.py └── clReflect.py ├── README ├── Test ├── Code │ ├── Main.cpp │ ├── MainDepA.h │ ├── MainDepB.h │ ├── MainDepC.h │ ├── Other.cpp │ ├── SubDepA.h │ ├── abs │ │ └── unk.cpp │ └── rec │ │ ├── hello │ │ └── old.cpp │ │ └── new.cpp ├── pibfile └── purge.bat └── VSMacros └── PiBMacros └── PiBMacros.vsmacros /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | Test/bin/* 3 | Test/obj/* 4 | Test/TestProject.ncb 5 | Test/TestProject.sln 6 | Test/TestProject.suo 7 | Test/metadata.pib 8 | Test/Code/TestProject.vcproj 9 | Test/Code/TestProject.vcproj.* -------------------------------------------------------------------------------- /Doc/nvcc.txt: -------------------------------------------------------------------------------- 1 | 2 | Usage : nvcc [options] 3 | 4 | Options for specifying the compilation phase 5 | ============================================ 6 | More exactly, this option specifies up to which stage the input files must be 7 | compiled, according to the following compilation trajectories for different 8 | input file types: 9 | .c/.cc/.cpp/.cxx : preprocess, compile, link 10 | .o : link 11 | .i/.ii : compile, link 12 | .cu : preprocess, cuda frontend, ptxassemble, 13 | merge with host C code, compile, link 14 | .gpu : cicc compile into cubin 15 | .ptx : ptxassemble into cubin. 16 | 17 | --cuda (-cuda) 18 | Compile all .cu input files to .cu.cpp.ii output. 19 | 20 | --cubin (-cubin) 21 | Compile all .cu/.ptx/.gpu input files to device- only .cubin files. 22 | This step discards the host code for each .cu input file. 23 | 24 | --fatbin(-fatbin) 25 | Compile all .cu/.ptx/.gpu input files to ptx or device- only .cubin 26 | files (depending on the values specified for options '-arch' and/or 27 | '-code') and place the result into the fat binary file specified with 28 | option -o. 29 | This step discards the host code for each .cu input file. 30 | 31 | --ptx (-ptx) 32 | Compile all .cu/.gpu input files to device- only .ptx files. This step 33 | discards the host code for each of these input file. 34 | 35 | --gpu (-gpu) 36 | Compile all .cu input files to device-only .gpu files. This step 37 | discards the host code for each .cu input file. 38 | 39 | --preprocess (-E) 40 | Preprocess all .c/.cc/.cpp/.cxx/.cu input files. 41 | 42 | --generate-dependencies (-M) 43 | Generate for the one .c/.cc/.cpp/.cxx/.cu input file (more than one 44 | input file is not allowed in this mode) a dependency file that can be 45 | included in a make file. 46 | 47 | --compile (-c) 48 | Compile each .c/.cc/.cpp/.cxx/.cu input file into an object file. 49 | 50 | --device-c (-dc) 51 | Compile each .c/.cc/.cpp/.cxx/.cu input file into an object file that 52 | contains relocatable device code. It is equivalent to 53 | '--relocatable-device-code=true --compile'. 54 | 55 | --device-w (-dw) 56 | Compile each .c/.cc/.cpp/.cxx/.cu input file into an object file that 57 | contains executable device code. It is equivalent to 58 | '--relocatable-device-code=false --compile'. 59 | 60 | --device-link (-dlink) 61 | Link object files with relocatable device code and .ptx/.cubin/.fatbin 62 | files into an object file with executable device code, which can be 63 | passed to the host linker. 64 | 65 | --link (-link) 66 | This option specifies the default behavior: compile and link all inputs 67 | . 68 | 69 | --no-device-link (-nodlink) 70 | Skip the device link step when linking object files. 71 | 72 | --lib (-lib) 73 | Compile all inputs into object files (if necessary) and add the results 74 | to the specified output library file. 75 | 76 | --run (-run) 77 | This option compiles and links all inputs into an executable, and 78 | executes it. Or, when the input is a single executable, it is executed 79 | without any compilation or linking. This step is intended for 80 | developers who do not want to be bothered with setting the necessary 81 | cuda dll search paths (these will be set temporarily by nvcc). 82 | 83 | 84 | File and path specifications 85 | ============================ 86 | 87 | --x (-x) 88 | Explicitly specify the language for the input files, rather than 89 | letting the compiler choose a default based on the file name suffix. 90 | Allowed values for this option: 'c','c++','cu'. 91 | 92 | --output-file (-o) 93 | Specify name and location of the output file. Only a single input file 94 | is allowed when this option is present in nvcc non- linking/archiving 95 | mode. 96 | 97 | --pre-include ,... (-include) 98 | Specify header files that must be preincluded during preprocessing. 99 | 100 | --library ,... (-l) 101 | Specify libraries to be used in the linking stage without the library 102 | file extension. The libraries are searched for on the library search 103 | paths that have been specified using option '-L'. 104 | 105 | --define-macro ,... (-D) 106 | Specify macro definitions to define for use during preprocessing or 107 | compilation. 108 | 109 | --undefine-macro ,... (-U) 110 | Specify macro definitions to undefine for use during preprocessing or 111 | compilation. 112 | 113 | --include-path ,... (-I) 114 | Specify include search paths. 115 | 116 | --system-include ,... (-isystem) 117 | Specify system include search paths. 118 | 119 | --library-path ,... (-L) 120 | Specify library search paths. 121 | 122 | --output-directory (-odir) 123 | Specify the directory of the output file. This option is intended for 124 | letting the dependency generation step (option 125 | '--generate-dependencies') generate a rule that defines the target 126 | object file in the proper directory. 127 | 128 | --compiler-bindir (-ccbin) 129 | Specify the directory in which the compiler executable (Microsoft 130 | Visual Studio cl, or a gcc derivative) resides. By default, this 131 | executable is expected in the current executable search path. For a 132 | different compiler, or to specify these compilers with a different 133 | executable name, specify the path to the compiler including the 134 | executable name. 135 | 136 | --cudart(-cudart) 137 | Specify the type of CUDA runtime library to be used: static CUDA 138 | runtime library, shared/dynamic CUDA runtime library, or no CUDA 139 | runtime library. By default, the static CUDA runtime library is used. 140 | Allowed values for this option: 'none','shared','static'. 141 | Default value: 'static'. 142 | 143 | --cl-version --cl-version 144 | 145 | Specify the version of Microsoft Visual Studio installation. Note: this 146 | option is to be used in conjunction with '--use-local-env', and is 147 | ignored when '--use-local-env' is not specified. 148 | Allowed values for this option: 2008,2010,2012. 149 | 150 | --use-local-env --use-local-env 151 | Specify whether the environment is already set up for the host compiler 152 | . 153 | 154 | --libdevice-directory (-ldir) 155 | Specify the directory that contains the libdevice library files when 156 | option '--dont-use-profile' is used. Libdevice library files are 157 | located in the 'nvvm/libdevice' directory in the CUDA toolkit. 158 | 159 | 160 | Options for specifying behaviour of compiler/linker 161 | =================================================== 162 | 163 | --profile (-pg) 164 | Instrument generated code/executable for use by gprof (Linux only). 165 | 166 | --debug (-g) 167 | Generate debug information for host code. 168 | 169 | --device-debug (-G) 170 | Generate debug information for device code. 171 | 172 | --generate-line-info (-lineinfo) 173 | Generate line-number information for device code. 174 | 175 | --optimize (-O) 176 | Specify optimization level for host code. 177 | 178 | --shared(-shared) 179 | Generate a shared library during linking. Note: when other linker 180 | options are required for controlling dll generation, use option 181 | -Xlinker. 182 | 183 | --machine (-m) 184 | Specify 32 vs 64 bit architecture. 185 | Allowed values for this option: 32,64. 186 | Default value: 64. 187 | 188 | 189 | Options for passing specific phase options 190 | ========================================== 191 | These allow for passing options directly to the intended compilation phase. 192 | Using these, users have the ability to pass options to the lower level 193 | compilation tools, without the need for nvcc to know about each and every such 194 | option. 195 | 196 | --compiler-options ,... (-Xcompiler) 197 | Specify options directly to the compiler/preprocessor. 198 | 199 | --linker-options ,... (-Xlinker) 200 | Specify options directly to the host linker. 201 | 202 | --archive-options ,... (-Xarchive) 203 | Specify options directly to library manager. 204 | 205 | --ptxas-options ,... (-Xptxas) 206 | Specify options directly to the ptx optimizing assembler. 207 | 208 | --nvlink-options ,... (-Xnvlink) 209 | Specify options directly to nvlink. 210 | 211 | 212 | Miscellaneous options for guiding the compiler driver 213 | ===================================================== 214 | 215 | --dont-use-profile (-noprof) 216 | Nvcc uses the nvcc.profiles file for compilation. When specifying this 217 | option, the profile file is not used. 218 | 219 | --dryrun(-dryrun) 220 | Do not execute the compilation commands generated by nvcc. Instead, 221 | list them. 222 | 223 | --verbose (-v) 224 | List the compilation commands generated by this compiler driver, but do 225 | not suppress their execution. 226 | 227 | --keep (-keep) 228 | Keep all intermediate files that are generated during internal 229 | compilation steps. 230 | 231 | --keep-dir (-keep-dir) 232 | Keep all intermediate files that are generated during internal 233 | compilation steps in this directory. 234 | 235 | --save-temps (-save-temps) 236 | This option is an alias of '--keep'. 237 | 238 | --clean-targets (-clean) 239 | This option reverses the behaviour of nvcc. When specified, none of the 240 | compilation phases will be executed. Instead, all of the non- temporary 241 | files that nvcc would otherwise create will be deleted. 242 | 243 | --run-args ,... (-run-args) 244 | Used in combination with option -R, to specify command line arguments 245 | for the executable. 246 | 247 | --input-drive-prefix (-idp) 248 | On Windows platforms, all command line arguments that refer to file 249 | names must be converted to Windows native format before they are passed 250 | to pure Windows executables. This option specifies how the 'current' 251 | development environment represents absolute paths. Use '-idp /cygwin/' 252 | for CygWin build environments, and '-idp /' for Mingw. 253 | 254 | --dependency-drive-prefix (-ddp) 255 | On Windows platforms, when generating dependency files (option -M), all 256 | file names must be converted to whatever the used instance of 'make' 257 | will recognize. Some instances of 'make' have trouble with the colon in 258 | absolute paths in native Windows format, which depends on the 259 | environment in which this 'make' instance has been compiled. Use '-ddp 260 | /cygwin/' for a CygWin make, and '-ddp /' for Mingw. Or leave these 261 | file names in native Windows format by specifying nothing. 262 | 263 | --dependency-target-name (-MT) 264 | Specify the target name of the generated rule when generating a 265 | dependency file (option -M). 266 | 267 | --drive-prefix (-dp) 268 | Specifies as both input-drive-prefix and 269 | dependency-drive-prefix. 270 | 271 | --no-align-double --no-align-double 272 | Specifies that -malign-double should not be passed as a compiler 273 | argument on 32-bit platforms. WARNING: this makes the ABI incompatible 274 | with the cuda's kernel ABI for certain 64-bit types. 275 | 276 | 277 | Options for steering GPU code generation 278 | ======================================== 279 | 280 | --gpu-architecture (-arch) 281 | Specify the name of the class of nVidia GPU architectures for which the 282 | cuda input files must be compiled. 283 | With the exception as described for the shorthand below, the 284 | architecture specified with this option must be a virtual architecture 285 | (such as compute_10), and it will be the assumed architecture during 286 | the cicc compilation stage. 287 | This option will cause no code to be generated (that is the role of 288 | nvcc option '--gpu-code', see below); rather, its purpose is to steer 289 | the cicc stage, influencing the architecture of the generated ptx 290 | intermediate. 291 | For convenience in case of simple nvcc compilations the following 292 | shorthand is supported: if no value for option '--gpu-code' is 293 | specified, then the value of this option defaults to the value of 294 | '--gpu-architecture'. In this situation, as only exception to the 295 | description above, the value specified for '--gpu-architecture' may be 296 | a 'real' architecture (such as a sm_13), in which case nvcc uses the 297 | specified real architecture and its closest virtual architecture as 298 | effective architecture values. For example, 'nvcc -arch=sm_13' is 299 | equivalent to 'nvcc -arch=compute_13 -code=sm_13,compute_13'. 300 | Allowed values for this option: 'compute_10','compute_11','compute_12', 301 | 'compute_13','compute_20','compute_30','compute_32','compute_35', 302 | 'compute_50','sm_10','sm_11','sm_12','sm_13','sm_20','sm_21','sm_30', 303 | 'sm_32','sm_35','sm_50'. 304 | 305 | --gpu-code ,... (-code) 306 | Specify the names of nVidia gpus to generate code for. 307 | nvcc will embed a compiled code image in the resulting executable for 308 | each specified 'code' architecture. This code image will be a true 309 | binary load image for each 'real' architecture (such as a sm_13), and 310 | ptx intermediate code for each virtual architecture (such as 311 | compute_10). During runtime, in case no better binary load image is 312 | found, and provided that the ptx architecture is compatible with the 313 | 'current' GPU, such embedded ptx code will be dynamically translated 314 | for this current GPU by the cuda runtime system. 315 | Architectures specified for this option can be virtual as well as real, 316 | but each of these 'code' architectures must be compatible with the 317 | architecture specified with option '--gpu-architecture'. 318 | For instance, 'arch'=compute_13 is not compatible with 'code'=sm_10, 319 | because the generated ptx code will assume the availability of 320 | compute_13 features that are not present on sm_10. 321 | Allowed values for this option: 'compute_10','compute_11','compute_12', 322 | 'compute_13','compute_20','compute_30','compute_32','compute_35', 323 | 'compute_50','sm_10','sm_11','sm_12','sm_13','sm_20','sm_21','sm_30', 324 | 'sm_32','sm_35','sm_50'. 325 | 326 | --generate-code (-gencode) 327 | This option provides a generalization of the '--gpu-architecture= 328 | --gpu-code=code,...' option combination for specifying nvcc behavior 329 | with respect to code generation. Where use of the previous options 330 | generates different code for a fixed virtual architecture, option 331 | '--generate-code' allows multiple cicc invocations, iterating over 332 | different virtual architectures. In fact, 333 | '--gpu-architecture= --gpu-code=,...' 334 | is equivalent to 335 | '--generate-code arch=,code=,...'. 336 | '--generate-code' options may be repeated for different virtual 337 | architectures. 338 | Allowed keywords for this option: 'arch','code'. 339 | 340 | --maxrregcount (-maxrregcount) 341 | Specify the maximum amount of registers that GPU functions can use. 342 | Until a function- specific limit, a higher value will generally 343 | increase the performance of individual GPU threads that execute this 344 | function. However, because thread registers are allocated from a global 345 | register pool on each GPU, a higher value of this option will also 346 | reduce the maximum thread block size, thereby reducing the amount of 347 | thread parallelism. Hence, a good maxrregcount value is the result of a 348 | trade-off. 349 | If this option is not specified, then no maximum is assumed. 350 | Value less than the minimum registers required by ABI will be bumped up 351 | by the compiler to ABI minimum limit. 352 | 353 | --ftz [true,false] (-ftz) 354 | When performing single-precision floating-point operations, flush 355 | denormal values to zero or preserve denormal values. -use_fast_math 356 | implies --ftz=true. 357 | Default value: 0. 358 | 359 | --prec-div [true,false] (-prec-div) 360 | For single-precision floating-point division and reciprocals, use IEEE 361 | round-to-nearest mode or use a faster approximation. -use_fast_math 362 | implies --prec-div=false. 363 | Default value: 1. 364 | 365 | --prec-sqrt [true,false] (-prec-sqrt) 366 | For single-precision floating-point square root, use IEEE 367 | round-to-nearest mode or use a faster approximation. -use_fast_math 368 | implies --prec-sqrt=false. 369 | Default value: 1. 370 | 371 | --fmad [true,false] (-fmad) 372 | Enables (disables) the contraction of floating-point multiplies and 373 | adds/subtracts into floating-point multiply-add operations (FMAD, FFMA, 374 | or DFMA). This option is supported only when '--gpu-architecture' is 375 | set with compute_20, sm_20, or higher. For other architecture classes, 376 | the contraction is always enabled. -use_fast_math implies --fmad=true. 377 | Default value: 1. 378 | 379 | --relocatable-device-code [true,false] (-rdc) 380 | Enable (disable) the generation of relocatable device code. If 381 | disabled, executable device code is generated. 382 | Default value: 0. 383 | 384 | 385 | Options for steering cuda compilation 386 | ===================================== 387 | 388 | --target-cpu-architecture (-target-cpu-arch) 389 | Specify the name of the class of CPU architecture for which the input 390 | files must be compiled. 391 | Allowed values for this option: 'x86'. 392 | Default value: 'x86'. 393 | 394 | --use_fast_math (-use_fast_math) 395 | Make use of fast math library. -use_fast_math implies -ftz=true 396 | -prec-div=false -prec-sqrt=false. 397 | 398 | --entries entry,... (-e) 399 | In case of compilation of ptx or gpu files to cubin: specify the global 400 | entry functions for which code must be generated. By default, code will 401 | be generated for all entry functions. 402 | 403 | 404 | Generic tool options 405 | ==================== 406 | 407 | --disable-warnings (-w) 408 | Inhibit all warning messages. 409 | 410 | --source-in-ptx (-src-in-ptx) 411 | Interleave source in ptx. 412 | 413 | --restrict (-restrict) 414 | Programmer assertion that all kernel pointer parameters are restrict 415 | pointers. 416 | 417 | --Wno-deprecated-gpu-targets 418 | (-Wno-deprecated-gpu-targets) 419 | Suppress warnings about deprecated GPU target architectures. 420 | 421 | --Werror,... (-Werror) 422 | Make warnings of the specified kinds into errors. The following is the 423 | list of warning kinds accepted by this option: 424 | 425 | cross-execution-space-call 426 | Be more strict about unsupported cross execution space calls. 427 | The compiler will generate an error instead of a warning for a 428 | call from a __host__ __device__ to a __host__ function. 429 | 430 | Allowed values for this option: 'cross-execution-space-call'. 431 | 432 | --help (-h) 433 | Print this help information on this tool. 434 | 435 | --version (-V) 436 | Print version information on this tool. 437 | 438 | --options-file ,... (-optf) 439 | Include command line options from specified file. 440 | 441 | 442 | -------------------------------------------------------------------------------- /Doc/wave.txt: -------------------------------------------------------------------------------- 1 | Usage: wave [options] [@config-file(s)] file: 2 | 3 | Options allowed on the command line only: 4 | -h [--help]: print out program usage (this message) 5 | -v [--version]: print the version number 6 | -c [--copyright]: print out the copyright statement 7 | --config-file filepath: specify a config file (alternatively: @filepath) 8 | 9 | Options allowed additionally in a config file: 10 | -o [--output] path: specify a file [path] to use for output instead of 11 | stdout or disable output [-] 12 | -E [ --autooutput ]: output goes into a file named .i 13 | -I [--include] path: specify an additional include directory 14 | -S [--sysinclude] syspath: specify an additional system include directory 15 | -F [--forceinclude] file: force inclusion of the given file 16 | -D [--define] macro[=[value]]: specify a macro to define 17 | -P [--predefine] macro[=[value]]: specify a macro to predefine 18 | -U [--undefine] macro: specify a macro to undefine 19 | -u [--undefineall]: undefine all macrodefinitions 20 | -n [--nesting] depth: specify a new maximal include nesting depth 21 | 22 | Extended options (allowed everywhere) 23 | -t [--traceto] arg: output trace info to a file [arg] or to stderr [-] 24 | --timer: output overall elapsed computing time 25 | --long_long: enable long long support if C++ mode 26 | --variadics: enable variadics and placemarkers in C++ mode 27 | --c99: enable C99 mode (implies variadics and placemarkers) 28 | --c++11 enable C++11 mode (implies --variadics and --long_long) 29 | -l [ --listincludes ] arg: list included file to a file [arg] or to stdout [-] 30 | -m [ --macronames ] arg: list names of all defined macros to a file [arg] or 31 | to stdout [-] 32 | -c [ --macrocounts ] arg list macro invocation counts to a file [arg] or to 33 | stdout [-] 34 | -p [ --preserve ] arg (=0): preserve whitespace 35 | 0: no whitespace is preserved (default), 36 | 1: begin of line whitespace is preserved, 37 | 2: comments and begin of line whitespace is preserved, 38 | 3: all whitespace is preserved 39 | -L [ --line ] arg (=1): control the generation of #line directives 40 | 0: no #line directives are generated 41 | 1: #line directives will be emitted (default) 42 | -x [ --extended ]: enable the #pragma wave system() directive 43 | -G [ --noguard ]: disable include guard detection 44 | -g [ --listguards ]: list names of files flagged as 'include once' to a 45 | file [arg] or to stdout [-] 46 | -s [ --state ] arg: load and save state information from/to the given 47 | file [arg] or 'wave.state' [-] (interactive mode 48 | only) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (C) 2011 Don Williamson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Python/BuildSystem.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # --- MIT Open Source License -------------------------------------------------- 4 | # PiB - Python Build System 5 | # Copyright (C) 2011 by Don Williamson 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # ------------------------------------------------------------------------------ 25 | # 26 | # BuildSystem.py: Some basic build nodes and file metadata. 27 | # 28 | 29 | import os 30 | import sys 31 | import gzip 32 | import pickle 33 | import binascii 34 | import Utils 35 | 36 | 37 | # 38 | # File metadata that persists between builds to aid dependency evaluation and 39 | # track any changes. 40 | # 41 | class FileMetadata: 42 | 43 | def __init__(self): 44 | 45 | self.ModTime = 0 46 | self.ImplicitDeps = [ ] 47 | self.ImplicitOutputs = [ ] 48 | self.CachedModTime = None 49 | 50 | # Custom state implementations for the pickle module to ignore transient data 51 | def __getstate__(self): 52 | state = self.__dict__.copy() 53 | del state["CachedModTime"] 54 | return state 55 | def __setstate__(self, state): 56 | self.__dict__.update(state) 57 | self.CachedModTime = None 58 | 59 | def HasFileChanged(self, filename): 60 | 61 | # As calls into the OS for file times are expensive cache the result as much as possible 62 | if self.CachedModTime == None: 63 | 64 | # Modtime get will only succeed if the file exists 65 | try: 66 | self.CachedModTime = os.path.getmtime(filename) 67 | except: 68 | # If the file no longer exists, it has changed 69 | # Note this path will force the cached mod time to NOT update and call getmtime 70 | # on each evaluation. This is safe and no slower than the original implementation 71 | # and only triggered if you delete output files. 72 | return True 73 | 74 | # Compare modification times 75 | return self.CachedModTime != self.ModTime 76 | 77 | def UpdateModTime(self, filename): 78 | 79 | if self.CachedModTime != None: 80 | self.ModTime = self.CachedModTime 81 | else: 82 | # Only updates if the file exists 83 | # Faster than first checking to see if the file exists 84 | try: 85 | self.ModTime = os.path.getmtime(filename) 86 | except: 87 | pass 88 | 89 | def SetImplicitDeps(self, env, deps): 90 | 91 | # Create file nodes for each dependency 92 | self.ImplicitDeps = [ ] 93 | for filename in set(deps): 94 | filenode = env.NewFile(filename) 95 | 96 | # Ensure each dependency has a metadata entry 97 | env.GetFileMetadata(filename) 98 | self.ImplicitDeps.append(filenode) 99 | 100 | def SetImplicitOutputs(self, env, outputs): 101 | 102 | # Create file nodes for each dependency 103 | self.ImplicitOutputs = [ ] 104 | for filename in set(outputs): 105 | filenode = env.NewFile(filename) 106 | 107 | # Ensure each dependency has a metadata entry 108 | env.GetFileMetadata(filename) 109 | self.ImplicitOutputs.append(filenode) 110 | 111 | def __repr__(self): 112 | 113 | return str(self.ModTime) + "->" + str(self.ImplicitDeps) + "->" + str(self.ImplicitOutputs) 114 | 115 | 116 | # 117 | # Metadata that persists between builds 118 | # 119 | class BuildMetadata: 120 | 121 | OutputFilename = "metadata.pib" 122 | 123 | def __init__(self): 124 | 125 | self.Version = 2 126 | self.FileMap = { } 127 | self.FileMetadata = { } 128 | self.UserData = None 129 | 130 | def Save(self): 131 | 132 | with gzip.open(BuildMetadata.OutputFilename, "wb") as f: 133 | pickle.dump(self, f) 134 | 135 | def Load(): 136 | 137 | try: 138 | # Open and load the metadata file if it exists 139 | if os.path.exists(BuildMetadata.OutputFilename): 140 | with gzip.open(BuildMetadata.OutputFilename, "rb") as f: 141 | data = pickle.load(f) 142 | 143 | # Return if the version matches 144 | if hasattr(data, "Version") and data.Version == 2: 145 | return data 146 | 147 | print("Metadata file version out of date, discarding...") 148 | 149 | # Handle malformed files 150 | except: 151 | print("Error loading Metadata file, discarding...") 152 | 153 | # Return empty constructed build metadata if it can't be loaded 154 | return BuildMetadata() 155 | 156 | def AddToFileMap(self, filename): 157 | 158 | # Ignore empty filenames 159 | if filename == None: 160 | return 161 | 162 | # Generate the CRC 163 | filename = Utils.NormalisePath(filename) 164 | crc = binascii.crc32(bytes(filename, "utf-8")) 165 | 166 | # Check for collision 167 | if crc in self.FileMap and filename != self.FileMap[crc]: 168 | raise Exception("CRC collision with " + filename + " and " + self.FileMap[crc]) 169 | 170 | self.FileMap[crc] = filename 171 | return crc 172 | 173 | def GetFilename(self, crc): 174 | 175 | return self.FileMap[crc] 176 | 177 | def GetFileMetadata(self, target, filename): 178 | 179 | # Ignore empty filenames 180 | if filename == None: 181 | return None 182 | 183 | # Create unique file metadata objects for each target so that builds 184 | # don't interfere with each other 185 | if target not in self.FileMetadata: 186 | self.FileMetadata[target] = { } 187 | file_metadata = self.FileMetadata[target] 188 | 189 | # Return an existing metadata? 190 | crc = self.AddToFileMap(filename) 191 | if crc in file_metadata: 192 | return file_metadata[crc] 193 | 194 | # Otherwise create a new one 195 | data = FileMetadata() 196 | file_metadata[crc] = data 197 | return data 198 | 199 | def UpdateModTimes(self, target): 200 | 201 | # It's safe to update the mod times for any files which were different since the last build 202 | file_metadata = self.FileMetadata[target] 203 | for crc, metadata in file_metadata.items(): 204 | metadata.UpdateModTime(self.GetFilename(crc)) 205 | 206 | 207 | # 208 | # Base node for the dependency graph 209 | # 210 | class Node: 211 | 212 | def __init__(self): 213 | self.Dependencies = [ ] 214 | 215 | def GetInputFile(self, env): 216 | raise Exception("Derived class hasn't implemented GetInputFile") 217 | 218 | def GetOutputFiles(self, env): 219 | raise Exception("Derived class hasn't implemented GetOutputFiles") 220 | 221 | def GetTempOutputFiles(self, env): 222 | return self.GetOutputFiles(env) 223 | 224 | 225 | # 226 | # A file node is simply an ecapsulation around a file on disk with no build step 227 | # 228 | class FileNode (Node): 229 | 230 | def __init__(self, crc): 231 | 232 | super().__init__() 233 | self.CRC = crc 234 | 235 | def GetInputFile(self, env): 236 | return env.GetFilename(self.CRC) 237 | 238 | def GetOutputFiles(self, env): 239 | return [ env.GetFilename(self.CRC) ] 240 | 241 | 242 | # 243 | # Used to depend on output files from build steps 244 | # Bound to the environment that generates the output file 245 | # 246 | class OutputFileNode (Node): 247 | 248 | def __init__(self, env, node): 249 | 250 | super().__init__() 251 | self.Env = env 252 | self.GetInputFileFunc = node.GetInputFile 253 | self.GetOutputFilesFunc = node.GetOutputFiles 254 | 255 | def GetInputFile(self, env): 256 | 257 | return self.GetInputFileFunc(self.Env) 258 | 259 | def GetOutputFiles(self, env): 260 | 261 | return self.GetOutputFilesFunc(self.Env) 262 | 263 | 264 | # 265 | # A file copying node that can be placed anywhere in the dependency chain, always 266 | # returning True on Build 267 | # 268 | class CopyNode (Node): 269 | 270 | def __init__(self, output, source, dest): 271 | 272 | super().__init__() 273 | self.Dependencies = [ output ] 274 | self.Source = source 275 | self.Destination = dest 276 | 277 | def Build(self, env): 278 | 279 | if not os.path.exists(self.Source): 280 | Utils.Print(env, " ERROR: Source file doesn't exist") 281 | return False 282 | 283 | Utils.Print(env, "Copying from " + self.Source + " to " + self.Destination) 284 | if Utils.Makedirs(os.path.dirname(self.Destination)) == False: 285 | Utils.Print(env, " ERROR: destination directories couldn't be created") 286 | return False 287 | if Utils.CopyFile(self.Source, self.Destination) == False: 288 | Utils.Print(env, " ERROR: Copy operation failed") 289 | return False 290 | 291 | return True 292 | 293 | def GetInputFile(self, env): 294 | return self.Source 295 | 296 | def GetOutputFiles(self, env): 297 | return [ self.Destination ] 298 | 299 | -------------------------------------------------------------------------------- /Python/CUDAPlatform.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import Utils 4 | import Process 5 | import BuildSystem 6 | 7 | 8 | # Retrieve the installation directories from the environment 9 | InstallDir = None 10 | if "CUDA_PATH" in os.environ: 11 | InstallDir = os.environ["CUDA_PATH"] 12 | SampleDir = None 13 | if "NVCUDASAMPLES_ROOT" in os.environ: 14 | SampleDir = os.environ["NVCUDASAMPLES_ROOT"] 15 | 16 | 17 | # Setup paths relative to the installation path 18 | IncludeDir = os.path.join(InstallDir, "include") if InstallDir else None 19 | x86LibDir = os.path.join(InstallDir, "lib/Win32") if InstallDir else None 20 | x64LibDir = os.path.join(InstallDir, "lib/x64") if InstallDir else None 21 | BinDir = os.path.join(InstallDir, "bin") if InstallDir else None 22 | 23 | 24 | # Setup paths relative to the samples path 25 | SampleCommonIncludeDir = os.path.join(SampleDir, "common/inc") if SampleDir else None 26 | 27 | 28 | # 29 | # Names of nVidia GPU Virtual Architectures for generating up to the PTX stage 30 | # 31 | VirtualArch = Utils.enum( 32 | compute_10 = 'compute_10', 33 | compute_11 = 'compute_11', 34 | compute_12 = 'compute_12', 35 | compute_13 = 'compute_13', 36 | compute_20 = 'compute_20', 37 | compute_30 = 'compute_30', 38 | compute_32 = 'compute_32', 39 | compute_35 = 'compute_35', 40 | compute_50 = 'compute_50', 41 | ) 42 | 43 | # 44 | # Names of nVidia GPU Real Archtectures for generating final binary images 45 | # 46 | RealArch = Utils.enum( 47 | sm_10 = 'sm_10', 48 | sm_11 = 'sm_11', 49 | sm_12 = 'sm_12', 50 | sm_13 = 'sm_13', 51 | sm_20 = 'sm_20', 52 | sm_21 = 'sm_21', 53 | sm_30 = 'sm_30', 54 | sm_32 = 'sm_32', 55 | sm_35 = 'sm_35', 56 | sm_50 = 'sm_50', 57 | ) 58 | 59 | 60 | class CUDACompileOptions: 61 | 62 | def __init__(self): 63 | 64 | # Set to 'c', 'c++' or 'cu' to explicitly set input language, rather than using extension 65 | self.Language = None 66 | 67 | # List of normal/system include search paths 68 | self.IncludePaths = [ ] 69 | self.SystemIncludePaths = [ ] 70 | 71 | # List of files to include first during preprocessing 72 | self.IncludeFiles = [ ] 73 | 74 | # List of macros to define/undefine for preprocessor 75 | self.DefineMacros = [ ] 76 | self.UndefineMacros = [ ] 77 | 78 | # List of library search paths 79 | self.LibraryPaths = [ ] 80 | 81 | # List of libraries to link with (specified without the library extension) 82 | self.Libraries = [ ] 83 | 84 | # Specify 32/64 bit machine target 85 | self.MachineBits = 32 86 | 87 | # Specific the path in which the compiler host EXE resides (e.g. MSVC, GCC) 88 | self.HostCompilerPath = None 89 | 90 | # Set to 'none', 'shared' or 'static' to specify runtime library type - default is 'static' 91 | self.CUDARuntime = None 92 | 93 | # Generate debug information for host/device code 94 | self.HostDebugLevel = None 95 | self.DeviceDebug = False 96 | 97 | # GPU architecture and GPUs to generate code for 98 | self.GPUArch = VirtualArch.compute_10; 99 | self.GPUCode = RealArch.sm_10; 100 | 101 | # Math operation behaviour 102 | self.FlushSingleDenormalsToZero = False 103 | self.PreciseSingleDivRecip = True 104 | self.PreciseSingleSqrt = True 105 | self.FuseMultipleAdds = True 106 | self.UseFastMath = False 107 | 108 | # Tool options 109 | self.DisableWarnings = False 110 | self.SourceInPTX = False 111 | self.RestrictPointers = False 112 | 113 | def UpdateCommandLine(self): 114 | 115 | cmdline = [ ] 116 | 117 | if self.Language: cmdline += [ '--x=' + self.Language ] 118 | 119 | cmdline += [ '--include-path=' + path for path in self.IncludePaths ] 120 | cmdline += [ '--system-include=' + path for path in self.SystemIncludePaths ] 121 | cmdline += [ '--pre-include=' + file for file in self.IncludeFiles ] 122 | cmdline += [ '--define-macro=' + macro for macro in self.DefineMacros ] 123 | cmdline += [ '--undefine-macro=' + macro for macro in self.UndefineMacros ] 124 | 125 | cmdline += [ '--library-path=' + lib for lib in self.LibraryPaths ] 126 | cmdline += [ '--library' + lib for lib in self.Libraries ] 127 | 128 | cmdline += [ '--machine=' + str(self.MachineBits) ] 129 | 130 | if self.HostCompilerPath: cmdline += [ '--compiler-bindir=' + self.HostCompilerPath ] 131 | if self.CUDARuntime: cmdline += [ '--cudart=' + self.CUDARuntime ] 132 | 133 | if self.HostDebugLevel != None: cmdline += [ '--debug=' + str(self.HostDebugLevel) ] 134 | if self.DeviceDebug: cmdline += [ '--device-debug' ] 135 | 136 | cmdline += [ '--gpu-architecture=' + self.GPUArch ] 137 | cmdline += [ '--gpu-code=' + self.GPUCode ] 138 | 139 | cmdline += [ '--ftz=' + ('true' if self.FlushSingleDenormalsToZero else 'false') ] 140 | cmdline += [ '--prec-div=' + ('true' if self.PreciseSingleDivRecip else 'false') ] 141 | cmdline += [ '--prec-sqrt=' + ('true' if self.PreciseSingleSqrt else 'false') ] 142 | cmdline += [ '--fmad=' + ('true' if self.FuseMultipleAdds else 'false') ] 143 | if self.UseFastMath: cmdline += [ '--use_fast_math' ] 144 | 145 | if self.DisableWarnings: cmdline += [ '--disable-warnings' ] 146 | if self.SourceInPTX: cmdline += [ '--source-in-ptx' ] 147 | if self.RestrictPointers: cmdline += [ '--restrict' ] 148 | 149 | self.CommandLine = cmdline 150 | 151 | 152 | class BuildPTXNode (BuildSystem.Node): 153 | 154 | def __init__(self, source): 155 | 156 | super().__init__() 157 | self.Source = source 158 | self.Dependencies = [ source ] 159 | 160 | def Build(self, env): 161 | 162 | # Build command-line from current configuration 163 | cmdline = [ os.path.join(BinDir, "nvcc.exe") ] 164 | cmdline += [ '--ptx' ] 165 | cmdline += env.CurrentConfig.CUDACompileOptions.CommandLine 166 | 167 | # Add the output .ptx file 168 | output_files = self.GetOutputFiles(env) 169 | cmdline += [ '--output-file=' + output_files[0] ] 170 | 171 | # Add input file before finishing 172 | cmdline += [ self.GetInputFile(env) ] 173 | Utils.ShowCmdLine(env, cmdline) 174 | 175 | # Launch the compiler and wait for it to finish 176 | process = Process.OpenPiped(cmdline) 177 | output = Process.WaitForPipeOutput(process) 178 | if not env.NoToolOutput: 179 | print(output) 180 | 181 | return process.returncode == 0 182 | 183 | def GetInputFile(self, env): 184 | 185 | return self.Source.GetOutputFiles(env)[0] 186 | 187 | def GetOutputFiles(self, env): 188 | 189 | # Get the filename minus path and extension 190 | # TODO: This only works if this node has another node as input that resides in 191 | # the same directory as it. Need to evaluate relative path inputs in long chains. 192 | input_file = self.GetInputFile(env) 193 | input_file = os.path.split(input_file)[1] 194 | input_file = os.path.splitext(input_file)[0] 195 | 196 | ptx_path = os.path.join(env.CurrentConfig.OutputPath, input_file + ".ptx") 197 | return [ ptx_path ] 198 | 199 | def GetTempOutputFiles(self, env): 200 | 201 | return self.GetOutputFiles(env) 202 | -------------------------------------------------------------------------------- /Python/ComputeBridgePlatform.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Uses ComputeBridge (https://github.com/Celtoys/ComputeBridge) to unify compute code for OpenCL/CUDA 4 | # 5 | 6 | import os 7 | import Utils 8 | import Process 9 | import BuildSystem 10 | 11 | 12 | # Require user to set the installation directory 13 | _InstallPath = None 14 | def SetInstallPath(path): 15 | global _InstallPath 16 | _InstallPath = path 17 | 18 | 19 | class Options: 20 | 21 | # 22 | # TODO: Uses new dirty options checking mechanism. 23 | # 24 | def __init__(self): 25 | 26 | # List of include search paths and macros 27 | self.IncludePaths = [ ] 28 | self.DefineMacros = [ ] 29 | 30 | self.Dirty = True 31 | self.CommandLine = [ ] 32 | 33 | def __setattr__(self, name, value): 34 | 35 | # Assign the field and mark the command-line as dirty 36 | self.__dict__[name] = value 37 | self.__dict__["Dirty"] = True 38 | 39 | def UpdateCommandLine(self): 40 | 41 | if self.Dirty: 42 | 43 | cmdline = [ ] 44 | 45 | for path in self.IncludePaths: 46 | cmdline += [ '-i', os.path.normpath(path) ] 47 | 48 | self.FormatDefines(cmdline) 49 | 50 | # Update and mark as not dirty without calling into __setattr__ 51 | self.__dict__["CommandLine"] = cmdline 52 | self.__dict__["Dirty"] = False 53 | 54 | def FormatDefines(self, cmdline): 55 | 56 | for define in self.DefineMacros: 57 | if isinstance(define, str): 58 | cmdline += [ '-d ' + define ] 59 | else: 60 | cmdline += [ '-d ' + str(define[0]) + "=" + str(define[1]) ] 61 | 62 | 63 | 64 | class BuildNode (BuildSystem.Node): 65 | 66 | # 67 | # TODO: Uses new options location system of passing a map from config name to options 68 | # that are referenced in Build. Means nothing specific to this build node need to 69 | # be stored in the config object. 70 | # 71 | def __init__(self, source, target, options_map): 72 | 73 | super().__init__() 74 | self.Source = source 75 | self.Dependencies = [ source ] 76 | self.Target = target 77 | self.OptionsMap = options_map 78 | 79 | def Build(self, env): 80 | 81 | # Ensure command -line for current configuration is up-to-date 82 | options = self.OptionsMap[env.CurrentConfig.CmdLineArg] 83 | options.UpdateCommandLine() 84 | 85 | output_files = self.GetOutputFiles(env) 86 | 87 | # Build command-line from current configuration 88 | cmdline = [ os.path.join(_InstallPath, "cbpp.exe") ] 89 | cmdline += [ self.GetInputFile(env) ] 90 | cmdline += options.CommandLine 91 | cmdline += [ "-noheader" ] 92 | cmdline += [ "-output", output_files[0] ] 93 | cmdline += [ "-show_includes" ] 94 | if len(output_files) > 1: 95 | cmdline += [ "-output_bin", output_files[1] ] 96 | cmdline += [ "-target", self.Target ] 97 | Utils.ShowCmdLine(env, cmdline) 98 | 99 | # Launch cbpp with a dependency scanner and wait for it to finish 100 | scanner = Utils.LineScanner(env) 101 | scanner.AddLineParser("Includes", 'cpp: included "', None, lambda line, length: line[length:-1]) 102 | process = Process.OpenPiped(cmdline, env.EnvironmentVariables) 103 | Process.WaitForPipeOutput(process, scanner) 104 | 105 | # Record the implicit dependencies for this file 106 | data = env.GetFileMetadata(self.GetInputFile(env)) 107 | data.SetImplicitDeps(env, scanner.Includes) 108 | 109 | return process.returncode == 0 110 | 111 | def GetInputFile(self, env): 112 | 113 | return self.Source.GetOutputFiles(env)[0] 114 | 115 | def GetOutputFiles(self, env): 116 | 117 | # Get the filename minus path and extension 118 | # TODO: This only works if this node has another node as input that resides in 119 | # the same directory as it. Need to evaluate relative path inputs in long chains. 120 | input_file = self.GetInputFile(env) 121 | input_file = os.path.split(input_file)[1] 122 | input_file = os.path.splitext(input_file)[0] 123 | 124 | # Put pre-processed location in intermidate directory 125 | pp_path = os.path.join(env.CurrentConfig.IntermediatePath, input_file + "." + self.Target + "_cb") 126 | paths = [ pp_path ] 127 | 128 | # CUDA binary path is the output directory with extension change 129 | if self.Target == "cuda": 130 | bin_path = os.path.join(env.CurrentConfig.OutputPath, input_file + ".ckt") 131 | paths += [ bin_path ] 132 | 133 | return paths 134 | 135 | def GetTempOutputFiles(self, env): 136 | 137 | return self.GetOutputFiles(env) 138 | -------------------------------------------------------------------------------- /Python/CppLanguage.py: -------------------------------------------------------------------------------- 1 | 2 | import Utils 3 | 4 | 5 | # 6 | # Use this to mark library dependencies as "weak". This means that they will be used as input to a link 7 | # node but won't be used to see if that node needs to be rebuilt if the library changes. 8 | # 9 | CppLinkWeakDep = True 10 | 11 | 12 | class CppBuild: 13 | 14 | def __init__(self, env, dirs, target, ext_libs = [], build = True): 15 | 16 | # Gather source/header files 17 | self.cpp_files = [] 18 | self.hpp_files = [] 19 | for dir in dirs: 20 | if dir.endswith(".cpp") or dir.endswith(".c"): 21 | self.cpp_files += [ dir ] 22 | elif dir.endswith(".h"): 23 | self.hpp_files += [ dir ] 24 | else: 25 | self.cpp_files += Utils.Glob(dir, "*.cpp") 26 | self.cpp_files += Utils.Glob(dir, "*.c") 27 | self.hpp_files += Utils.Glob(dir, "*.h") 28 | 29 | # Create nodes for compiling the C+ files 30 | self.obj_files = [ env.CPPFile(file) for file in self.cpp_files ] 31 | 32 | # Create file nodes for the input libraries 33 | # Split into two lists: strong/weak dependencies (see CppLinkWeakDep) 34 | self.lib_files = [ env.NewFile(file) for file in ext_libs if type(file) != tuple ] 35 | self.weak_lib_files = [ env.NewFile(file[0]) for file in ext_libs if type(file) == tuple ] 36 | 37 | # Link or use librarian dependent on output path 38 | self.output = None 39 | if target.endswith(".exe"): 40 | self.output = self.exe = env.Link(target, self.obj_files, self.lib_files, self.weak_lib_files) 41 | elif target.endswith(".dll"): 42 | self.output = self.dll = env.Link(target, self.obj_files, self.lib_files, self.weak_lib_files) 43 | elif target.endswith(".lib"): 44 | self.output = self.lib = env.Lib(target, self.obj_files, self.lib_files) 45 | 46 | # Build all the config command lines 47 | for config in env.Configs.values(): 48 | config.UpdateCommandLines() 49 | 50 | if build: 51 | env.Build(self.output, target[:-4]) 52 | 53 | 54 | def OverrideCPPOptions(self, cpp_file_match, override_cpp_opts): 55 | 56 | # Find all C++ files that match the input string and apply the override options 57 | cpp_file_match = cpp_file_match.lower() 58 | for obj_file in self.obj_files: 59 | if cpp_file_match in obj_file.Path.lower(): 60 | obj_file.SetCPPOptions(override_cpp_opts) 61 | -------------------------------------------------------------------------------- /Python/DirectXPlatform.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import Utils 4 | import Process 5 | import BuildSystem 6 | import WindowsPlatform 7 | 8 | 9 | # Retrieve the installation directory from the environment 10 | InstallDir = None 11 | if "DXSDK_DIR" in os.environ: 12 | InstallDir = os.environ["DXSDK_DIR"] 13 | 14 | # Setup some common paths relative to that 15 | if InstallDir != None: 16 | IncludeDirs = os.path.join(InstallDir, "Include") 17 | x86LibDir = os.path.join(InstallDir, "Lib/x86") 18 | x64LibDir = os.path.join(InstallDir, "Lib/x64") 19 | x86BinDir = os.path.join(InstallDir, "Utilities/bin/x86") 20 | x64BinDir = os.path.join(InstallDir, "Utilities/bin/x64") 21 | 22 | # When no install directory has been found, either the SDK is not installed 23 | # or it's bundled as part of the later Windows 8+ SDK. Copy values from that 24 | # to cover those cases. 25 | else: 26 | IncludeDirs = WindowsPlatform.IncludeDirs 27 | x86LibDir = WindowsPlatform.x86LibDir 28 | x64LibDir = WindowsPlatform.x64LibDir 29 | x86BinDir = WindowsPlatform.x86BinDir 30 | x64BinDir = WindowsPlatform.x64BinDir 31 | 32 | # 33 | # Usage: fxc 34 | # 35 | # /?, /help print this message 36 | # 37 | # /T target profile 38 | # /E entrypoint name 39 | # /I additional include path 40 | # /Vi display details about the include process 41 | # 42 | # /Od disable optimizations 43 | # /Op disable preshaders 44 | # /O{0,1,2,3} optimization level 0..3. 1 is default 45 | # /WX treat warnings as errors 46 | # /Vd disable validation 47 | # /Zi enable debugging information 48 | # /Zpr pack matrices in row-major order 49 | # /Zpc pack matrices in column-major order 50 | # 51 | # /Gpp force partial precision 52 | # /Gfa avoid flow control constructs 53 | # /Gfp prefer flow control constructs 54 | # /Gdp disable effect performance mode 55 | # /Ges enable strict mode 56 | # /Gec enable backwards compatibility mode 57 | # /Gis force IEEE strictness 58 | # /Gch compile as a child effect for FX 4.x targets 59 | # 60 | # /Fo output object file 61 | # /Fc output assembly code listing file 62 | # /Fx output assembly code and hex listing file 63 | # /Fh output header file containing object code 64 | # /Fe output warnings and errors to a specific file 65 | # /Vn use as variable name in header file 66 | # /Cc output color coded assembly listings 67 | # /Ni output instruction numbers in assembly listings 68 | # 69 | # /P preprocess to file (must be used alone) 70 | # 71 | # @ options response file 72 | # /dumpbin load a binary file rather than compiling 73 | # /Qstrip_reflect strip reflection data from 4_0+ shader bytecode 74 | # /Qstrip_debug strip debug information from 4_0+ shader bytecode 75 | # 76 | # /compress compress DX10 shader bytecode from files 77 | # /decompress decompress bytecode from first file, output files should 78 | # be listed in the order they were in during compression 79 | # 80 | # /D= define macro 81 | # /LD Load d3dx9_31.dll 82 | # /nologo suppress copyright message 83 | # 84 | # : cs_4_0 cs_4_1 cs_5_0 ds_5_0 fx_2_0 fx_4_0 fx_4_1 fx_5_0 gs_4_0 85 | # gs_4_1 gs_5_0 hs_5_0 ps_2_0 ps_2_a ps_2_b ps_2_sw ps_3_0 ps_3_sw ps_4_0 86 | # ps_4_0_level_9_1 ps_4_0_level_9_3 ps_4_0_level_9_0 ps_4_1 ps_5_0 tx_1_0 87 | # vs_1_1 vs_2_0 vs_2_a vs_2_sw vs_3_0 vs_3_sw vs_4_0 vs_4_0_level_9_1 88 | # vs_4_0_level_9_3 vs_4_0_level_9_0 vs_4_1 vs_5_0 89 | # 90 | class FXCompileOptions: 91 | 92 | def __init__(self): 93 | 94 | self.EntryPoint = None 95 | self.IncludePaths = [ ] 96 | 97 | self.DisableOptimisations = False 98 | self.OptimisationLevel = 1 99 | self.WarningsAsErrors = False 100 | self.DisableValidation = False 101 | self.EnableDebugInfo = False 102 | self.RowMajorMatrices = True 103 | 104 | self.PartialPrecision = False 105 | self.AvoidFlowControl = False 106 | self.PreferFlowControl = False 107 | self.Strict = False 108 | self.BackCompat = False 109 | self.IEEEStrict = False 110 | 111 | self.OutputObject = False 112 | self.OutputAsm = False 113 | self.OutputAsmHex = False 114 | self.OutputHeader = False 115 | self.OutputWarningsErrors = False 116 | self.HeaderVariableName = None 117 | self.InstructionNumbers = False 118 | 119 | self.Defines = [ ] 120 | self.NoLogo = True 121 | 122 | def UpdateCommandLine(self): 123 | 124 | # Start with showing includes for dependency evaluation 125 | cmdline = [ 126 | '/Vi' 127 | ] 128 | 129 | cmdline += [ '/I' + path for path in self.IncludePaths ] 130 | 131 | if self.DisableOptimisations: 132 | cmdline += [ '/Od' ] 133 | 134 | cmdline += [ '/O' + str(self.OptimisationLevel) ] 135 | 136 | if self.WarningsAsErrors: cmdline += [ '/WX' ] 137 | if self.DisableValidation: cmdline += [ '/Vd' ] 138 | if self.EnableDebugInfo: cmdline += [ '/Zi' ] 139 | 140 | if self.RowMajorMatrices: 141 | cmdline += [ '/Zpr' ] 142 | else: 143 | cmdline += [ '/Zpc' ] 144 | 145 | if self.PartialPrecision: cmdline += [ '/Gpp' ] 146 | if self.AvoidFlowControl: cmdline += [ '/Gfa' ] 147 | if self.PreferFlowControl: cmdline += [ '/Gfp' ] 148 | if self.Strict: cmdline += [ '/Ges' ] 149 | if self.BackCompat: cmdline += [ '/Gec' ] 150 | if self.IEEEStrict: cmdline += [ '/Gis' ] 151 | if self.HeaderVariableName: cmdline += [ '/Vn' + self.HeaderVariableName ] 152 | if self.InstructionNumbers: cmdline += [ '/Ni' ] 153 | 154 | cmdline += FXCompileOptions.FormatDefines(self.Defines) 155 | 156 | if self.NoLogo: 157 | cmdline += [ '/nologo' ] 158 | 159 | self.CommandLine = cmdline 160 | 161 | def FormatDefines(defines): 162 | 163 | cmdline = [ ] 164 | for define in defines: 165 | if isinstance(define, str): 166 | cmdline += [ '/D' + str(define) ] 167 | else: 168 | cmdline += [ '/D' + str(define[0]) + '=' + str(define[1])] 169 | return cmdline 170 | 171 | 172 | class FXCompileNode (BuildSystem.Node): 173 | 174 | def __init__(self, path, profile, path_postfix = "", defines = [ ], entry_point = None): 175 | 176 | super().__init__() 177 | self.Path = path 178 | self.Profile = profile 179 | self.PathPostfix = path_postfix 180 | self.DefineCmdLine = FXCompileOptions.FormatDefines(defines) 181 | self.EntryPoint = entry_point 182 | 183 | def Build(self, env): 184 | 185 | output_files = self.GetOutputFiles(env) 186 | 187 | # Node entry point takes precendence over config specified entry-point 188 | entry_point = self.EntryPoint 189 | if entry_point == None: 190 | entry_point = env.CurrentConfig.FXCompileOptions.EntryPoint 191 | 192 | # Build command line 193 | cmdline = [ os.path.join(x86BinDir, "fxc.exe") ] 194 | cmdline += [ self.Path, '/T' + self.Profile ] 195 | cmdline += env.CurrentConfig.FXCompileOptions.CommandLine 196 | cmdline += self.DefineCmdLine 197 | cmdline += self.BuildCommandLine 198 | if entry_point: 199 | cmdline += [ '/E' + entry_point ] 200 | Utils.ShowCmdLine(env, cmdline) 201 | 202 | # Create the include scanner and launch the compiler 203 | scanner = Utils.LineScanner(env) 204 | scanner.AddLineParser("Includes", "Resolved to [", [ "Opening file [", "Current working dir [" ], lambda line, length: line[length:-1].lstrip()) 205 | process = Process.OpenPiped(cmdline, env.EnvironmentVariables) 206 | Process.WaitForPipeOutput(process, scanner) 207 | 208 | # Record the implicit dependencies for this file 209 | data = env.GetFileMetadata(self.GetInputFile(env)) 210 | data.SetImplicitDeps(env, scanner.Includes) 211 | 212 | return process.returncode == 0 213 | 214 | def GetInputFile(self, env): 215 | 216 | return self.Path 217 | 218 | def AddOutputFile(self, option, file): 219 | 220 | self.BuildCommandLine += [ option + file ] 221 | return [ file ] 222 | 223 | def _GetOutputFiles(self, env, opts): 224 | 225 | # Get the relocated path with postfix before extension 226 | split_path = os.path.splitext(self.Path) 227 | path = split_path[0] 228 | path = os.path.join(env.CurrentConfig.OutputPath, path) 229 | path += self.PathPostfix 230 | path += split_path[1] 231 | 232 | # Start a local command-line for use by Build 233 | self.BuildCommandLine = [ ] 234 | 235 | # Add whatever output files have been specified 236 | files = [ ] 237 | if opts.OutputObject: 238 | files += self.AddOutputFile('/Fo', path + ".sobj") 239 | if opts.OutputAsmHex: 240 | files += self.AddOutputFile('/Fx', path + ".asm") 241 | elif opts.OutputAsm: 242 | files += self.AddOutputFile('/Fc', path + ".asm") 243 | if opts.OutputHeader: 244 | files += self.AddOutputFile('/Fh', path + ".h") 245 | if opts.OutputWarningsErrors: 246 | files += self.AddOutputFile('/Fe', path + ".elog") 247 | 248 | return files 249 | 250 | def GetOutputFiles(self, env): 251 | 252 | return self._GetOutputFiles(env, env.CurrentConfig.FXCompileOptions) 253 | 254 | def GetTempOutputFiles(self, env): 255 | 256 | return self.GetOutputFiles(env) 257 | 258 | 259 | #options = ShaderCompileOptions() 260 | #options.UpdateCommandLine() 261 | #print(options.CommandLine) -------------------------------------------------------------------------------- /Python/Environment.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # --- MIT Open Source License -------------------------------------------------- 4 | # PiB - Python Build System 5 | # Copyright (C) 2011 by Don Williamson 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # ------------------------------------------------------------------------------ 25 | # 26 | # Environment.py: The core dependency graph build/evaluation code and anything 27 | # else that doesn't fit elsewhere. 28 | # 29 | 30 | import os 31 | import sys 32 | import fnmatch 33 | import Utils 34 | import BuildSystem 35 | import MSVCPlatform 36 | import WindowsPlatform 37 | 38 | 39 | # 40 | # This may or may not work out in the future but it's a nice convenience that ties building configurations 41 | # with generating project files for the configurations. 42 | # 43 | class Config: 44 | 45 | def __init__(self, name, arg, base_config_options): 46 | 47 | self.Name = name 48 | self.CmdLineArg = arg 49 | self.IntermediatePath = "obj/" + name 50 | self.OutputPath = "bin/" + name 51 | self.CPPOptions = MSVCPlatform.VCCompileOptions(base_config_options) 52 | self.LinkOptions = MSVCPlatform.VCLinkOptions(base_config_options) 53 | self.LibOptions = MSVCPlatform.VCLibOptions(base_config_options) 54 | 55 | def SetPaths(self, path): 56 | 57 | path = path.replace("%config", self.Name) 58 | self.IntermediatePath = path.replace("%type", "obj") 59 | self.OutputPath = path.replace("%type", "bin") 60 | 61 | def SetPaths2(self, intermediate, output): 62 | 63 | self.IntermediatePath = intermediate 64 | self.OutputPath = output 65 | 66 | def UpdateCommandLines(self): 67 | 68 | self.CPPOptions.UpdateCommandLine() 69 | self.LinkOptions.UpdateCommandLine() 70 | self.LibOptions.UpdateCommandLine() 71 | 72 | 73 | # 74 | # The build environment - currently only Visual Studio 2005 is supported. 75 | # Contains the main dependency graph evaluation and a host of other things which 76 | # couldn't be put elsewhere. 77 | # 78 | class Environment: 79 | 80 | def New(): 81 | 82 | # Load the metadata first as that encodes as much of the cached environment state as possible 83 | metadata = BuildSystem.BuildMetadata.Load() 84 | 85 | # Check to see if the MSVC envvars are in the metadata before figuring them out, 86 | # as that's quite an expensive operation 87 | if metadata.UserData == None: 88 | envvars = MSVCPlatform.GetVisualCEnv() 89 | if envvars == None: 90 | return None 91 | metadata.UserData = envvars 92 | 93 | return Environment(metadata.UserData, metadata) 94 | 95 | def __init__(self, envvars, metadata): 96 | 97 | # Construc the environment variables 98 | self.EnvironmentVariables = envvars 99 | 100 | # Force node builds irrespective of dependencies? 101 | self.ForceBuild = "-force" in sys.argv 102 | self.NoToolOutput = "-no_tool_output" in sys.argv 103 | self.ShowCmdLine = "-show_cmdline" in sys.argv 104 | self.ConfigName = Utils.GetSysArgvProperty("-config", "debug") 105 | self.Verbose = "-verbose" in sys.argv 106 | 107 | # Show environment variables 108 | if "-show_env" in sys.argv: 109 | for k,v in metadata.UserData.items(): 110 | print(k, "=", v) 111 | 112 | # Parse any build filters in the command-line 113 | self.BuildTargets = Utils.GetSysArgvProperties("-target", None) 114 | self.BuildInputFilter = Utils.GetSysArgvProperty("-input_filter", None) 115 | if self.BuildInputFilter != None: 116 | self.BuildInputFilter = self.BuildInputFilter.lower() 117 | 118 | # Set up some default configurations 119 | self.Configs = { } 120 | self.Configs["debug"] = Config("Debug", "debug", MSVCPlatform.VCBaseConfig.DEBUG) 121 | self.Configs["release"] = Config("Release", "release", MSVCPlatform.VCBaseConfig.RELEASE) 122 | self.CurrentConfig = self.Configs[self.ConfigName] 123 | 124 | # Load existing file metadata from disk 125 | self.BuildMetadata = metadata 126 | self.CurrentBuildTarget = None 127 | 128 | def NewFile(self, filename): 129 | 130 | # Always add to the file map 131 | crc = self.BuildMetadata.AddToFileMap(filename) 132 | return BuildSystem.FileNode(crc) 133 | 134 | def NewFiles(self, path, pattern): 135 | 136 | return [ self.NewFile(filename) for filename in Utils.Glob(path, pattern) ] 137 | 138 | def OutputFile(self, env, node): 139 | 140 | return BuildSystem.OutputFileNode(env, node) 141 | 142 | def CPPFile(self, filename, override_cpp_opts = None): 143 | 144 | return MSVCPlatform.VCCompileNode(filename, override_cpp_opts) 145 | 146 | def Link(self, filename, obj_files, lib_files = [], weak_lib_files = []): 147 | 148 | return MSVCPlatform.VCLinkNode(filename, obj_files, lib_files, weak_lib_files) 149 | 150 | def Lib(self, filename, dependencies, lib_files = []): 151 | 152 | return MSVCPlatform.VCLibNode(filename, dependencies, lib_files) 153 | 154 | def CopyFile(self, source, dest_path): 155 | 156 | dest = os.path.join(dest_path, os.path.basename(source)) 157 | output = self.NewFile(source) 158 | return BuildSystem.CopyNode(output, source, dest) 159 | 160 | def CopyOutputFile(self, output, index, dest_path): 161 | 162 | source = output.GetOutputFiles(self)[index] 163 | dest = os.path.join(dest_path, os.path.basename(source)) 164 | return BuildSystem.CopyNode(output, source, dest) 165 | 166 | def FindFiles(self, path, patterns): 167 | 168 | # With no pattern, just use the path 169 | if patterns == None: 170 | return [ path ] 171 | 172 | # Process each pattern split by semi-colon 173 | matches = [ ] 174 | for pattern in patterns.split(";"): 175 | 176 | pattern = pattern.strip(); 177 | 178 | # Recursive walk the required path looking for pattern matches 179 | for root, dirnames, filenames in os.walk(path): 180 | for filename in fnmatch.filter(filenames, pattern): 181 | matches += [ os.path.join(root, filename) ] 182 | 183 | return matches 184 | 185 | def CopyFiles(self, path, patterns, dest_dir): 186 | 187 | files = self.FindFiles(path, patterns) 188 | 189 | # Generate copy nodes for each file 190 | copy_objs = [ ] 191 | for src in files: 192 | 193 | # Need the directory name relative to the source root 194 | dest = os.path.relpath(src, path) 195 | dest = os.path.dirname(dest) 196 | 197 | # Prefix with the publish destination 198 | dest = os.path.join(dest_dir, dest) 199 | 200 | copy_objs += [ self.CopyFile(src, dest) ] 201 | 202 | return copy_objs 203 | 204 | def GetFilename(self, crc): 205 | 206 | return self.BuildMetadata.GetFilename(crc) 207 | 208 | def GetFileMetadata(self, filename): 209 | 210 | return self.BuildMetadata.GetFileMetadata(self.CurrentBuildTarget, filename) 211 | 212 | def SaveFileMetadata(self): 213 | 214 | self.BuildMetadata.Save() 215 | 216 | def DeleteTempOutput(files): 217 | 218 | # If the output file exists then it has been built previously. 219 | # Delete the output before subsequent builds so that aborts can be detected next build. 220 | for file in files: 221 | Utils.RemoveFile(file) 222 | 223 | def MakeOutputDirs(files): 224 | 225 | for file in files: 226 | # If the output file doesn't exist then this may be the first build attempt. 227 | # In this case we have to ensure the directories for the output files exist before 228 | # creating them. 229 | dirname = os.path.dirname(file) 230 | if dirname != "": 231 | Utils.Makedirs(dirname) 232 | 233 | def ExecuteNodeBuild(self, node, tab): 234 | 235 | # Get some info about the input/output files 236 | input_filename = node.GetInputFile(self) 237 | output_filenames = node.GetOutputFiles(self) 238 | input_metadata = self.GetFileMetadata(input_filename) 239 | 240 | if self.Verbose: 241 | print(tab + "BUILD NODE: " + input_filename) 242 | 243 | # Don't build the same node more than once 244 | if node in self.BuildResults: 245 | return self.BuildResults[node] 246 | 247 | # Have any of the explicit dependencies changed? 248 | requires_build = self.ForceBuild 249 | success = True 250 | for dep in node.Dependencies: 251 | if self.Verbose: 252 | print(tab + " Explicit dependency: " + str(dep)) 253 | (a, b) = self.ExecuteNodeBuild(dep, tab + " ") 254 | requires_build |= a 255 | success &= b 256 | if a and self.Verbose: 257 | print(tab + " Changed: " + str(dep)) 258 | 259 | # Have any of the implicit dependencies changed? 260 | if not requires_build and input_metadata != None: 261 | for dep in input_metadata.ImplicitDeps: 262 | (a, b) = self.ExecuteNodeBuild(dep, tab + " ") 263 | requires_build |= a 264 | success &= b 265 | if a and self.Verbose: 266 | print(tab + "Implicit dependency changed: " + str(dep)) 267 | 268 | # If the dependencies haven't changed, check to see if the node itself has been changed 269 | if not requires_build and input_metadata != None and input_metadata.HasFileChanged(input_filename): 270 | requires_build = True 271 | if self.Verbose: 272 | print(tab + "Input has changed: " + input_filename + ", " + str(node)) 273 | 274 | # If any output files don't exist and no build is required, we must build! 275 | if not requires_build: 276 | for output_file in output_filenames: 277 | if input_filename != output_file and not os.path.exists(output_file): 278 | requires_build = True 279 | if self.Verbose: 280 | print(tab + "Output file doesn't exist: " + output_file) 281 | break 282 | 283 | # If any implicit output files don't exist and no build is required, we must build! 284 | if not requires_build and input_metadata != None: 285 | for output_file in input_metadata.ImplicitOutputs: 286 | output_filename = self.GetFilename(output_file.CRC) 287 | if not os.path.exists(output_filename): 288 | requires_build = True 289 | if self.Verbose: 290 | print(tab + "Implicit output file doesn't exist: " + output_filename) 291 | 292 | # At the last minute, cancel any builds if they're excluded by the input filter 293 | if requires_build and self.BuildInputFilter != None: 294 | input_filename = os.path.realpath(input_filename).lower() 295 | if self.BuildInputFilter not in input_filename: 296 | requires_build = False 297 | 298 | # Execute any build steps 299 | if requires_build and success: 300 | if Utils.ObjectHasMethod(node, "Build"): 301 | 302 | # Prepare for build aborts 303 | Environment.DeleteTempOutput(node.GetTempOutputFiles(self)) 304 | Environment.MakeOutputDirs(node.GetOutputFiles(self)) 305 | 306 | if not node.Build(self): 307 | success = False 308 | 309 | # Record the build result incase this node is visited again in this build step 310 | self.BuildResults[node] = (requires_build, success) 311 | return (requires_build, success) 312 | 313 | def ExecuteNodeClean(self, node): 314 | 315 | for dep in node.Dependencies: 316 | self.ExecuteNodeClean(dep) 317 | 318 | # Only clean if there is a build step for this node 319 | if Utils.ObjectHasMethod(node, "Build"): 320 | 321 | # Clean output files 322 | output_files = node.GetOutputFiles(self) 323 | for file in output_files: 324 | if self.Verbose: 325 | print("Deleting: " + file) 326 | Utils.RemoveFile(file) 327 | 328 | # Clean implicit output files 329 | input_metadata = self.GetFileMetadata(node.GetInputFile(self)) 330 | if input_metadata: 331 | for output_file in input_metadata.ImplicitOutputs: 332 | output_filename = self.GetFilename(output_file.CRC) 333 | if self.Verbose: 334 | print("Deleting: " + file) 335 | Utils.RemoveFile(output_filename) 336 | 337 | def Build(self, build_graphs, target = None): 338 | 339 | # Apply the current build target 340 | self.CurrentBuildTarget = self.CurrentConfig.Name + ":" 341 | if target == None: 342 | self.CurrentBuildTarget += "PiBDefaultTarget" 343 | else: 344 | self.CurrentBuildTarget += target 345 | 346 | # Reset build results on each build 347 | self.BuildResults = { } 348 | 349 | # Promote to a list if necessary 350 | if type(build_graphs) != type([]): 351 | build_graphs = [ build_graphs ] 352 | 353 | # Exclude targets not mentioned on the command-line, if any 354 | if target != None and len(self.BuildTargets): 355 | if target not in self.BuildTargets: 356 | return 357 | 358 | # Determine a printable target name 359 | target_name = "" 360 | if target != None: 361 | target_name = " target '" + target + "'" 362 | 363 | # Clean outputs? 364 | if "clean" in sys.argv or "rebuild" in sys.argv: 365 | print("PiB Cleaning" + target_name + "...") 366 | [ self.ExecuteNodeClean(bg) for bg in build_graphs ] 367 | 368 | # Build the graph? 369 | if "rebuild" in sys.argv or not "clean" in sys.argv: 370 | print("PiB Building" + target_name + "...") 371 | [ self.ExecuteNodeBuild(bg, "") for bg in build_graphs ] 372 | 373 | self.BuildMetadata.UpdateModTimes(self.CurrentBuildTarget) 374 | self.CurrentBuildTarget = None 375 | 376 | 377 | if __name__ == '__main__': 378 | Environment.New() 379 | -------------------------------------------------------------------------------- /Python/MSVCGeneration.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # --- MIT Open Source License -------------------------------------------------- 4 | # PiB - Python Build System 5 | # Copyright (C) 2011 by Don Williamson 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # ------------------------------------------------------------------------------ 25 | # 26 | # MSVCGeneration.py: Automatic, dependency-based generation of Visual Studio C++ 27 | # 2005/2008 Projects and Solutions 28 | # 29 | 30 | import os 31 | import uuid 32 | import hashlib 33 | import base64 34 | import sys 35 | import Utils 36 | 37 | 38 | class MSVCGeneration2005: 39 | VisualStudio = "2005" 40 | Solution = "9.00" 41 | Project = "8.00" 42 | 43 | class MSVCGeneration2008: 44 | VisualStudio = "2008" 45 | Solution = "10.00" 46 | Project = "9.00" 47 | 48 | 49 | def SolutionHeader(): 50 | 51 | # Determine the current version using the exact same logic that drives the compilation 52 | version = MSVCGeneration2005 53 | if os.getenv("VS90COMNTOOLS"): 54 | version = MSVCGeneration2008 55 | 56 | header = """Microsoft Visual Studio Solution File, Format Version {0} 57 | # Visual Studio {1}""" 58 | header = header.format(version.Solution, version.VisualStudio) 59 | return header 60 | 61 | def ProjectHeader(): 62 | # Determine the current version using the exact same logic that drives the compilation 63 | version = MSVCGeneration2005 64 | if os.getenv("VS90COMNTOOLS"): 65 | version = MSVCGeneration2008 66 | 67 | header = """ 75 | 76 | 79 | 80 | 81 | """ 82 | header = header.replace("{0}", version.Project) 83 | return header 84 | 85 | 86 | vcproj_config = """ 93 | 106 | """ 107 | 108 | def CreateFolderLists(dict): 109 | 110 | # Gather a list of folders first 111 | folders = [ ] 112 | for dir, content in dict.items(): 113 | if content != None: 114 | folders += [ (dir, CreateFolderLists(content)) ] 115 | 116 | # Followed by a list of files 117 | files = [ ] 118 | for dir, content in dict.items(): 119 | if content == None: 120 | files += [ (dir, None) ] 121 | 122 | folders = sorted(folders) 123 | files = sorted(files) 124 | 125 | return folders + files 126 | 127 | 128 | def WriteProjectFiles(f, tab, name, entries): 129 | 130 | # Write file markup if this is a file 131 | if entries == None: 132 | print(tab + "", file=f) 135 | print(tab + "", file=f) 136 | return 137 | 138 | # Open a filter tag if this is a named folder 139 | if name != "": 140 | print(tab + "", file=f) 145 | 146 | # Recurse into the entries of this folder 147 | for entry in entries: 148 | WriteProjectFiles(f, tab, entry[0], entry[1]) 149 | 150 | # Close the filter tag 151 | if name != "": 152 | print(tab[:-1] + "", file=f) 153 | 154 | 155 | def DoesProjectNeedUpdating(vcproj_path, files): 156 | 157 | # Hash all the inputs 158 | md5 = hashlib.md5() 159 | for file in files: 160 | md5.update(bytes(file, "utf-8")) 161 | 162 | src_digest = md5.digest() 163 | src_digest = base64.urlsafe_b64encode(src_digest) 164 | src_digest = bytes(src_digest).decode() 165 | 166 | # Forced regeneration 167 | if "-force_vcfiles" in sys.argv or "-force_vcproj" in sys.argv: 168 | return src_digest 169 | 170 | # Regenerate if it doesn't exist 171 | if not os.path.exists(vcproj_path): 172 | return src_digest 173 | 174 | with open(vcproj_path, "r") as f: 175 | 176 | # Search the file for the previous digest 177 | dst_digest = None 178 | lines = f.readlines() 179 | for i in range(len(lines)): 180 | if 'Name="PiBDigest"' in lines[i] and i + 1 < len(lines): 181 | dst_digest = lines[i + 1].split('"')[1] 182 | 183 | # Regeneration required if it's not there 184 | if dst_digest == None: 185 | return src_digest 186 | 187 | # If they're equal, no need to return a new digest 188 | if src_digest == dst_digest: 189 | return None 190 | 191 | return src_digest 192 | 193 | 194 | # Need: input files, configurations and args to run for configurations 195 | def VCGenerateProjectFile(env, name, files, output, targets=None, configs=None, replacements = [ ], pibfile = "pibfile", include_search = [ ]): 196 | 197 | # Promote target to a list 198 | if targets != None and type(targets) != type([]): 199 | targets = [ targets ] 200 | 201 | # Exclude targets not mentioned on the command-line, if any 202 | if targets != None and len(env.BuildTargets): 203 | valid_targets = set(targets).intersection(env.BuildTargets) 204 | if len(valid_targets) == 0: 205 | return 206 | 207 | # Generate file paths 208 | vcproj_path = name + ".vcproj" 209 | vcproj_name = os.path.basename(name) 210 | vcproj_dir = os.path.dirname(vcproj_path) 211 | vcproj_guid = str(uuid.uuid1()).upper() 212 | 213 | # Remove the file if requested 214 | if "-remove_vcfiles" in sys.argv: 215 | if os.path.exists(vcproj_path): 216 | print("Deleting " + vcproj_path) 217 | os.remove(vcproj_path) 218 | return 219 | 220 | # Ensure the paths are normalised for stable hashing 221 | input_files = [ os.path.normcase(os.path.normpath(file)) for file in files] 222 | if targets != None: 223 | input_files += targets 224 | 225 | # Does the vcproj need to be regenerated? 226 | digest = DoesProjectNeedUpdating(vcproj_path, input_files) 227 | if digest == None: 228 | return vcproj_guid 229 | 230 | print("Generating VCProject file: " + vcproj_path) 231 | 232 | # Use default configs from the environment if none are specified 233 | if configs == None: 234 | configs = env.Configs 235 | 236 | pibcmd = None 237 | if pibfile != None: 238 | # Figure out the relative location of the pibfile 239 | pibfile = os.path.relpath(pibfile, vcproj_dir) 240 | pibfile_dir = os.path.dirname(pibfile) 241 | 242 | # Switch to the pibfile directory before launching the build 243 | pibcmd = "pib -pf " + os.path.basename(pibfile) + " " 244 | if pibfile_dir != "": 245 | pibcmd = "cd " + pibfile_dir + " & " + pibcmd # '&' in XML-speak 246 | 247 | # Construct target specification command-line option 248 | target_opt = "" 249 | if targets != None: 250 | for target in targets: 251 | target_opt += " -target " + target 252 | 253 | # Concatenate include search paths 254 | include_search_str = "" 255 | for include in include_search: 256 | include_search_str += include + ";" 257 | 258 | f = open(vcproj_path, "w") 259 | 260 | print('', file=f) 261 | 262 | # Generate the header 263 | vcproj_header = ProjectHeader() 264 | header_xml = vcproj_header.replace("%NAME%", vcproj_name) 265 | header_xml = header_xml.replace("%GUID%", vcproj_guid) 266 | print(header_xml, file=f) 267 | 268 | # Generate each configuration 269 | print("\t", file=f) 270 | for name, config in env.Configs.items(): 271 | 272 | xml = vcproj_config.replace("%CONFIG%", config.Name) 273 | 274 | xml = xml.replace("%OUTPUTDIR%", os.path.relpath(config.OutputPath, vcproj_dir)) 275 | xml = xml.replace("%INTERDIR%", os.path.relpath(config.IntermediatePath, vcproj_dir)) 276 | xml = xml.replace("%INCLUDESEARCH%", include_search_str) 277 | 278 | if pibcmd != None: 279 | xml = xml.replace("%BUILD%", pibcmd + "-config " + config.CmdLineArg + target_opt) 280 | xml = xml.replace("%REBUILD%", pibcmd + "rebuild " + "-config " + config.CmdLineArg + target_opt) 281 | xml = xml.replace("%CLEAN%", pibcmd + "clean " + "-config " + config.CmdLineArg + target_opt) 282 | else: 283 | xml = xml.replace("%BUILD%", "") 284 | xml = xml.replace("%REBUILD%", "") 285 | xml = xml.replace("%CLEAN%", "") 286 | 287 | # Specify the output executable for debugging 288 | if output != None: 289 | (output_path, output_ext) = output.GetPrimaryOutput(config) 290 | xml = xml.replace("%OUTPUT%", os.path.relpath(output_path + output_ext, vcproj_dir)) 291 | else: 292 | xml = xml.replace("%OUTPUT%", "") 293 | 294 | print(xml, file=f) 295 | 296 | print("\t", file=f) 297 | print("\t", file=f) 298 | print("\t", file=f) 299 | 300 | # Create a hierarchical dictionary of folders and files 301 | folders = { } 302 | for file in files: 303 | 304 | # We need the path relative to the project file 305 | filename = os.path.relpath(file, vcproj_dir) 306 | filename = os.path.normpath(filename) 307 | 308 | # Separate the filename as seen in Visual Studio from the physical filename on disk 309 | # Perform any text replacements requested by the caller 310 | folder_filename = filename 311 | for r in replacements: 312 | folder_filename = folder_filename.replace(r[0], r[1]) 313 | 314 | # Split the path into its component parts 315 | file_dir = os.path.dirname(folder_filename) 316 | dir_parts = [] 317 | if file_dir != "": 318 | dir_parts = file_dir.split(os.sep) 319 | 320 | # Walk along each part creating any nodes looking for the final host directory 321 | host_dir = folders 322 | for part in dir_parts: 323 | if part not in host_dir: 324 | host_dir[part] = { } 325 | host_dir = host_dir[part] 326 | 327 | host_dir[filename] = None 328 | 329 | # Convert the dictionaries into sorted lists 330 | folders = CreateFolderLists(folders) 331 | 332 | print("\t", file=f) 333 | WriteProjectFiles(f, "\t\t", "", folders) 334 | print("\t", file=f) 335 | 336 | # Write the digest for detecting regeneration 337 | print("\t", file=f) 338 | print("\t\t", file=f) 342 | print("\t", file=f) 343 | 344 | print("", file=f) 345 | f.close() 346 | 347 | 348 | def DoesSolutionNeedUpdating(sln_path, projects): 349 | 350 | # Hash all the inputs 351 | # TODO: Output filename 352 | md5 = hashlib.md5() 353 | for name in projects: 354 | md5.update(bytes(name, "utf-8")) 355 | 356 | src_digest = md5.digest() 357 | src_digest = base64.urlsafe_b64encode(src_digest) 358 | src_digest = bytes(src_digest).decode() 359 | 360 | # Forced regeneration 361 | if "-force_vcfiles" in sys.argv or "-force_vcsln" in sys.argv: 362 | return src_digest 363 | 364 | # Regenerate if it doesn't exist 365 | if not os.path.exists(sln_path): 366 | return src_digest 367 | 368 | with open(sln_path, "r") as f: 369 | 370 | # Find the line with the metadata 371 | dst_digest = None 372 | lines = f.readlines() 373 | for line in lines: 374 | if line.startswith("\t\tPiBDigest = "): 375 | dst_digest = line.split(" = ")[1][:-1] 376 | break 377 | 378 | # Regeneration required if there's no comparison key 379 | if dst_digest == None: 380 | return src_digest 381 | 382 | # If they're equal, no need to return a new digest 383 | if src_digest == dst_digest: 384 | return None 385 | 386 | return src_digest 387 | 388 | 389 | def ReadProjectGUID(vcproj_path): 390 | 391 | with open(vcproj_path, "r") as f: 392 | 393 | for line in f.readlines(): 394 | 395 | line = line.strip() 396 | if line.startswith("ProjectGUID="): 397 | guid = line.split("=")[1][1:-1] 398 | return guid 399 | 400 | 401 | def VCGenerateSolutionFile(env, name, add_dependencies, projects): 402 | 403 | sln_path = name + ".sln" 404 | 405 | # Remove the file if requested 406 | if "-remove_vcfiles" in sys.argv: 407 | if os.path.exists(sln_path): 408 | print("Deleting " + sln_path) 409 | os.remove(sln_path) 410 | return 411 | 412 | # Does the sln file need to be generated? 413 | digest = DoesSolutionNeedUpdating(sln_path, projects) 414 | if digest == None: 415 | return 416 | 417 | print("Generating Solution File: " + sln_path) 418 | 419 | f = open(sln_path, "w") 420 | 421 | sln_header = SolutionHeader() 422 | print(sln_header, file=f) 423 | 424 | # Write the project summary 425 | guids = { } 426 | prev_guid = None 427 | for name in projects: 428 | vcproj_path = os.path.normpath(name + ".vcproj") 429 | vcproj_name = os.path.basename(name) 430 | guids[name] = ReadProjectGUID(vcproj_path) 431 | 432 | # Need the vcproj path relative to the solution for Visual Studio 433 | vcproj_path = os.path.relpath(vcproj_path, os.path.dirname(sln_path)) 434 | print('Project("{' + str(uuid.uuid1()).upper() + '}") = "' + vcproj_name + '", "' + vcproj_path + '", "' + guids[name] + '"', file=f) 435 | 436 | # Ensure the Solution build order matches the order in which projects were passed 437 | if add_dependencies and prev_guid != None: 438 | print("\tProjectSection(ProjectDependencies) = postProject", file=f) 439 | print("\t\t" + prev_guid + " = " + prev_guid, file=f) 440 | print("\tEndProjectSection", file=f) 441 | 442 | prev_guid = guids[name] 443 | print("EndProject", file=f) 444 | 445 | print("Global", file=f) 446 | print("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution", file=f) 447 | 448 | # Write the configuration summary 449 | for config in env.Configs.values(): 450 | print("\t\t" + config.Name + "|Win32 = " + config.Name + "|Win32", file=f) 451 | 452 | print("\tEndGlobalSection", file=f) 453 | print("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution", file=f) 454 | 455 | # Write how each solution configs to each project config 456 | for name in projects: 457 | for config in env.Configs.values(): 458 | config_name = config.Name + "|Win32" 459 | prefix = "\t\t" + guids[name] + "." + config_name 460 | print(prefix + ".ActiveCfg = " + config_name, file=f) 461 | print(prefix + ".Build.0 = " + config_name, file=f) 462 | 463 | print("\tEndGlobalSection", file=f) 464 | print("\tGlobalSection(SolutionProperties) = preSolution", file=f) 465 | print("\t\tHideSolutionNode = FALSE", file=f) 466 | print("\tEndGlobalSection", file=f) 467 | 468 | # Record the solution digest 469 | print("\tGlobalSection(ExtensibilityGlobals) = postSolution", file=f) 470 | print("\t\tPiBDigest = " + digest, file=f) 471 | print("\tEndGlobalSection", file=f) 472 | 473 | print("EndGlobal", file=f) 474 | 475 | f.close() 476 | -------------------------------------------------------------------------------- /Python/MSVCPlatform.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # --- MIT Open Source License -------------------------------------------------- 4 | # PiB - Python Build System 5 | # Copyright (C) 2011 by Don Williamson 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # ------------------------------------------------------------------------------ 25 | # 26 | # MSVCPlatform.py: Command-line parameter abstraction and build nodes for 27 | # Microsoft Visual C++ 2005/2008/2010. 28 | # 29 | # C/C++ Building Reference (2005): 30 | # http://msdn.microsoft.com/en-us/library/91621w01(v=VS.80).aspx 31 | # 32 | # Compiler Warnings that are Off by Default 33 | # http://msdn.microsoft.com/en-us/library/23k5d385(v=VS.80).aspx 34 | # 35 | # Potentially useful warnings: 36 | # 37 | # C4191 'operator/operation' : unsafe conversion from 'type of expression' to 'type required' 38 | # C4242 'identifier' : conversion from 'type1' to 'type2', possible loss of data 39 | # C4263 'function' : member function does not override any base class virtual member function 40 | # C4264 'virtual_function' : no override available for virtual member function from base 'class'; function is hidden 41 | # C4266 'function' : no override available for virtual member function from base 'type'; function is hidden 42 | # C4287 'operator' : unsigned/negative constant mismatch 43 | # C4289 nonstandard extension used : 'var' : loop control variable declared in the for-loop is used outside the for-loop scope 44 | # C4296 'operator' : expression is always false 45 | # C4302 'conversion' : truncation from 'type 1' to 'type 2' 46 | # C4365 'action' : conversion from 'type_1' to 'type_2', signed/unsigned mismatch 47 | # 48 | 49 | import os 50 | import sys 51 | import Utils 52 | import Process 53 | import BuildSystem 54 | 55 | 56 | VSToolsDir = None 57 | VCVarsPath = None 58 | VCIncludeDir = None 59 | VCLibraryDir = None 60 | VSCRTVer = None 61 | 62 | 63 | def GetVSInstallDir(vs_tools_dir): 64 | 65 | vs_install_dir = vs_tools_dir 66 | while vs_install_dir != None and vs_install_dir != "": 67 | split_path = os.path.split(vs_install_dir) 68 | 69 | # Detect infinite loop 70 | if vs_install_dir == split_path[0]: 71 | print("ERROR: Visual Studio Tools path is not formatted as expected") 72 | VSInstallDir = None 73 | break 74 | 75 | vs_install_dir = split_path[0] 76 | if split_path[1] == "Common7": 77 | break 78 | 79 | return vs_install_dir 80 | 81 | 82 | # Allow the user to override which Visual Studio version to use 83 | def UserAllows(version): 84 | user_msvc_ver = Utils.GetSysArgvProperty("-msvc_ver") 85 | if user_msvc_ver == None: 86 | return True 87 | return user_msvc_ver == version 88 | 89 | # These versions use vswhere.exe but where is that? Search known directory layouts for now 90 | vs_2019_path = "C:/Program Files (x86)/Microsoft Visual Studio/2019" 91 | vs_2017_path = "C:/Program Files (x86)/Microsoft Visual Studio/2017" 92 | 93 | if VSToolsDir == None and UserAllows("2019"): 94 | if os.path.exists(vs_2019_path + "/BuildTools/Common7/Tools"): 95 | VSToolsDir = vs_2019_path + "/BuildTools/Common7/Tools" 96 | VSCRTVer = "14.28.29333" 97 | elif os.path.exists(vs_2019_path + "/Community/Common7/Tools"): 98 | VSToolsDir = vs_2019_path + "/Community/Common7/Tools" 99 | VSCRTVer = "14.28.29910" 100 | 101 | if VSToolsDir == None and UserAllows("2017"): 102 | if os.path.exists(vs_2017_path + "/Community/Common7/Tools"): 103 | VSToolsDir = vs_2017_path + "/Community/Common7/Tools" 104 | VSCRTVer = "14.15.26726" 105 | 106 | # Complete paths for new method 107 | if VSToolsDir != None: 108 | VSInstallDir = GetVSInstallDir(VSToolsDir) 109 | if VSInstallDir != None: 110 | VCVarsPath = os.path.join(VSInstallDir, "VC/Auxiliary/Build/vcvarsall.bat") 111 | VCIncludeDir = os.path.join(VSInstallDir, "VC/Tools/MSVC/" + VSCRTVer + "/include") 112 | VCLibraryDir = os.path.join(VSInstallDir, "VC/Tools/MSVC/" + VSCRTVer + "/lib/x86") 113 | 114 | # Use the old method 115 | if VSToolsDir == None: 116 | if VSToolsDir == None and UserAllows("2015"): 117 | VSToolsDir = os.getenv("VS140COMNTOOLS") 118 | if VSToolsDir == None and UserAllows("2013"): 119 | VSToolsDir = os.getenv("VS120COMNTOOLS") 120 | if VSToolsDir == None and UserAllows("2012"): 121 | VSToolsDir = os.getenv("VS110COMNTOOLS") 122 | if VSToolsDir == None and UserAllows("2008"): 123 | VSToolsDir = os.getenv("VS90COMNTOOLS") 124 | if VSToolsDir == None and UserAllows("2005"): 125 | VSToolsDir = os.getenv("VS80COMNTOOLS") 126 | 127 | VSInstallDir = GetVSInstallDir(VSToolsDir) 128 | 129 | # VC directories are a subdirectory of VS install 130 | if VSInstallDir != None: 131 | VCVarsPath = os.path.join(VSInstallDir, "VC/vcvarsall.bat") 132 | VCIncludeDir = os.path.join(VSInstallDir, "VC/include") 133 | VCLibraryDir = os.path.join(VSInstallDir, "VC/lib") 134 | 135 | # Show chosen environment 136 | if "-msvc_show_env" in sys.argv: 137 | print("VSToolsDir = ", VSToolsDir) 138 | print("VSInstallDir = ", VSInstallDir) 139 | print("VCVarsPath = ", VCVarsPath) 140 | print("VCIncludeDir = ", VCIncludeDir) 141 | print("VCLibraryDir = ", VCLibraryDir) 142 | 143 | if VSToolsDir == None: 144 | print("ERROR: Failed to find installed Visual Studio") 145 | sys.exit(1) 146 | 147 | 148 | # 149 | # There is no direct way in Python to apply the environment of one subprocess to another. The typical solution is 150 | # to generate a batch file at runtime that does the following: 151 | # 152 | # call ApplyEnvironment.bat 153 | # RunProcess.exe 154 | # 155 | # Rather than doing this for each call to cl.exe, I'm calling the batch file once at the start and copying the 156 | # resulting environment for use later when calling cl.exe. 157 | # 158 | def GetVisualCEnv(): 159 | 160 | if VSInstallDir == None: 161 | print("ERROR: Visual Studio install directory not detected") 162 | return None 163 | 164 | # Locate the batch file that sets up the Visual C build environment 165 | if not os.path.exists(VCVarsPath): 166 | print("ERROR: Visual C environment setup batch file not found") 167 | return None 168 | 169 | # Run the batch file, output the environment and prepare it for parsing 170 | process = Process.OpenPiped(VCVarsPath + " x86 & echo ===ENVBEGIN=== & set") 171 | output = Process.WaitForPipeOutput(process) 172 | output = output.split("===ENVBEGIN=== \r\n")[1] 173 | output = output.splitlines() 174 | 175 | # Start with the current environment, override with any parsed environment values 176 | env = os.environ.copy() 177 | for line in output: 178 | try: 179 | var, value = line.split("=") 180 | env[var.upper()] = value 181 | except: 182 | print("WARNING: environment variables skipped -> " + line) 183 | 184 | # This environment variable is defined in the VS2005 IDE and prevents cl.exe output 185 | # being correctly captured, so remove it! 186 | if "VS_UNICODE_OUTPUT" in env: 187 | del env["VS_UNICODE_OUTPUT"] 188 | 189 | return env 190 | 191 | 192 | # 193 | # Visual C++ Compiler (cl.exe) 194 | # 195 | # Options: 196 | # 197 | # /c Compiles without linking 198 | # /nologo Suppresses the logo 199 | # /showIncludes Display a list of all include files during compilation 200 | # /W{0|1|2|3|4} Warning level 201 | # /WX Treat warnings as errors 202 | # /errorReport:{none|prompt|queue|send} How to report ICEs to Microsoft 203 | # 204 | # /O1 Minimise size (/Og /Os /Oy /Ob2 /Gs /GF /Gy) 205 | # /O2 Maximise speed (/Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) 206 | # /Ob{0|1|2} Disable inline expansion, expand marked funtions, compiler expands what it wants 207 | # /Od Disable optimisations 208 | # /Og Provides local and global optimisations (DEPRECATED) 209 | # /Oi Generate intrinsic functions 210 | # /Os Favour smaller code 211 | # /Ot Favour faster code 212 | # /Ox Full optimisation - favours speed over size (/Og /Oi /Ot /Ob2 /Oy) 213 | # /Oy Omits frame pointers (X86 ONLY) 214 | # 215 | # /arch:{SSE|SSE2} Specifies architecture for code generation (X86 ONLY) 216 | # /EH{s|a}[c][-] Specifies exception handling behaviour 217 | # /fp:{precise|except[-]|fast|strict} Specifies floating-point behaviour 218 | # /Gd __cdecl calling convention, except marked (X86 ONLY) 219 | # /Gr __fastcall calling convention, except marked (X86 ONLY) 220 | # /Gz __stdcall calling convention, except marked (X86 ONLY) 221 | # /GF Enable read-only string pooling 222 | # /GL[-] Enable whole program optimisation 223 | # /Gs Controls stack probes 224 | # /Gy Enable function level linking 225 | # /MD References multi-threaded MSVCRT.lib, code is in a DLL. Defines _MT, _DLL. 226 | # /MDd References multi-threaded MSVCRTD.lib, code is in a DLL. Defines _DEBUG, _MT, _DLL. 227 | # /MT References multi-threaded LIBCMT.lib, code is linked statically. Defines _MT. 228 | # /MTd References multi-threaded LIBCMTD.lib, code is linked statically. Defines _DEBUG, _MT. 229 | # 230 | # /Fopathname Specifies the output .obj file 231 | # /Fppathname Provides a path name for a precompiled header instead of using the default payh name 232 | # /Fdpathname Specifies a name for the PDB file 233 | # 234 | # /GS[-] Detects buffer overruns that overwrite the return address (on by default) 235 | # /RTC{c|s|u} Controls runtime error checking 236 | # /Zi Produce debugging info in PDB files 237 | # /ZI Produce debugging info in PDB files with edit and continue (X86 ONLY) 238 | # 239 | # /D[= | #[{string|number}] ] Defines a preprocessing symbol for your source file 240 | # /I[ ]directory Adds a directory to the list of directories searched for include files 241 | # 242 | # /Y- Ignores all other PCH compiler options in the current build 243 | # /Yc[filename] Create a PCH 244 | # /Yu[filename] Use a PCH 245 | # 246 | # Typical cl.exe command-lines: 247 | # 248 | # Debug Windows 249 | # /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /Gm /EHsc /RTC1 /MDd /Fo"Debug\\" /Fd"Debug\vc80.pdb" /W3 /nologo /c /Wp64 /ZI /TP /errorReport:prompt 250 | # 251 | # Release Windows 252 | # /O2 /GL /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /FD /EHsc /MD /Fo"Release\\" /Fd"Release\vc80.pdb" /W3 /nologo /c /Wp64 /Zi /TP /errorReport:prompt 253 | # 254 | # Note that DLL builds add "_WINDLL". This may only be needed so that you can decide whether to use declspec dllimport or dllexport. 255 | 256 | VCBaseConfig = Utils.enum( 257 | 'DEBUG', 258 | 'RELEASE' 259 | ) 260 | 261 | VCArchitecture = Utils.enum( 262 | DEFAULT = None, 263 | IA32 = '/arch:IA32', 264 | SSE = '/arch:SSE', 265 | SSE2 = '/arch:SSE2', 266 | AVX = '/arch:AVX', 267 | AVX2 = '/arch:AVX2' 268 | ) 269 | 270 | VCFloatingPoint = Utils.enum( 271 | PRECISE = '/fp:precise', 272 | FAST = '/fp:fast', 273 | STRICT = '/fp:strict' 274 | ) 275 | 276 | VCCallingConvention = Utils.enum( 277 | CDECL = '/Gd', 278 | FASTCALL = '/Gr', 279 | STDCALL = '/Gz' 280 | ) 281 | 282 | VCOptimisations = Utils.enum( 283 | DISABLE = '/Od', 284 | SIZE = '/O1', 285 | SPEED = '/O2' 286 | ) 287 | 288 | VCDebuggingInfo = Utils.enum( 289 | DISABLE = None, 290 | PDB = '/Zi', 291 | PDBEDITANDCONTINUE = '/ZI' 292 | ) 293 | 294 | VCCRTType = Utils.enum( 295 | MT_DLL = '/MD', 296 | MT_DEBUG_DLL = '/MDd', 297 | MT = '/MT', 298 | MT_DEBUG = '/MTd' 299 | ) 300 | 301 | VCExceptionHandling = Utils.enum( 302 | DISABLE = None, 303 | CPP_ONLY = '/EHsc', 304 | CPP_SEH = '/EHa', 305 | ) 306 | 307 | VCStandard = Utils.enum( 308 | CPP_14 = '/std:c++14', 309 | CPP_17 = '/std:c++17', 310 | CPP_20 = '/std:c++20', 311 | CPP_LATEST = '/std:c++latest', 312 | C_11 = '/std:c11', 313 | C_17 = '/std:c17', 314 | ) 315 | 316 | 317 | class VCCompileOptions: 318 | 319 | def __init__(self, config): 320 | 321 | # Initialise the requested config settings 322 | if config == VCBaseConfig.DEBUG: 323 | self.InitDebug() 324 | elif config == VCBaseConfig.RELEASE: 325 | self.InitRelease() 326 | 327 | def InitDebug(self): 328 | 329 | # Default settings for all compiler options 330 | self.Alignment = 8 331 | self.Architecture = VCArchitecture.DEFAULT 332 | self.CallingConvention = VCCallingConvention.CDECL 333 | self.CRTType = VCCRTType.MT_DEBUG 334 | self.CompileAsC = False 335 | self.DebuggingInfo = VCDebuggingInfo.PDBEDITANDCONTINUE 336 | self.Defines = [ 'WIN32', '_WINDOWS' ] 337 | self.DetectBufferOverruns = True 338 | self.DisabledWarnings = [ ] 339 | self.EnableIntrinsicFunctions = False 340 | self.ExceptionHandling = VCExceptionHandling.CPP_ONLY 341 | self.FloatingPoint = VCFloatingPoint.PRECISE 342 | self.FloatingPointExceptions = False 343 | self.FullPathnameReports = True 344 | self.IncludePaths = [ ] 345 | self.NoLogo = True 346 | self.Optimisations = VCOptimisations.DISABLE 347 | self.ReportClassLayout = False 348 | self.ReportSingleClassLayout = [ ] 349 | self.RTTI = True 350 | self.RuntimeChecks = True 351 | self.WarningLevel = 3 352 | self.WarningsAsErrors = False 353 | self.WholeProgramOptimisation = False 354 | self.Standard = None 355 | self.UpdateCommandLine() 356 | 357 | def InitRelease(self): 358 | 359 | # Initialise changes from debug 360 | self.InitDebug() 361 | self.DebuggingInfo = VCDebuggingInfo.PDB 362 | self.RuntimeChecks = False 363 | self.DetectBufferOverruns = False 364 | self.Optimisations = VCOptimisations.SPEED 365 | self.WholeProgramOptimisation = True 366 | self.EnableIntrinsicFunctions = True 367 | self.CRTType = VCCRTType.MT 368 | self.Defines.extend( [ 'NDEBUG' ]) 369 | self.UpdateCommandLine() 370 | 371 | def UpdateCommandLine(self): 372 | 373 | # Compile only & we need showIncludes for dependency evaluation 374 | # Complete exception handling 375 | cmdline = [ 376 | '/c', # Compile only 377 | '/showIncludes', # Show includes for dependency evaluation 378 | '/errorReport:none', # Don't send any ICEs to Microsoft 379 | '/Zc:threadSafeInit-', # Disable C++11 thread-safe statics 380 | #'/Bt+', 381 | #'/d2cgsummary', 382 | ] 383 | 384 | # Construct the command line from the set options 385 | 386 | if self.NoLogo: 387 | cmdline += [ '/nologo' ] 388 | 389 | cmdline += [ "/W" + str(self.WarningLevel) ] 390 | cmdline += [ self.CRTType ] 391 | 392 | if self.ExceptionHandling != None: 393 | cmdline += [ self.ExceptionHandling ] 394 | 395 | if self.WarningsAsErrors: 396 | cmdline += [ "/WX" ] 397 | 398 | for warning in self.DisabledWarnings: 399 | cmdline += [ "/wd" + str(warning) ] 400 | 401 | if self.Architecture != None: 402 | cmdline += [ self.Architecture ] 403 | 404 | cmdline += [ self.FloatingPoint ] 405 | cmdline += [ '/Zp' + str(self.Alignment) ] 406 | 407 | if self.FloatingPointExceptions: 408 | cmdline += "/fp:except" 409 | 410 | cmdline += [ self.CallingConvention ] 411 | 412 | if self.DebuggingInfo != None: 413 | cmdline += [ self.DebuggingInfo ] 414 | 415 | if self.RuntimeChecks: 416 | cmdline += [ "/RTC1" ] 417 | 418 | if not self.RTTI: 419 | cmdline += [ "/GR-" ] 420 | 421 | if not self.DetectBufferOverruns: 422 | cmdline += [ "/GS-" ] 423 | 424 | cmdline += [ self.Optimisations ] 425 | 426 | if self.WholeProgramOptimisation: 427 | cmdline += [ "/GL" ] 428 | 429 | if self.EnableIntrinsicFunctions: 430 | cmdline += [ "/Oi" ] 431 | 432 | for define in self.Defines: 433 | cmdline += [ '/D', define ] 434 | 435 | for include in self.IncludePaths: 436 | cmdline += [ '/I', include ] 437 | 438 | if self.ReportClassLayout: 439 | cmdline += [ '/d1reportAllClassLayout' ] 440 | 441 | for cls in self.ReportSingleClassLayout: 442 | cmdline += [ '/d1reportSingleClassLayout' + cls ] 443 | 444 | if self.FullPathnameReports: 445 | cmdline += [ '/FC' ] 446 | 447 | if self.CompileAsC: 448 | cmdline += [ '/TC' ] 449 | 450 | if self.Standard != None: 451 | cmdline += [ self.Standard ] 452 | 453 | self.CommandLine = cmdline 454 | 455 | 456 | # 457 | # Visual C++ Linker (link.exe) 458 | # 459 | # Command-line: 460 | # 461 | # LINK.exe [options] [files] [@responsefile] 462 | # 463 | # Options: 464 | # 465 | # /DEBUG Creates debugging information 466 | # /DEFAULTLIB:library Adds a library that is searched AFTER input libraries but BEFORE the default libraries named in .obj files 467 | # /DLL Builds a DLL 468 | # /ENTRY:function Specifies an entry point function 469 | # /INCREMENTAL[:NO] By default the linker runs in incremental mode, this allows you to change that 470 | # /LARGEADDRESSAWARE Tells the compiler that the application supports addresses larger than 2GB 471 | # /LIBPATH:dir Adds a library search path that gets used before the environment LIB path 472 | # 473 | # /LTCG[:NOSTATUS|:STATUS|:PGINSTRUMENT|:PGOPTIMIZE|:PGUPDATE] Link-time Code Generation control 474 | # 475 | # /MACHINE:{X64|X86} Specifies the target platform 476 | # /MAP[:filename] Generate a MAP file 477 | # /NOLOGO Suppresses the logo 478 | # /NODEFAULTLIB[:library] Tells the linker to remove one or more default libraries from the list (the compiler can insert some) 479 | # /OPT:{REF|NOREF} Controls the optimisations performed during a build 480 | # /OPT:{ICF[=iterations]|NOICF} 481 | # /OPT:{WIN98|NOWIN98} 482 | # /OUT:filename Specifies the output filename 483 | # /PDB:filename Creates a program database file 484 | # /SUBSYSTEM:{CONSOLE|WINDOWS} Either command-line or window based application (main or WinMain) 485 | # /WX[:NO] Treat linker warnings as errors 486 | # 487 | # Typical link.exe command-lines: 488 | # 489 | # Debug Windows EXE 490 | # /OUT:"D:\dev\projects\TestProject\Debug\TestProject.exe" /INCREMENTAL /NOLOGO /MANIFEST /MANIFESTFILE:"Debug\TestProject.exe.intermediate.manifest" /DEBUG 491 | # /PDB:"d:\dev\projects\testproject\debug\TestProject.pdb" /SUBSYSTEM:WINDOWS /MACHINE:X86 /ERRORREPORT:PROMPT 492 | # kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib 493 | # 494 | # Release Windows EXE 495 | # /OUT:"D:\dev\projects\TestProject\Release\TestProject.exe" /INCREMENTAL:NO /NOLOGO /MANIFEST /MANIFESTFILE:"Release\TestProject.exe.intermediate.manifest" /DEBUG 496 | # /PDB:"d:\dev\projects\testproject\release\TestProject.pdb" /SUBSYSTEM:WINDOWS /OPT:REF /OPT:ICF /LTCG /MACHINE:X86 /ERRORREPORT:PROMPT 497 | # kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib 498 | # 499 | 500 | VCMachine = Utils.enum( 501 | X86 = '/MACHINE:x86', 502 | X64 = '/MACHINE:x64' 503 | ) 504 | 505 | VCUnrefSymbols = Utils.enum( 506 | ELIMINATE = '/OPT:REF', 507 | KEEP = '/OPT:NOREF' 508 | ) 509 | 510 | VCDupComdats = Utils.enum( 511 | FOLD = '/OPT:ICF', 512 | KEEP = '/OPT:NOICF' 513 | ) 514 | 515 | VCSubsystem = Utils.enum( 516 | CONSOLE = '/SUBSYSTEM:CONSOLE', 517 | WINDOWS = '/SUBSYSTEM:WINDOWS' 518 | ) 519 | 520 | 521 | class VCLinkOptions: 522 | 523 | def __init__(self, config): 524 | 525 | # Initialise the requested config settings 526 | if config == VCBaseConfig.DEBUG: 527 | self.InitDebug() 528 | elif config == VCBaseConfig.RELEASE: 529 | self.InitRelease() 530 | 531 | def InitDebug(self): 532 | 533 | # Default settings for all linker options 534 | self.SafeSEH = False 535 | self.Debug = True 536 | self.NoLogo = True 537 | self.DLL = False 538 | self.EntryPoint = None 539 | self.Incremental = True 540 | self.LargeAddressAware = False 541 | self.LTCG = False 542 | self.Machine = VCMachine.X86 543 | self.MapFile = False 544 | self.UnrefSymbols = VCUnrefSymbols.KEEP 545 | self.DupComdats = VCDupComdats.KEEP 546 | self.Subsystem = VCSubsystem.WINDOWS 547 | self.DefaultLibs = [ ] 548 | self.NoDefaultLibs = False 549 | self.NoDefaultLib = [ ] 550 | self.LibPaths = [ ] 551 | self.UpdateCommandLine() 552 | 553 | def InitRelease(self): 554 | 555 | # Initialise changes from debug 556 | self.InitDebug() 557 | self.Incremental = False 558 | self.LTCG = True 559 | self.UnrefSymbols = VCUnrefSymbols.ELIMINATE 560 | self.DupComdats = VCDupComdats.FOLD 561 | self.UpdateCommandLine() 562 | 563 | def UpdateCommandLine(self): 564 | 565 | cmdline = [ 566 | '/ERRORREPORT:NONE', # Don't send any ICEs to Microsoft 567 | '/VERBOSE:LIB' # Show libs searched for dependency evaluation 568 | ] 569 | 570 | if self.SafeSEH: 571 | cmdline += [ "/SAFESEH" ] 572 | 573 | if self.Debug: 574 | cmdline += [ "/DEBUG" ] 575 | 576 | if self.NoLogo: 577 | cmdline += [ "/NOLOGO" ] 578 | 579 | if self.DLL: 580 | cmdline += [ "/DLL" ] 581 | 582 | if self.EntryPoint != None: 583 | cmdline += [ "/ENTRY:" + self.EntryPoint ] 584 | 585 | # Compiler is incremental by default 586 | if not self.Incremental: 587 | cmdline += [ "/INCREMENTAL:NO" ] 588 | 589 | if self.LargeAddressAware: 590 | cmdline += [ "/LARGEADDRESSAWARE" ] 591 | 592 | if self.LTCG: 593 | cmdline += [ "/LTCG" ] 594 | 595 | cmdline += [ self.Machine ] 596 | cmdline += [ self.UnrefSymbols ] 597 | cmdline += [ self.DupComdats ] 598 | cmdline += [ self.Subsystem ] 599 | 600 | for lib in self.DefaultLibs: 601 | cmdline += [ "/DEFAULTLIB:" + lib ] 602 | 603 | for lib in self.NoDefaultLib: 604 | cmdline += [ "/NODEFAULTLIB:" + lib ] 605 | 606 | if self.NoDefaultLibs: 607 | cmdline += [ "/NODEFAULTLIB" ] 608 | 609 | for path in self.LibPaths: 610 | cmdline += [ "/LIBPATH:" + path ] 611 | 612 | self.CommandLine = cmdline 613 | 614 | 615 | # 616 | # Visual C++ Librarian (lib.exe) 617 | # 618 | # Command-line: 619 | # 620 | # LIB [options] [files] 621 | # 622 | # Options: 623 | # 624 | # /LIBPATH:dir Library path to search for when merging libraries 625 | # /LTCG Enable Link Time Code Generation 626 | # /MACHINE:{X64|X86} Specifies the target platform - not normally needed as it's inferred from the .obj file 627 | # /NODEFAULTLIB[:library] Tells the librarian to remove one or more default libraries from the list (the compiler can insert some) 628 | # /NOLOGO Suppress the logo 629 | # /OUT:filename Output library file 630 | # /SUBSYSTEM:{CONSOLE|WINDOWS} Specifies the platform type 631 | # /WX[:NO] Treat warnings as errors 632 | # 633 | # Typical command-lines: 634 | # 635 | # Debug 636 | # /OUT:"D:\dev\projects\TestProject\Debug\TestProject.lib" /NOLOGO 637 | # 638 | # Release 639 | # /OUT:"D:\dev\projects\TestProject\Release\TestProject.lib" /NOLOGO /LTCG 640 | # 641 | 642 | class VCLibOptions: 643 | 644 | def __init__(self, config): 645 | 646 | # Initialise the requested config settings 647 | if config == VCBaseConfig.DEBUG: 648 | self.InitDebug() 649 | elif config == VCBaseConfig.RELEASE: 650 | self.InitRelease() 651 | 652 | def InitDebug(self): 653 | 654 | # Default settings for all librarian options 655 | self.LTCG = False 656 | self.Machine = VCMachine.X86 657 | self.NoLogo = True 658 | self.Subsystem = None 659 | self.WarningsAsErrors = False 660 | self.LibPaths = [ ] 661 | self.NoDefaultLibs = False 662 | self.NoDefaultLib = [ ] 663 | self.UpdateCommandLine() 664 | 665 | def InitRelease(self): 666 | 667 | # Initialise changes from debug 668 | self.InitDebug() 669 | self.LTCG = True 670 | self.UpdateCommandLine() 671 | 672 | def UpdateCommandLine(self): 673 | 674 | cmdline = [ 675 | '/ERRORREPORT:NONE' # Don't send ICEs to Microsoft 676 | ] 677 | 678 | if self.LTCG: 679 | cmdline += [ '/LTCG' ] 680 | 681 | cmdline += [ self.Machine ] 682 | 683 | if self.NoLogo: 684 | cmdline += [ '/NOLOGO' ] 685 | 686 | # Subsystem is implied 687 | if self.Subsystem != None: 688 | cmdline += [ self.Subsystem ] 689 | 690 | if self.WarningsAsErrors: 691 | cmdline += [ '/WX' ] 692 | 693 | for lib in self.LibPaths: 694 | cmdline += [ '/LIBPATH:' + lib ] 695 | 696 | for lib in self.NoDefaultLib: 697 | cmdline += [ "/NODEFAULTLIB:" + lib ] 698 | 699 | if self.NoDefaultLibs: 700 | cmdline += [ "/NODEFAULTLIB" ] 701 | 702 | self.CommandLine = cmdline 703 | 704 | 705 | # 706 | # A node for compiling a single C/C++ file to a .obj file 707 | # 708 | class VCCompileNode (BuildSystem.Node): 709 | 710 | def __init__(self, path, override_cpp_opts): 711 | 712 | super().__init__() 713 | self.Path = path 714 | self.OverrideCPPOptions = override_cpp_opts 715 | 716 | def Build(self, env): 717 | 718 | output_files = self.GetOutputFiles(env) 719 | 720 | # Construct the command-line 721 | cpp_opts = self.GetCPPOptions(env) 722 | cmdline = [ "cl.exe" ] + cpp_opts.CommandLine 723 | if len(output_files) > 1: 724 | cmdline += [ "/Fd" + output_files[1] ] 725 | cmdline += [ "/Fo" + output_files[0], self.GetInputFile(env) ] 726 | Utils.ShowCmdLine(env, cmdline) 727 | 728 | # Create the include scanner and launch the compiler 729 | scanner = Utils.LineScanner(env) 730 | scanner.AddLineParser("Includes", "Note: including file:", None, lambda line, length: line[length:].lstrip()) 731 | process = Process.OpenPiped(cmdline, env.EnvironmentVariables) 732 | Process.PollPipeOutput(process, scanner) 733 | 734 | # Record the implicit dependencies for this file 735 | data = env.GetFileMetadata(self.GetInputFile(env)) 736 | data.SetImplicitDeps(env, scanner.Includes) 737 | 738 | return process.returncode == 0 739 | 740 | def SetCPPOptions(self, override_cpp_opts): 741 | 742 | self.OverrideCPPOptions = override_cpp_opts 743 | 744 | def GetCPPOptions(self, env): 745 | 746 | return env.CurrentConfig.CPPOptions if self.OverrideCPPOptions is None else self.OverrideCPPOptions 747 | 748 | def GetInputFile(self, env): 749 | 750 | return self.Path 751 | 752 | def GetOutputFiles(self, env): 753 | 754 | # Get the relocated path minus extension 755 | path = os.path.splitext(self.Path)[0] 756 | path = os.path.join(env.CurrentConfig.IntermediatePath, path) 757 | 758 | files = [ path + ".obj" ] 759 | 760 | cpp_opts = self.GetCPPOptions(env) 761 | if cpp_opts.DebuggingInfo != None: 762 | 763 | # The best we can do here is ensure that the obj\src directory for 764 | # a group of files shares the same pdb/idb 765 | path = os.path.dirname(path) 766 | files += [ os.path.join(path, "vc100.pdb") ] 767 | 768 | if cpp_opts.DebuggingInfo == VCDebuggingInfo.PDBEDITANDCONTINUE: 769 | files += [ os.path.join(path, "vc100.idb") ] 770 | 771 | return files 772 | 773 | def GetTempOutputFiles(self, env): 774 | 775 | return [ self.GetOutputFiles(env)[0] ] 776 | 777 | 778 | # 779 | # A node for linking an EXE or DLL given an output path and list of dependencies 780 | # 781 | class VCLinkNode (BuildSystem.Node): 782 | 783 | def __init__(self, path, obj_files, lib_files, weak_lib_files): 784 | 785 | super().__init__() 786 | self.Path = path 787 | 788 | # Object files are explicit dependencies, lib files are implicit, scanned during output 789 | self.Dependencies = obj_files 790 | self.LibFiles = lib_files 791 | self.WeakLibFiles = weak_lib_files 792 | 793 | def Build(self, env): 794 | 795 | output_files = self.GetOutputFiles(env) 796 | Utils.Print(env, "Linking: " + output_files[0] + "\n") 797 | 798 | # Construct the command-line 799 | cmdline = [ "link.exe" ] + env.CurrentConfig.LinkOptions.CommandLine 800 | cmdline += [ '/OUT:' + output_files[0] ] 801 | if env.CurrentConfig.LinkOptions.MapFile: 802 | cmdline += [ "/MAP:" + output_files[1] ] 803 | cmdline += [ dep.GetOutputFiles(env)[0] for dep in self.Dependencies ] 804 | cmdline += [ dep.GetOutputFiles(env)[0] for dep in self.LibFiles ] 805 | cmdline += [ dep.GetOutputFiles(env)[0] for dep in self.WeakLibFiles ] 806 | Utils.ShowCmdLine(env, cmdline) 807 | 808 | # 809 | # When library files get added as dependencies to this link node they get added without a path. 810 | # This requires the linker to check its list of search paths for the location of any input 811 | # library files. 812 | # 813 | # The build system however, needs full paths to evaluate dependencies on each build. Rather than 814 | # trying to search the library paths in the build system (and potentially getting them wrong/different 815 | # to the linker), the linker is asked to output the full path of all libraries it links with. These 816 | # then get added as implicit dependencies. 817 | # 818 | # Create the lib scanner and run the link process 819 | # 820 | scanner = Utils.LineScanner(env) 821 | scanner.AddLineParser("Includes", "Searching ", [ "Searching libraries", "Finished searching libraries" ], lambda line, length: line[length:-1]) 822 | process = Process.OpenPiped(cmdline, env.EnvironmentVariables) 823 | Process.PollPipeOutput(process, scanner) 824 | 825 | # 826 | # Weak library files are those that should be provided as input to the link step but not used 827 | # as dependencies to check if the link step needs to be rebuilt. Search for those in the scanner 828 | # output and exclude them from the implicit dependency list. 829 | # 830 | includes = [ ] 831 | for include in scanner.Includes: 832 | 833 | ignore_dep = False 834 | for lib in self.WeakLibFiles: 835 | lib_name = lib.GetInputFile(env) 836 | if lib_name in include: 837 | ignore_dep = True 838 | break 839 | 840 | if not ignore_dep: 841 | includes.append(include) 842 | 843 | # Record the implicit dependencies for this file 844 | data = env.GetFileMetadata(self.GetInputFile(env)) 845 | data.SetImplicitDeps(env, includes) 846 | 847 | return process.returncode == 0 848 | 849 | def GetInputFile(self, env): 850 | 851 | path = os.path.join(env.CurrentConfig.OutputPath, self.Path) 852 | return path 853 | 854 | def GetPrimaryOutput(self, config): 855 | 856 | # Get the relocated path minus extension 857 | path = os.path.splitext(self.Path)[0] 858 | path = os.path.join(config.OutputPath, path) 859 | 860 | ext = ".exe" 861 | if config.LinkOptions.DLL: 862 | ext = ".dll" 863 | 864 | return (path, ext) 865 | 866 | def GetOutputFiles(self, env): 867 | 868 | # Add the EXE/DLL 869 | (path, ext) = self.GetPrimaryOutput(env.CurrentConfig) 870 | files = [ path + ext ] 871 | 872 | # Make sure the .map file is in the intermediate directory, of the same name as the output 873 | if env.CurrentConfig.LinkOptions.MapFile: 874 | map_file = os.path.splitext(self.Path)[0] + ".map" 875 | map_file = os.path.join(env.CurrentConfig.IntermediatePath, map_file) 876 | files += [ map_file ] 877 | 878 | if env.CurrentConfig.LinkOptions.Debug: 879 | files += [ path + ".pdb" ] 880 | 881 | if env.CurrentConfig.LinkOptions.Incremental: 882 | files += [ path + ".ilk" ] 883 | 884 | return files 885 | 886 | def __repr__(self): 887 | 888 | return "LINK: " + self.Path 889 | 890 | 891 | # 892 | # A node for compositing a set of dependencies into a library file 893 | # 894 | class VCLibNode (BuildSystem.Node): 895 | 896 | def __init__(self, path, dependencies, lib_files): 897 | 898 | super().__init__() 899 | self.Path = path 900 | 901 | # Object files are explicit dependencies, lib files are implicit, scanned during output 902 | self.Dependencies = dependencies 903 | self.LibFiles = lib_files 904 | 905 | def Build(self, env): 906 | 907 | output_files = self.GetOutputFiles(env) 908 | 909 | # Construct the command-line 910 | cmdline = [ "lib.exe" ] + env.CurrentConfig.LibOptions.CommandLine 911 | cmdline += [ '/OUT:' + output_files[0] ] 912 | cmdline += [ dep.GetOutputFiles(env)[0] for dep in self.Dependencies ] 913 | cmdline += [ dep.GetOutputFiles(env)[0] for dep in self.LibFiles ] 914 | Utils.Print(env, "Librarian: " + output_files[0]) 915 | Utils.ShowCmdLine(env, cmdline) 916 | 917 | # Run the librarian process 918 | process = Process.OpenPiped(cmdline, env.EnvironmentVariables) 919 | output = Process.WaitForPipeOutput(process) 920 | if not env.NoToolOutput: 921 | Utils.Print(env, output) 922 | 923 | return process.returncode == 0 924 | 925 | def GetInputFile(self, env): 926 | 927 | path = os.path.join(env.CurrentConfig.OutputPath, self.Path) 928 | return path 929 | 930 | def GetPrimaryOutput(self, config): 931 | 932 | # Get the relocated path minus extension 933 | path = os.path.splitext(self.Path)[0] 934 | path = os.path.join(config.OutputPath, path) 935 | return (path, ".lib") 936 | 937 | def GetOutputFiles(self, env): 938 | 939 | (path, ext) = self.GetPrimaryOutput(env.CurrentConfig) 940 | return [ path + ext ] 941 | 942 | 943 | def __RunTests(): 944 | 945 | options = VCCompileOptions(VCBaseConfig.DEBUG) 946 | print(options.BuildCommandLine()) 947 | options = VCCompileOptions(VCBaseConfig.RELEASE) 948 | print(options.BuildCommandLine()) 949 | 950 | options = VCLinkOptions(VCBaseConfig.DEBUG) 951 | print (options.BuildCommandLine()) 952 | options = VCLinkOptions(VCBaseConfig.RELEASE) 953 | print (options.BuildCommandLine()) 954 | 955 | options = VCLibOptions(VCBaseConfig.DEBUG) 956 | print (options.BuildCommandLine()) 957 | options = VCLibOptions(VCBaseConfig.RELEASE) 958 | print (options.BuildCommandLine()) 959 | 960 | 961 | if __name__ == "__main__": 962 | __RunTests() 963 | -------------------------------------------------------------------------------- /Python/OpenCLPlatform.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Uses the OpenCL Precompiler available at https://github.com/Celtoys/oclpc 4 | # 5 | 6 | import os 7 | import Utils 8 | import Process 9 | import BuildSystem 10 | 11 | 12 | # Require user to set the installation directory 13 | _InstallPath = None 14 | def SetInstallPath(path): 15 | global _InstallPath 16 | _InstallPath = path 17 | 18 | 19 | class OpenCLCompileOptions: 20 | 21 | def __init__(self): 22 | 23 | self.Verbose = False 24 | 25 | # Platform/device selection 26 | self.PlatformIndex = -1 27 | self.DeviceIndex = -1 28 | self.PlatformSubstr = None 29 | self.DeviceSubstr = None 30 | 31 | # Preprocessor options 32 | self.DefineMacros = [ ] 33 | self.IncludePaths = [ ] 34 | 35 | 36 | def UpdateCommandLine(self): 37 | 38 | cmdline = [ "-noheader" ] 39 | 40 | if self.Verbose: cmdline += [ "-verbose" ] 41 | 42 | if self.PlatformIndex != -1: cmdline += [ "-platform_index " + self.PlatformIndex ] 43 | if self.DeviceIndex != -1: cmdline += [ "-device_index " + self.DeviceIndex ] 44 | if self.PlatformSubstr != None: cmdline += [ "-platform_substr " + self.PlatformSubstr ] 45 | if self.DeviceSubstr != None: cmdline += [ "-device_substr " + self.DeviceSubstr ] 46 | 47 | cmdline += [ "-D " + macro for macro in self.DefineMacros ] 48 | cmdline += [ "-I " + path for path in self.IncludePaths ] 49 | 50 | self.CommandLine = cmdline 51 | 52 | 53 | class BuildOpenCLNode (BuildSystem.Node): 54 | 55 | def __init__(self, source): 56 | 57 | super().__init__() 58 | self.Source = source 59 | self.Dependencies = [ source ] 60 | 61 | def Build(self, env): 62 | 63 | # Build command-line from current configuration 64 | cmdline = [ os.path.join(_InstallPath, "oclpc.exe") ] 65 | cmdline += env.CurrentConfig.OpenCLCompileOptions.CommandLine 66 | cmdline += [ self.GetInputFile(env) ] 67 | Utils.ShowCmdLine(env, cmdline) 68 | 69 | # Launch the compiler and wait for it to finish 70 | process = Process.OpenPiped(cmdline) 71 | output = Process.WaitForPipeOutput(process) 72 | if not env.NoToolOutput: 73 | print(output) 74 | 75 | # Write a dummy output file on build success 76 | if process.returncode == 0: 77 | output_files = self.GetOutputFiles(env) 78 | with open(output_files[0], "w") as out_file: 79 | print("Built successfully", file=out_file) 80 | 81 | return process.returncode == 0 82 | 83 | def GetInputFile(self, env): 84 | 85 | return self.Source.GetOutputFiles(env)[0] 86 | 87 | def GetOutputFiles(self, env): 88 | 89 | # Get the relocated path minus extension 90 | path = os.path.splitext(self.GetInputFile(env))[0] 91 | path = os.path.join(env.CurrentConfig.OutputPath, path) 92 | return [ path + "_built.txt" ] 93 | 94 | def GetTempOutputFiles(self, env): 95 | 96 | return self.GetOutputFiles(env) 97 | -------------------------------------------------------------------------------- /Python/Packages.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # --- MIT Open Source License -------------------------------------------------- 4 | # PiB - Python Build System 5 | # Copyright (C) 2011 by Don Williamson 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # ------------------------------------------------------------------------------ 25 | # 26 | # Packages.py: Experimental, simple package "manager" 27 | # 28 | 29 | import io 30 | import os 31 | import shutil 32 | import tempfile 33 | import urllib.request 34 | import zipfile 35 | 36 | import Process 37 | import Utils 38 | 39 | def DownloadFile(url): 40 | # Generate a temporary download location 41 | filename = os.path.join(tempfile._get_default_tempdir(), next(tempfile._get_candidate_names())) 42 | 43 | print(f"Downloading {url}") 44 | 45 | # Query server for download 46 | response = urllib.request.urlopen(url) 47 | length = response.getheader("content-length") 48 | 49 | # Determine download block size for progress 50 | if length: 51 | print(f" Size: {length} bytes") 52 | length = int(length) 53 | block_size = max(4096, length // 100) 54 | else: 55 | print(" Size Unknown") 56 | block_size = 1024 * 1024 57 | 58 | # Download in blocks 59 | buffer = io.BytesIO() 60 | size = 0 61 | while True: 62 | block_buffer = response.read(block_size) 63 | if not block_buffer: 64 | break 65 | buffer.write(block_buffer) 66 | size += len(block_buffer) 67 | 68 | # Display progress percentage 69 | if length: 70 | print(f"\t{int(size / length * 100)} %\r", end="") 71 | 72 | # Clear progress line 73 | print() 74 | 75 | # Write download to file 76 | with open(filename, "wb") as f: 77 | f.write(buffer.getbuffer()) 78 | 79 | return filename 80 | 81 | def ExtractZipFileTo(filename, path): 82 | # Extract entire zip file 83 | print(f"Extracting {filename} to {path}") 84 | with zipfile.ZipFile(filename) as zf: 85 | zf.extractall(path) 86 | 87 | return path 88 | 89 | def ExtractZipFile(filename): 90 | # Generate a temporary extract location 91 | path = os.path.join(tempfile._get_default_tempdir(), next(tempfile._get_candidate_names())) 92 | return ExtractZipFileTo(filename, path) 93 | 94 | def ExtractMsiFileTo(filename, path): 95 | print(f"Extracting {filename} to {path}") 96 | os.system(f"msiexec /a {filename} /qb TARGETDIR={path}") 97 | return path 98 | 99 | def ExtractMsiFile(filename): 100 | path = os.path.join(tempfile._get_default_tempdir(), next(tempfile._get_candidate_names())) 101 | return ExtractMsiFileTo(filename, path) 102 | 103 | # 7-Zip not installed by default 104 | SevenZipExe = None 105 | 106 | def Install7Zip(version, path): 107 | # See if 7zip is already there first 108 | SevenZipExe = os.path.join(path, f"7zip\\{version}\\7za.exe") 109 | if not os.path.exists(SevenZipExe): 110 | 111 | # Download and extract the whole thing 112 | stripped_ver = version.replace(".", "") 113 | SevenZipUrl = f"https://www.7-zip.org/a/7za{stripped_ver}.zip" 114 | downloaded = DownloadFile(SevenZipUrl) 115 | extracted = ExtractZipFile(downloaded) 116 | 117 | # Copy just the executable 118 | Utils.Makedirs(os.path.dirname(SevenZipExe)) 119 | Utils.CopyFile(os.path.join(extracted, "7za.exe"), SevenZipExe) 120 | 121 | def Extract7ZipFileTo(filename, path): 122 | if SevenZipExe == None: 123 | print("ERROR: 7-Zip has not been installed. Call Install7Zip first.") 124 | return 125 | 126 | # Use previously-installed 7zip 127 | command_line = f"{SevenZipExe} x -o{path} {filename}" 128 | process = Process.OpenPiped(command_line) 129 | Process.PollPipeOutput(process, lambda t: print(t.strip("\r\n"))) 130 | -------------------------------------------------------------------------------- /Python/PiB.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | set PYTHONDIR="c:\Program Files (x86)\Python 3.5\python.exe" 5 | set PIBDIR=%~dp0 6 | 7 | %PYTHONDIR% -u %PIBDIR%\pib.py %* -------------------------------------------------------------------------------- /Python/PiB.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # --- MIT Open Source License -------------------------------------------------- 4 | # PiB - Python Build System 5 | # Copyright (C) 2011 by Don Williamson 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # ------------------------------------------------------------------------------ 25 | # 26 | # PiB.py: Entry point for the build system. 27 | # 28 | # TODO: 29 | # 30 | # Instead of using input/output FILES, use NODES. 31 | # Cache explicit dependencies and mix with Windows USN Journal 32 | # Are configurations enough? Are they too much? 33 | # Conversion of command-line options to the command-line is a little error-prone 34 | # Does the code deal with #include "file.cpp"? 35 | # Needs to be simpler! I'm sure I've missed some key opportunities in the 36 | # dependency graph stuff 37 | # Add profiling and debug output 38 | # 39 | # Functionality tests: 40 | # 41 | # Build from scratch 42 | # Modify source files 43 | # Delete .obj files 44 | # Delete .exe files 45 | # File with error 46 | # Modify implicitly dependent files 47 | # Clean & build 48 | # Delete output directories 49 | # 50 | 51 | import os 52 | import sys 53 | 54 | # Fix for upgrade to 3.6 where relative imports no longer work 55 | sys.path.append(os.path.dirname(os.path.realpath(__file__))) 56 | 57 | import Utils 58 | #import cProfile 59 | #import pstats 60 | from datetime import datetime 61 | 62 | start_time = datetime.now() 63 | 64 | # See if the caller wants to use a custom build script name/location 65 | pibfile = Utils.GetSysArgvProperty("-pf", "pibfile") 66 | #cProfile.run('Utils.ExecPibfile(pibfile)', sort=pstats.SortKey.CUMULATIVE) 67 | Utils.ExecPibfile(pibfile) 68 | 69 | # Print closing message with time elapsed 70 | time_elapsed = (datetime.now() - start_time).total_seconds() 71 | minutes, seconds = divmod(int(time_elapsed), 60) 72 | milliseconds = int((time_elapsed - seconds) * 1000) 73 | print(f"Done within {minutes}:{seconds}.{milliseconds}") 74 | -------------------------------------------------------------------------------- /Python/Process.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # --- MIT Open Source License -------------------------------------------------- 4 | # PiB - Python Build System 5 | # Copyright (C) 2011 by Don Williamson 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # ------------------------------------------------------------------------------ 25 | # 26 | # Process.py: Functions for launching processes and capturing their output. 27 | # 28 | 29 | import subprocess 30 | import os 31 | 32 | 33 | def OpenPiped(args, env = None): 34 | 35 | # Even if the executable is in the path of the modified environment, you need to specify the full path to execute it 36 | # This is because Popen uses the existing environment to find the executable, applying the modified environment after 37 | if env != None: 38 | 39 | # Split all paths 40 | paths = env["PATH"] 41 | paths = paths.split(";") 42 | 43 | # Try and find a path that hosts the executable and modify the input 44 | for path in paths: 45 | file = os.path.join(path, args[0]) 46 | if os.path.exists(file): 47 | args[0] = file 48 | break 49 | 50 | # Send output to a pipe, push stderr through stdout to ensure they're ordered correctly 51 | #print (args) 52 | try: 53 | output = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) 54 | except: 55 | print(args) 56 | raise 57 | 58 | return output 59 | 60 | 61 | def WaitForPipeOutput(process, line_handler=None): 62 | 63 | # Note that the output from stdout is a bytearray and Python 3.0 strings are now Unicode 64 | # Need to "decode" the byte array: http://stackoverflow.com/questions/606191/convert-byte-array-to-python-string 65 | # 66 | # DO NOT USE process.wait -> communicate! 67 | # See: http://bugs.python.org/issue1236, http://scons.tigris.org/source/browse/scons/trunk/src/engine/SCons/Tool/MSCommon/common.py?revision=4958&view=markup 68 | # 69 | # process.wait() 70 | # output = process.communicate()[0] 71 | 72 | if line_handler != None: 73 | 74 | # Use a line handler if specified 75 | output = process.stdout.readlines() 76 | for line in output: 77 | line_handler(bytearray(line).decode()) 78 | 79 | # Force commit of the returncode parameter in process 80 | while process.returncode == None: 81 | process.poll() 82 | 83 | else: 84 | 85 | output = process.stdout.read() 86 | 87 | # Force commit of the returncode parameter in process 88 | while process.returncode == None: 89 | process.poll() 90 | 91 | return bytearray(output).decode() 92 | 93 | 94 | def PollPipeOutput(process, line_handler): 95 | 96 | # Iteration on readline stalls until the next line immediately comes through. It also ensures all lines are read from the 97 | # process until shutdown. 98 | for line in iter(process.stdout.readline, b""): 99 | 100 | # Note that the output from stdout is a bytearray and Python 3.0 strings are now Unicode 101 | # Need to "decode" the byte array: http://stackoverflow.com/questions/606191/convert-byte-array-to-python-string 102 | line = bytearray(line).decode() 103 | line_handler(line) 104 | 105 | # Return exit code 106 | process.stdout.close() 107 | return process.wait() 108 | -------------------------------------------------------------------------------- /Python/ShaderCompiler.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | import Utils 5 | import Process 6 | from DirectXPlatform import FXCompileOptions 7 | from DirectXPlatform import FXCompileNode 8 | 9 | 10 | # Directory where the shader compiler is located 11 | # TODO: Should this be in ShaderCompileOptions? 12 | ShaderCompilerPath = None 13 | 14 | # Whether to dump internal debugging information from the shader compiler 15 | ShowTrace = "-shader_compiler_trace" in sys.argv 16 | 17 | 18 | def SetCompilerPath(path): 19 | global ShaderCompilerPath 20 | ShaderCompilerPath = path 21 | 22 | 23 | class ShaderCompileOptions(FXCompileOptions): 24 | 25 | def __init__(self): 26 | 27 | super().__init__() 28 | self.SourceRoot = None 29 | self.CppOutputPath = None 30 | 31 | def UpdateCommandLine(self): 32 | 33 | super().UpdateCommandLine() 34 | 35 | if self.SourceRoot: 36 | self.CommandLine += [ "/SourceRoot" + self.SourceRoot ] 37 | if self.CppOutputPath: 38 | self.CommandLine += [ "/CppOutputPath" + self.CppOutputPath ] 39 | 40 | 41 | class ShaderCompileNode(FXCompileNode): 42 | 43 | def __init__(self, path, profile, path_postfix="", defines=[], entry_point=None): 44 | 45 | super().__init__(path, profile, path_postfix, defines, entry_point) 46 | 47 | def Build(self, env): 48 | 49 | # Node entry point takes precedence over config specified entry-point 50 | entry_point = self.EntryPoint 51 | if entry_point == None: 52 | entry_point = env.CurrentConfig.ShaderCompileOptions.EntryPoint 53 | 54 | # Build command line 55 | cmdline = [ os.path.join(ShaderCompilerPath, "ShaderCompiler.exe") ] 56 | cmdline += [ '/T' + self.Profile ] 57 | cmdline += env.CurrentConfig.ShaderCompileOptions.CommandLine 58 | cmdline += self.DefineCmdLine 59 | cmdline += self.BuildCommandLine 60 | if entry_point: 61 | cmdline += [ '/E' + entry_point ] 62 | cmdline += [ "/ShowCppOutputs" ] 63 | if ShowTrace: 64 | cmdline += [ "/trace" ] 65 | cmdline += [ self.Path ] 66 | Utils.ShowCmdLine(env, cmdline) 67 | 68 | # Create the include scanner and launch the compiler 69 | scanner = Utils.LineScanner(env) 70 | scanner.AddLineParser("Includes", "cpp: included", None, lambda line, length: line.lstrip()[15:-1]) 71 | scanner.AddLineParser("Outputs", "cpp: output", None, lambda line, length: line.lstrip()[12:]) 72 | process = Process.OpenPiped(cmdline, env.EnvironmentVariables) 73 | Process.WaitForPipeOutput(process, scanner) 74 | 75 | # Record the implicit dependencies/outputs for this file 76 | data = env.GetFileMetadata(self.GetInputFile(env)) 77 | data.SetImplicitDeps(env, scanner.Includes) 78 | data.SetImplicitOutputs(env, scanner.Outputs) 79 | 80 | return process.returncode == 0 81 | 82 | def GetOutputFiles(self, env): 83 | 84 | return super()._GetOutputFiles(env, env.CurrentConfig.ShaderCompileOptions) 85 | 86 | def GetTempOutputFiles(self, env): 87 | 88 | return self.GetOutputFiles(env) 89 | -------------------------------------------------------------------------------- /Python/Utils.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # --- MIT Open Source License -------------------------------------------------- 4 | # PiB - Python Build System 5 | # Copyright (C) 2011 by Don Williamson 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # ------------------------------------------------------------------------------ 25 | # 26 | # Utils.py: Some shared utility functions. 27 | # 28 | 29 | import os 30 | import sys 31 | import errno 32 | import fnmatch 33 | import shutil 34 | import re 35 | import glob 36 | 37 | 38 | # 39 | # Used to determine whether ANSI colour codes can be used. 40 | # A *seriously* hacky means of determining if this is being run through the bash shell... 41 | # 42 | import subprocess 43 | try: 44 | subprocess.check_output('ls') 45 | RunningFromBash = True 46 | except: 47 | RunningFromBash = False 48 | 49 | 50 | # 51 | # Create an enumeration type by assigning each value: 52 | # Type = enum(ONE=1, TWO=2, THREE='three') 53 | # 54 | def enum(**enums): 55 | return type('Enum', (), enums) 56 | 57 | 58 | # 59 | # Create an enumeration type with each value uniquely assigned: 60 | # Type = enum('ONE', 'TWO', 'THREE') 61 | # 62 | def enum(*sequential, **named): 63 | enums = dict(zip(sequential, range(len(sequential))), **named) 64 | return type('Enum', (), enums) 65 | 66 | 67 | def NormalisePath(path): 68 | 69 | path = os.path.normpath(path) 70 | path = os.path.normcase(path) 71 | return path 72 | 73 | 74 | # 75 | # Given the filename of a file that exists ask the OS how it references it. This only 76 | # matters on Windows where filenames are case-insensitive but Windows preserves case 77 | # in its directory listing. 78 | # 79 | def GetOSFilename(path): 80 | r = glob.glob(re.sub(r'([^:/\\])(?=[/\\]|$)', r'[\1]', path)) 81 | return r and r[0] or path 82 | 83 | 84 | # 85 | # Quick shortcut for finding out if a python class type contains a callable method 86 | # 87 | def ObjectHasMethod(object, method): 88 | 89 | # New-style classes: does the type contain the method? 90 | t = type(object) 91 | if method not in t.__dict__: 92 | return False 93 | 94 | # It's a method only if it's callable 95 | return callable(t.__dict__[method]) 96 | 97 | 98 | # 99 | # Instead of checking for the existence of a file before removing it, this will remove 100 | # the file and react to any thrown exceptions instead. This requires one less call into 101 | # the file system. 102 | # 103 | # Returns True if the file was removed. 104 | # 105 | def RemoveFile(filename): 106 | 107 | try: 108 | os.remove(filename) 109 | return True 110 | except OSError as exc: 111 | return False 112 | 113 | 114 | # 115 | # Copies files, returning True/False for whether the operation succeeded. 116 | # 117 | def CopyFile(source, dest): 118 | 119 | try: 120 | shutil.copyfile(source, dest) 121 | return True 122 | except IOError as exc: 123 | return False 124 | 125 | 126 | # 127 | # Instead of checking for existence of a path before creating it, this will try to 128 | # create the path and react to any thrown exceptions instead. This requires one less 129 | # call into the file system. 130 | # 131 | def Makedirs(path): 132 | 133 | try: 134 | os.makedirs(path) 135 | except OSError as exc: 136 | if exc.errno != errno.EEXIST: 137 | return False 138 | 139 | return True 140 | 141 | 142 | def Glob(path, pattern): 143 | 144 | matches = [ ] 145 | for root, dirnames, filenames in os.walk(path): 146 | for filename in fnmatch.filter(filenames, pattern): 147 | matches.append(os.path.join(root, filename)) 148 | return matches 149 | 150 | 151 | # 152 | # Searches the command-line for the given argument, returning the value passed 153 | # after that argument if it exists. Can return a specified default value if 154 | # the argument wasn't found. 155 | # 156 | def GetSysArgvProperty(name, default=None, index=0): 157 | 158 | nb_args = len(sys.argv) 159 | for i in range(nb_args): 160 | arg = sys.argv[i] 161 | if arg == name: 162 | if i < nb_args - 1 and index == 0: 163 | return sys.argv[i + 1] 164 | index -= 1 165 | 166 | return default 167 | 168 | 169 | # 170 | # Searches the command-line for the given argument that is repeated, 171 | # returning the values passed as a list. 172 | # 173 | def GetSysArgvProperties(name, default=None): 174 | 175 | props = [ ] 176 | index = 0 177 | 178 | while True: 179 | prop = GetSysArgvProperty(name, default, index) 180 | if prop == default: 181 | break 182 | props += [ prop ] 183 | index += 1 184 | 185 | return props 186 | 187 | 188 | class LineParser: 189 | 190 | def __init__(self, output_name, prefix, ignore_prefixes, parser): 191 | 192 | self.OutputName = output_name 193 | self.Prefix = prefix 194 | self.IgnorePrefixes = ignore_prefixes 195 | self.Parser = parser 196 | 197 | def IgnoreLine(self, line): 198 | 199 | if self.IgnorePrefixes: 200 | for prefix in self.IgnorePrefixes: 201 | if line.startswith(prefix): 202 | return True 203 | 204 | return False 205 | 206 | 207 | PrintFileRegex = re.compile(r"(\w:[/\\])?([/\\]?[\w\.\.])+(\.\w+)") 208 | def Print(env, line): 209 | 210 | if env.NoToolOutput: 211 | return 212 | 213 | if line == "": 214 | return 215 | 216 | if RunningFromBash: 217 | 218 | # https://bluesock.org/~willkg/dev/ansi.html 219 | Black = "\033[30m" 220 | Orange = "\033[38;2;255;165;0m" 221 | Red = "\033[31m" 222 | Cyan = "\033[36m" 223 | End = "\033[0m" 224 | Bold = "\033[1m" 225 | 226 | # Scan for filenames 227 | matches = PrintFileRegex.finditer(line) 228 | if matches: 229 | for match in matches: 230 | filename = match.group(0) 231 | 232 | # Colour filenames 233 | line = line.replace(filename, f"{Cyan}" + filename + f"{End}") 234 | 235 | # Replace filenames with correct case as reported by the OS. This is to stop VSCode opening 236 | # multiple copies of the same file when you click on the output. 237 | # Bug: https://github.com/Microsoft/vscode/issues/12448 238 | if os.path.exists(filename): 239 | os_filename = GetOSFilename(filename) 240 | line = line.replace(filename, os_filename) 241 | 242 | # Colour keywords 243 | line = line.replace("error", f"{Bold}{Red}error{End}") 244 | line = line.replace("ERROR", f"{Bold}{Red}ERROR{End}") 245 | line = line.replace("warning", f"{Orange}warning{End}") 246 | line = line.replace("WARNING", f"{Orange}WARNING{End}") 247 | 248 | print(line) 249 | 250 | 251 | # 252 | # This reads each line of output from a compiler and decides whether to print it or not. 253 | # If the line reports what file is being included by the .c/.cpp file then it's not printed 254 | # and instead stored locally so that it can report all the files included. 255 | # 256 | class LineScanner(): 257 | 258 | def __init__(self, env): 259 | 260 | self.Env = env 261 | self.LineParsers = [] 262 | 263 | def AddLineParser(self, output_name, prefix, ignore_prefixes, parser): 264 | 265 | # Add to the list and create an output field in the class instance 266 | self.LineParsers.append(LineParser(output_name, prefix, ignore_prefixes, parser)) 267 | setattr(self, output_name, set()) 268 | 269 | def __call__(self, line): 270 | 271 | if line == "": 272 | return 273 | 274 | # Strip newline/whitespace 275 | line = line.strip("\r\n") 276 | line = line.lstrip() 277 | 278 | # Check each parser 279 | print_line = True 280 | for line_parser in self.LineParsers: 281 | 282 | # Prioritise checking for ignored lines 283 | if line_parser.IgnoreLine(line): 284 | print_line = False 285 | break 286 | 287 | # Scan for included files and add to the list 288 | if line.startswith(line_parser.Prefix): 289 | path = line_parser.Parser(line, len(line_parser.Prefix)) 290 | path = NormalisePath(path) 291 | getattr(self, line_parser.OutputName).add(path) 292 | print_line = False 293 | 294 | # If no parsers have filtered the line, print it 295 | if print_line: 296 | Print(self.Env, line) 297 | 298 | return False 299 | 300 | 301 | def ShowCmdLine(env, cmdline): 302 | 303 | if env.ShowCmdLine: 304 | print(cmdline) 305 | 306 | for cmd in cmdline: 307 | print(cmd, end=" ") 308 | print("") 309 | 310 | 311 | def ExecPibfile(pibfile, global_symbols = { }): 312 | 313 | # Load the build script file 314 | if not os.path.exists(pibfile): 315 | print("ERROR: No '" + pibfile + "' found") 316 | sys.exit(1) 317 | code = None 318 | with open(pibfile) as f: 319 | code = f.read() 320 | 321 | # Switch to the directory of the pibfile 322 | cur_dir = os.getcwd() 323 | pibfile_dir = os.path.dirname(pibfile) 324 | if pibfile_dir != "": 325 | os.chdir(pibfile_dir) 326 | 327 | global_symbols["__file__"] = os.path.realpath(pibfile) 328 | 329 | # Compile the environment initialisation code 330 | prologue = """ 331 | from BuildSystem import * 332 | from Environment import * 333 | from Utils import * 334 | from CppLanguage import * 335 | 336 | env = Environment.New() 337 | if env == None: 338 | sys.exit(1) 339 | """ 340 | prologue_compiled = compile(prologue, "", "exec") 341 | 342 | # Compile the shutdown code 343 | epilogue = """ 344 | env.SaveFileMetadata() 345 | """ 346 | epilogue_compiled = compile(epilogue, "", "exec") 347 | 348 | # Compile the user code 349 | code_compiled = compile(code, pibfile, "exec") 350 | 351 | # Execute the compiled code in an isolated namespace 352 | exec(prologue_compiled, global_symbols) 353 | exec(code_compiled, global_symbols) 354 | exec(epilogue_compiled, global_symbols) 355 | 356 | # Restore initial directory 357 | os.chdir(cur_dir) 358 | -------------------------------------------------------------------------------- /Python/Wave.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Build Node for Boost.Wave Preprocessor Command-line Driver 4 | # http://www.boost.org/doc/libs/1_55_0/libs/wave/doc/wave_driver.html 5 | # 6 | 7 | 8 | import os 9 | import string 10 | import Utils 11 | import Process 12 | import BuildSystem 13 | 14 | 15 | # Directory where the Wave Driver executable is located 16 | _InstallPath = None 17 | 18 | def SetInstallPath(location): 19 | global _InstallPath 20 | _InstallPath = location 21 | 22 | 23 | class Options: 24 | 25 | # 26 | # TODO: Uses new dirty options checking mechanism. 27 | # 28 | def __init__(self): 29 | 30 | # List of normal/system include search paths 31 | self.IncludePaths = [ ] 32 | self.SystemIncludePaths = [ ] 33 | 34 | # List of macros to define/undefine for preprocessor 35 | self.DefineMacros = [ ] 36 | self.UndefineMacros = [ ] 37 | 38 | self.LongLong = True 39 | self.Variadics = True 40 | self.C99 = False 41 | self.Cpp11 = False 42 | 43 | self.Dirty = True 44 | self.CommandLine = [ ] 45 | 46 | def __setattr__(self, name, value): 47 | 48 | # Assign the field and mark the command-line as dirty 49 | self.__dict__[name] = value 50 | self.__dict__["Dirty"] = True 51 | 52 | def UpdateCommandLine(self): 53 | 54 | if self.Dirty: 55 | 56 | cmdline = [ ] 57 | cmdline += [ ('--include="' + os.path.normpath(path) + '"') for path in self.IncludePaths ] 58 | cmdline += [ ('--sysinclude="' + os.path.normpath(path) + '"') for path in self.SystemIncludePaths ] 59 | cmdline += [ '--undefine=' + macro for macro in self.UndefineMacros ] 60 | 61 | self.FormatDefines(cmdline) 62 | 63 | if self.LongLong: cmdline += [ "--long_long" ] 64 | if self.Variadics: cmdline += [ "--variadics" ] 65 | if self.C99: cmdline += [ "--c99" ] 66 | if self.Cpp11: cmdline += [ "--c++11" ] 67 | 68 | # Update and mark as not dirty without calling into __setattr__ 69 | self.__dict__["CommandLine"] = cmdline 70 | self.__dict__["Dirty"] = False 71 | 72 | def FormatDefines(self, cmdline): 73 | 74 | for define in self.DefineMacros: 75 | if isinstance(define, str): 76 | cmdline += [ '-D ' + define ] 77 | else: 78 | cmdline += [ '-D ' + str(define[0]) + "=" + str(define[1]) ] 79 | 80 | 81 | 82 | class BuildNode (BuildSystem.Node): 83 | 84 | # 85 | # TODO: Uses new options location system of passing a map from config name to options 86 | # that are referenced in Build. Means nothing specific to this build node need to 87 | # be stored in the config object. 88 | # 89 | def __init__(self, source, options_map, extension): 90 | 91 | super().__init__() 92 | self.Source = source 93 | self.Dependencies = [ source ] 94 | self.OptionsMap = options_map 95 | self.Extension = extension 96 | 97 | def Build(self, env): 98 | 99 | # Ensure command -line for current configuration is up-to-date 100 | options = self.OptionsMap[env.CurrentConfig.CmdLineArg] 101 | options.UpdateCommandLine() 102 | 103 | # Augment command-line with current environment 104 | cmdline = [ os.path.join(_InstallPath, "wave.exe") ] 105 | cmdline += self.OptionsMap[env.CurrentConfig.CmdLineArg].CommandLine 106 | cmdline += [ '--output=' + self.GetOutputFiles(env)[0] ] 107 | cmdline += [ '--listincludes=-' ] 108 | cmdline += [ self.GetInputFile(env) ] 109 | Utils.ShowCmdLine(env, cmdline) 110 | 111 | # Launch Wave with a dependency scanner and wait for it to finish 112 | scanner = Utils.LineScanner(env) 113 | scanner.AddLineParser("Includes", '"', [ "<" ], lambda line, length: line.split("(")[1].rstrip()[:-1]) 114 | process = Process.OpenPiped(cmdline, env.EnvironmentVariables) 115 | Process.WaitForPipeOutput(process, scanner) 116 | 117 | # Record the implicit dependencies for this file 118 | data = env.GetFileMetadata(self.GetInputFile(env)) 119 | data.SetImplicitDeps(env, scanner.Includes) 120 | 121 | return process.returncode == 0 122 | 123 | def GetInputFile(self, env): 124 | 125 | path = self.Source.GetOutputFiles(env)[0] 126 | return path 127 | 128 | def GetOutputFiles(self, env): 129 | 130 | # Get the relocated path minus extension 131 | path = os.path.splitext(self.GetInputFile(env))[0] 132 | path = os.path.join(env.CurrentConfig.IntermediatePath, path) 133 | return [ path + "." + self.Extension ] 134 | 135 | def GetTempOutputFiles(self, env): 136 | 137 | return self.GetOutputFiles(env) 138 | -------------------------------------------------------------------------------- /Python/WindowsPlatform.py: -------------------------------------------------------------------------------- 1 | 2 | import winreg 3 | import os 4 | import string 5 | 6 | 7 | # 8 | # Big list of known SDK versions and their properties 9 | # 10 | # I wrote some automatic discovery code for building all this (300 lines) but it turns 11 | # out that Windows SDK versions differ enough such that there is no discernible 12 | # pattern to quickly and easily figure what goes where. 13 | # 14 | # Examples: 15 | # 16 | # * ProductVersion is inconsistent in the registry and not monotonic. 17 | # * Identifying "latest" SDK thus requires parsing their registry names. 18 | # * Between 7.1 and 8.x the root "include" directory is no longer used and split into "shared" and "um". 19 | # * Between 7.1 and 8.x the relative lib paths between x86 and x64 change. 20 | # * This "bin" directory has the same problem. 21 | # * Each of 7.1, 8.0 and 8.1 have different root "lib" directories with no pattern. 22 | # * There is no guarantee that the version names will maintain their pattern. 23 | # 24 | # The logic became convoluted and hard to decipher. Much worse; Microsoft clearly 25 | # have no interest in maintaining installation patterns between versions* so the 26 | # code would only have gotten worse with each new SDK. 27 | # 28 | # Instead, just list everything known and see what is where. Code to parse it 29 | # is simple and easy to understand. Adding exceptions for what we haven't seen before 30 | # is just as easy. 31 | # 32 | # * when you also write the build system, would you? 33 | # 34 | # Don't be afraid to write the stupid code. Don't be afraid to throw away the clever code once written 35 | # 36 | SDKVersions = [ 37 | { 38 | "version" : "7.1", 39 | "install" : "Microsoft SDKs\\Windows\\v7.1A", 40 | "includes" : [ "include" ], 41 | "lib32" : "lib", 42 | "lib64" : "lib\\x64", 43 | "bin32" : "bin", 44 | "bin64" : "bin\\x64" 45 | }, 46 | #{ 47 | # "version" : "8.0", 48 | # "install" : "Windows Kits\\8.0", 49 | # "includes" : [ "include\\shared", "include\\um" ], 50 | # "lib32" : "lib\\win8\\um\\x86", 51 | # "lib64" : "lib\\win8\\um\\x64", 52 | # "bin32" : "bin\\x86", 53 | # "bin64" : "bin\\x64" 54 | #}, 55 | { 56 | "version" : "8.1", 57 | "install" : "Windows Kits\\8.1", 58 | "includes" : [ "include\\shared", "include\\um" ], 59 | "lib32" : "lib\\winv6.3\\um\\x86", 60 | "lib64" : "lib\\winv6.3\\um\\x64", 61 | "bin32" : "bin\\x86", 62 | "bin64" : "bin\\x64" 63 | }, 64 | { 65 | "version" : "10", 66 | "install" : "Windows Kits\\10", 67 | "includes" : [ "include\\10.0.18362.0\\shared", "include\\10.0.18362.0\\um" ], 68 | "lib32" : "lib\\10.0.18362.0\\um\\x86", 69 | "lib64" : "lib\\10.0.18362.0\\um\\x64", 70 | "bin32" : "bin\\10.0.18362.0\\x86", 71 | "bin64" : "bin\\10.0.18362.0\\x64", 72 | }, 73 | { 74 | "version" : "10", 75 | "install" : "Windows Kits\\10", 76 | "includes" : [ "include\\10.0.16299.0\\shared", "include\\10.0.16299.0\\um" ], 77 | "lib32" : "lib\\10.0.16299.0\\um\\x86", 78 | "lib64" : "lib\\10.0.16299.0\\um\\x64", 79 | "bin32" : "bin\\10.0.16299.0\\x86", 80 | "bin64" : "bin\\10.0.16299.0\\x64", 81 | }, 82 | ] 83 | 84 | 85 | # Locate program files 86 | ProgramFilesx86 = os.getenv("ProgramFiles(x86)") 87 | if ProgramFilesx86 == None: 88 | print("ERROR: Couldn't locate Program Files (x86) directory") 89 | ProgramFilesx86 = "" 90 | 91 | 92 | SDKDir = None 93 | IncludeDirs = [ ] 94 | x86LibDir = None 95 | x64LibDir = None 96 | x86BinDir = None 97 | x64BinDir = None 98 | 99 | 100 | # Search SDK version list backwards from most recent 101 | for sdk in reversed(SDKVersions): 102 | 103 | # Is this one installed? 104 | sdk_dir = os.path.join(ProgramFilesx86, sdk["install"]) 105 | if not os.path.exists(sdk_dir): 106 | continue 107 | 108 | # Get list of include paths that exist 109 | includes = sdk["includes"] 110 | includes = [ os.path.join(sdk_dir, include) for include in includes ] 111 | includes = [ include for include in includes if os.path.exists(include) ] 112 | if len(includes) == 0: 113 | continue 114 | 115 | # Get lib directories 116 | lib32 = os.path.join(sdk_dir, sdk["lib32"]) 117 | if not os.path.exists(lib32): 118 | continue 119 | lib64 = os.path.join(sdk_dir, sdk["lib64"]) 120 | if not os.path.exists(lib64): 121 | continue 122 | 123 | # Get bin directories 124 | bin32 = os.path.join(sdk_dir, sdk["bin32"]) 125 | if not os.path.exists(bin32): 126 | continue 127 | bin64 = os.path.join(sdk_dir, sdk["bin64"]) 128 | if not os.path.exists(bin64): 129 | continue 130 | 131 | # Passed all tests, assign this as the installed SDK 132 | SDKDir = sdk_dir 133 | IncludeDirs = includes 134 | x86LibDir = lib32 135 | x64LibDir = lib64 136 | x86BinDir = bin32 137 | x64BinDir = bin64 138 | break 139 | -------------------------------------------------------------------------------- /Python/clReflect.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import Utils 4 | import Process 5 | import BuildSystem 6 | 7 | 8 | # Directory where the clReflect executables are located 9 | _InstallLocation = None 10 | 11 | 12 | def _MakePath(filename): 13 | 14 | if _InstallLocation: 15 | return os.path.join(_InstallLocation, filename) 16 | return filename 17 | 18 | 19 | class CppExportNode(BuildSystem.Node): 20 | 21 | def __init__(self, path, input, map_file): 22 | 23 | super().__init__() 24 | self.Path = path 25 | self.Input = input 26 | self.MapFile = map_file 27 | self.Dependencies = [ input, map_file ] 28 | 29 | def Build(self, env): 30 | 31 | input_file = self.GetInputFile(env) 32 | output_file = self.GetOutputFiles(env)[0] 33 | Utils.Print(env, "clexport: " + os.path.basename(output_file)) 34 | 35 | # Construct the command-line 36 | cmdline = [ _MakePath("clexport.exe") ] 37 | cmdline += [ input_file ] 38 | cmdline += [ "-cpp", output_file ] 39 | cmdline += [ "-cpp_log", output_file + ".log" ] 40 | if self.MapFile != None: 41 | cmdline += [ "-map", self.MapFile.GetOutputFiles(env)[0] ] 42 | Utils.ShowCmdLine(env, cmdline) 43 | 44 | # Launch the exporter and wait for it to finish 45 | process = Process.OpenPiped(cmdline) 46 | output = Process.WaitForPipeOutput(process) 47 | if not env.NoToolOutput: 48 | Utils.Print(env, output) 49 | 50 | return process.returncode == 0 51 | 52 | def GetInputFile(self, env): 53 | 54 | return self.Input.GetOutputFiles(env)[0] 55 | 56 | def GetOutputFiles(self, env): 57 | 58 | path = os.path.join(env.CurrentConfig.OutputPath, self.Path) 59 | return [ path ] 60 | 61 | def GetTempOutputFiles(self, env): 62 | 63 | return self.GetOutputFiles(env) 64 | 65 | 66 | class MergeNode (BuildSystem.Node): 67 | 68 | def __init__(self, path, db_files, cpp_codegen, h_codegen): 69 | 70 | super().__init__() 71 | self.Path = path 72 | self.Dependencies = db_files 73 | self.CppCodeGen = cpp_codegen 74 | self.HCodeGen = h_codegen 75 | 76 | def Build(self, env): 77 | 78 | output_file = self.GetOutputFiles(env)[0] 79 | Utils.Print(env, "clmerge: " + os.path.basename(output_file)) 80 | 81 | # Construct the command-line 82 | cmdline = [ _MakePath("clmerge.exe") ] 83 | cmdline += [ output_file ] 84 | if self.CppCodeGen != None: 85 | cmdline += [ "-cpp_codegen", self.CppCodeGen.GetInputFile(env) ] 86 | if self.HCodeGen != None: 87 | cmdline += [ "-h_codegen", self.HCodeGen.GetInputFile(env) ] 88 | cmdline += [ file.GetOutputFiles(env)[0] for file in self.Dependencies ] 89 | Utils.ShowCmdLine(env, cmdline) 90 | 91 | # Launch the merger and wait for it to finish 92 | process = Process.OpenPiped(cmdline) 93 | output = Process.WaitForPipeOutput(process) 94 | if not env.NoToolOutput: 95 | Utils.Print(env, output) 96 | 97 | return process.returncode == 0 98 | 99 | def GetInputFile(self, env): 100 | 101 | path = os.path.join(env.CurrentConfig.IntermediatePath, self.Path) 102 | return path 103 | 104 | def GetOutputFiles(self, env): 105 | 106 | output_files = [ os.path.join(env.CurrentConfig.IntermediatePath, self.Path) ] 107 | if self.CppCodeGen != None: 108 | output_files += [ self.CppCodeGen.GetInputFile(env) ] 109 | return output_files 110 | 111 | def GetTempOutputFiles(self, env): 112 | 113 | # Exclude the output C++ file as we don't want that to be deleted 114 | temp_files = self.GetOutputFiles(env)[:1] 115 | return temp_files 116 | 117 | 118 | class CppScanNode (BuildSystem.Node): 119 | 120 | def __init__(self, sys_include_paths, include_paths, defines, cpp_output): 121 | 122 | super().__init__() 123 | self.SysIncludePaths = sys_include_paths 124 | self.IncludePaths = include_paths 125 | self.CppOutput = cpp_output 126 | self.Defines = defines 127 | self.Dependencies = [ cpp_output ] 128 | 129 | def Build(self, env): 130 | 131 | input_file = self.GetInputFile(env) 132 | output_files = self.GetOutputFiles(env) 133 | Utils.Print(env, "clscan: " + Utils.GetOSFilename(os.path.basename(input_file))) 134 | 135 | # Construct the command-line 136 | cmdline = [ _MakePath("clscan.exe") ] 137 | cmdline += [ input_file ] 138 | cmdline += [ "--output", output_files[0] ] 139 | cmdline += [ "--ast_log", output_files[1] ] 140 | cmdline += [ "--spec_log", output_files[2] ] 141 | cmdline += [ "--" ] 142 | cmdline += [ "-fdiagnostics-format=msvc" ] 143 | cmdline += [ "-D__clcpp_parse__" ] 144 | cmdline += [ "-m32" ] 145 | cmdline += [ "-fms-extensions" ] 146 | cmdline += [ "-fms-compatibility" ] 147 | cmdline += [ "-mms-bitfields" ] 148 | cmdline += [ "-fdelayed-template-parsing" ] 149 | cmdline += [ "-std=c++17" ] 150 | cmdline += [ "-fno-rtti" ] 151 | cmdline += [ "-Wno-microsoft-enum-forward-reference" ] 152 | for path in self.SysIncludePaths: 153 | cmdline += [ "-isystem", path ] 154 | for path in self.IncludePaths: 155 | cmdline += [ "-I", path ] 156 | for define in self.Defines: 157 | cmdline += [ "-D", define ] 158 | Utils.ShowCmdLine(env, cmdline) 159 | 160 | # Launch the scanner and wait for it to finish 161 | output = Utils.LineScanner(env) 162 | output.AddLineParser("Includes", "Included:", None, lambda line, length: line[length:].lstrip()) 163 | process = Process.OpenPiped(cmdline) 164 | Process.WaitForPipeOutput(process, output) 165 | 166 | return process.returncode == 0 167 | 168 | def GetInputFile(self, env): 169 | 170 | return self.CppOutput.GetInputFile(env) 171 | 172 | def GetOutputFiles(self, env): 173 | 174 | path = os.path.splitext(self.GetInputFile(env))[0] 175 | path = os.path.join(env.CurrentConfig.IntermediatePath, path) 176 | return [ path + ".csv", path + "_astlog.txt", path + "_speclog.txt" ] 177 | 178 | def GetTempOutputFiles(self, env): 179 | 180 | return self.GetOutputFiles(env) 181 | 182 | 183 | def CppScan(sys_include_paths, include_paths, defines, cpp_output): 184 | return CppScanNode(sys_include_paths, include_paths, defines, cpp_output) 185 | 186 | def Merge(path, db_files, cpp_codegen, h_codegen): 187 | return MergeNode(path, db_files, cpp_codegen, h_codegen) 188 | 189 | def CppExport(path, input, map_file): 190 | return CppExportNode(path, input, map_file) 191 | 192 | def SetInstallLocation(location): 193 | global _InstallLocation 194 | _InstallLocation = location -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | PiB v0.2 - Python Build System 3 | ------------------------------ 4 | 5 | Project Homepage: https://github.com/dwilliamson/b/blob/master/Static/PiB.txt (archived) 6 | 7 | For a more in-depth discussion of the origins of this build system, check out the following blog post: 8 | 9 | https://github.com/dwilliamson/b/blob/master/Posts/2011-06-28-17-04-00.txt (archived) 10 | 11 | To install, follow these two steps: 12 | 13 | * Ensure the location of the PiB installation directory is in your PATH. 14 | * Edit PiB.bat and point PYTHONDIR to the python installation you want to use. 15 | 16 | This project will be updated continuously as my needs for it change :) -------------------------------------------------------------------------------- /Test/Code/Main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "MainDepA.h" 3 | #include "MainDepB.h" 4 | #include "MainDepC.h" 5 | 6 | #include 7 | 8 | #define WIN32_LEAN_AND_MEAN 9 | 10 | #define NOVIRTUALKEYCODES //- VK_* 11 | #define NOWINMESSAGES //- WM_*, EM_*, LB_*, CB_* 12 | #define NOWINSTYLES //- WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* 13 | #define NOSYSMETRICS //- SM_* 14 | #define NOMENUS //- MF_* 15 | #define NOICONS //- IDI_* 16 | #define NOKEYSTATES //- MK_* 17 | #define NOSYSCOMMANDS //- SC_* 18 | #define NORASTEROPS //- Binary and Tertiary raster ops 19 | #define NOSHOWWINDOW //- SW_* 20 | #define OEMRESOURCE //- OEM Resource values 21 | #define NOATOM //- Atom Manager routines 22 | #define NOCLIPBOARD //- Clipboard routines 23 | #define NOCOLOR //- Screen colors 24 | #define NOCTLMGR //- Control and Dialog routines 25 | #define NODRAWTEXT //- DrawText() and DT_* 26 | #define NOGDI //- All GDI defines and routines 27 | #define NOKERNEL //- All KERNEL defines and routines 28 | #define NOUSER //- All USER defines and routines 29 | #define NONLS //- All NLS defines and routines 30 | #define NOMB //- MB_* and MessageBox() 31 | #define NOMEMMGR //- GMEM_*, LMEM_*, GHND, LHND, associated routines 32 | #define NOMETAFILE //- typedef METAFILEPICT 33 | #define NOMINMAX //- Macros min(a,b) and max(a,b) 34 | #define NOMSG //- typedef MSG and associated routines 35 | #define NOOPENFILE //- OpenFile(), OemToAnsi, AnsiToOem, and OF_* 36 | #define NOSCROLL //- SB_* and scrolling routines 37 | #define NOSERVICE //- All Service Controller routines, SERVICE_ equates, etc. 38 | #define NOSOUND //- Sound driver routines 39 | #define NOTEXTMETRIC //- typedef TEXTMETRIC and associated routines 40 | #define NOWH //- SetWindowsHook and WH_* 41 | #define NOWINOFFSETS //- GWL_*, GCL_*, associated routines 42 | #define NOCOMM //- COMM driver routines 43 | #define NOKANJI //- Kanji support stuff. 44 | #define NOHELP //- Help engine interface. 45 | #define NOPROFILER //- Profiler interface. 46 | #define NODEFERWINDOWPOS //- DeferWindowPos routines 47 | #define NOMCX //- Modem Configuration Extensions 48 | 49 | #include 50 | 51 | int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int) 52 | { 53 | return 0; 54 | } 55 | 56 | int main() 57 | { 58 | return 0; 59 | } -------------------------------------------------------------------------------- /Test/Code/MainDepA.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SubDepA.h" -------------------------------------------------------------------------------- /Test/Code/MainDepB.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SubDepA.h" -------------------------------------------------------------------------------- /Test/Code/MainDepC.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "MainDepA.h" -------------------------------------------------------------------------------- /Test/Code/Other.cpp: -------------------------------------------------------------------------------- 1 | #include "MainDepB.h" -------------------------------------------------------------------------------- /Test/Code/SubDepA.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | -------------------------------------------------------------------------------- /Test/Code/abs/unk.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Celtoys/PiB/3c3e9a1fcfc96ce3ae1c970f6e2ef36f76c3acaa/Test/Code/abs/unk.cpp -------------------------------------------------------------------------------- /Test/Code/rec/hello/old.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Celtoys/PiB/3c3e9a1fcfc96ce3ae1c970f6e2ef36f76c3acaa/Test/Code/rec/hello/old.cpp -------------------------------------------------------------------------------- /Test/Code/rec/new.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Celtoys/PiB/3c3e9a1fcfc96ce3ae1c970f6e2ef36f76c3acaa/Test/Code/rec/new.cpp -------------------------------------------------------------------------------- /Test/pibfile: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # pibfile: Test build script. 4 | # 5 | # --- MIT Open Source License -------------------------------------------------- 6 | # PiB - Python Build System 7 | # Copyright (C) 2011 by Don Williamson 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy 10 | # of this software and associated documentation files (the "Software"), to deal 11 | # in the Software without restriction, including without limitation the rights 12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | # copies of the Software, and to permit persons to whom the Software is 14 | # furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # ------------------------------------------------------------------------------ 27 | # 28 | 29 | cpp_files = Glob("Code", "*.cpp") 30 | h_files = Glob("Code", "*.h") 31 | 32 | # Compile 33 | obj_files = [ env.CPPFile(file) for file in cpp_files ] 34 | 35 | # Link 36 | exe = env.Link("Out.exe", obj_files) 37 | env.Build(exe) 38 | 39 | # Generate projects 40 | VCGenerateProjectFile(env, "Code/TestProject", cpp_files + h_files, exe) 41 | VCGenerateSolutionFile(env, "TestProject", [ "Code/TestProject" ]) 42 | -------------------------------------------------------------------------------- /Test/purge.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | del *.pib /s 3 | attrib -h * /s 4 | del *.suo /s 5 | del *.ncb /s 6 | del *.sln /s 7 | del *.vcproj /s 8 | del *.vcproj.* /s 9 | rmdir /s /q bin 10 | rmdir /s /q obj -------------------------------------------------------------------------------- /VSMacros/PiBMacros/PiBMacros.vsmacros: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Celtoys/PiB/3c3e9a1fcfc96ce3ae1c970f6e2ef36f76c3acaa/VSMacros/PiBMacros/PiBMacros.vsmacros --------------------------------------------------------------------------------