├── .coveragerc ├── .gitignore ├── .pylintrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.asciidoc ├── appveyor.yml ├── clcache.nuspec ├── clcache.pth ├── clcache ├── __init__.py ├── __main__.py ├── monkey.py ├── server │ ├── __init__.py │ └── __main__.py └── storage.py ├── clcachesrv.py ├── pyinstaller └── clcache_main.py ├── server └── requirements.txt ├── setup.py ├── showprofilereport.py └── tests ├── distutils ├── example.c └── setup.py ├── integrationtests ├── basedir │ ├── constants.h │ └── main.cpp ├── compiler-encoding │ ├── .gitignore │ ├── non-ascii-message-ansi.c │ ├── non-ascii-message-utf16.c │ └── non-ascii-message-utf8.c ├── fibonacci.c ├── fibonacci.cpp ├── header-change │ ├── .gitignore │ └── main.cpp ├── header-miss-obsolete │ ├── .gitignore │ └── main.cpp ├── header-miss │ ├── .gitignore │ └── main.cpp ├── hits-and-misses │ ├── .gitignore │ ├── hit.cpp │ ├── stable-source-transitive-header.cpp │ ├── stable-source-with-alternating-header.cpp │ └── transitive-header.h ├── minimal.cpp ├── mutiple-sources │ ├── .gitignore │ ├── fibonacci01.cpp │ ├── fibonacci02.cpp │ ├── fibonacci03.cpp │ ├── fibonacci04.cpp │ └── fibonacci05.cpp ├── parallel │ ├── .gitignore │ ├── fibonacci01.cpp │ ├── fibonacci02.cpp │ ├── fibonacci03.cpp │ ├── fibonacci04.cpp │ ├── fibonacci05.cpp │ ├── fibonacci06.cpp │ ├── fibonacci07.cpp │ ├── fibonacci08.cpp │ ├── fibonacci09.cpp │ └── fibonacci10.cpp ├── precompiled-headers │ ├── Makefile │ ├── README.txt │ ├── another.h │ ├── applib.cpp │ ├── myapp.cpp │ ├── stable.h │ └── unstable.h ├── recompile1.cpp ├── recompile2.cpp └── recompile3.cpp ├── output └── .gitkeep ├── performancetests └── concurrency │ └── file01.cpp ├── test_integration.py ├── test_performance.py ├── test_unit.py └── unittests ├── broken_json.txt ├── compiler-artifacts-repository └── .gitkeep ├── configuration └── .gitkeep ├── empty_file.txt ├── fi-build-debug └── .gitkeep ├── files-beneath ├── a │ ├── 1.txt │ └── 2.txt ├── b │ └── c │ │ └── 3.txt └── d │ ├── 4.txt │ └── e │ └── 5.txt ├── fo-build-debug └── .gitkeep ├── manifests ├── .gitkeep └── br │ └── brokenmanifest.json ├── parse-includes ├── compiler_output.txt ├── compiler_output_lang_de.txt └── compiler_output_no_includes.txt ├── response-files ├── default_encoded.rsp ├── nested_response_file.rsp ├── nested_response_file_2.rsp └── utf16_encoded.rsp └── statistics └── .gitkeep /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | include = */clcache/* 4 | omit = 5 | tests/* 6 | 7 | # pytest-cov: 8 | # "Note that this plugin controls some options and setting the option in the 9 | # config file will have no effect. These include specifying source to be 10 | # measured (source option) and all data file handling (data_file and parallel 11 | # options)." 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.bak 4 | 5 | # Generated by PyInstaller 6 | *.spec 7 | /build 8 | /dist 9 | 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Add files or directories to the blacklist. They should be base names, not 11 | # paths. 12 | ignore=CVS 13 | 14 | # Pickle collected data for later comparisons. 15 | persistent=yes 16 | 17 | # List of plugins (as comma separated values of python modules names) to load, 18 | # usually to register additional checkers. 19 | load-plugins= 20 | 21 | # Use multiple processes to speed up Pylint. 22 | jobs=1 23 | 24 | # Allow loading of arbitrary C extensions. Extensions are imported into the 25 | # active Python interpreter and may run arbitrary code. 26 | unsafe-load-any-extension=no 27 | 28 | # A comma-separated list of package or module names from where C extensions may 29 | # be loaded. Extensions are loading into the active Python interpreter and may 30 | # run arbitrary code 31 | extension-pkg-whitelist=pyuv 32 | 33 | # Allow optimization of some AST trees. This will activate a peephole AST 34 | # optimizer, which will apply various small optimizations. For instance, it can 35 | # be used to obtain the result of joining multiple strings with the addition 36 | # operator. Joining a lot of strings can lead to a maximum recursion error in 37 | # Pylint and this flag can prevent that. It has one side effect, the resulting 38 | # AST will be different than the one from reality. 39 | optimize-ast=no 40 | 41 | 42 | [MESSAGES CONTROL] 43 | 44 | # Only show warnings with the listed confidence levels. Leave empty to show 45 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 46 | confidence= 47 | 48 | # Enable the message, report, category or checker with the given id(s). You can 49 | # either give multiple identifier separated by comma (,) or put this option 50 | # multiple time (only on the command line, not in the configuration file where 51 | # it should appear only once). See also the "--disable" option for examples. 52 | #enable= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once).You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use"--disable=all --enable=classes 62 | # --disable=W" 63 | disable=old-ne-operator,zip-builtin-not-iterating,filter-builtin-not-iterating,metaclass-assignment,cmp-method,raw_input-builtin,parameter-unpacking,standarderror-builtin,input-builtin,cmp-builtin,no-absolute-import,round-builtin,coerce-method,file-builtin,import-star-module-level,long-suffix,using-cmp-argument,old-division,reduce-builtin,raising-string,dict-view-method,map-builtin-not-iterating,nonzero-method,buffer-builtin,unicode-builtin,setslice-method,old-octal-literal,range-builtin-not-iterating,apply-builtin,useless-suppression,getslice-method,backtick,intern-builtin,unichr-builtin,unpacking-in-except,next-method-called,dict-iter-method,reload-builtin,coerce-builtin,hex-method,basestring-builtin,old-raise-syntax,execfile-builtin,long-builtin,xrange-builtin,print-statement,delslice-method,oct-method,suppressed-message,indexing-exception, 64 | missing-docstring, 65 | unidiomatic-typecheck, 66 | no-else-return, 67 | not-context-manager 68 | 69 | [REPORTS] 70 | 71 | # Set the output format. Available formats are text, parseable, colorized, msvs 72 | # (visual studio) and html. You can also give a reporter class, eg 73 | # mypackage.mymodule.MyReporterClass. 74 | output-format=text 75 | 76 | # Put messages in a separate file for each module / package specified on the 77 | # command line instead of printing them on stdout. Reports (if any) will be 78 | # written in a file name "pylint_global.[txt|html]". 79 | files-output=no 80 | 81 | # Tells whether to display a full report or only the messages 82 | reports=no 83 | 84 | # Python expression which should return a note less than 10 (10 is the highest 85 | # note). You have access to the variables errors warning, statement which 86 | # respectively contain the number of errors / warnings messages and the total 87 | # number of statements analyzed. This is used by the global evaluation report 88 | # (RP0004). 89 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 90 | 91 | # Template used to display messages. This is a python new-style format string 92 | # used to format the message information. See doc for all details 93 | #msg-template= 94 | 95 | 96 | [BASIC] 97 | 98 | # List of builtins function names that should not be used, separated by a comma 99 | bad-functions=map,filter 100 | 101 | # Good variable names which should always be accepted, separated by a comma 102 | good-names=i,j,k,ex,Run,_ 103 | 104 | # Bad variable names which should always be refused, separated by a comma 105 | bad-names=foo,bar,baz,toto,tutu,tata 106 | 107 | # Colon-delimited sets of names that determine each other's naming style when 108 | # the name regexes allow several styles. 109 | name-group= 110 | 111 | # Include a hint for the correct naming format with invalid-name 112 | include-naming-hint=yes 113 | 114 | # 115 | # clcache camelCasePattern: ([a-z]+[0-9]*[A-Z]?){1,6}$ 116 | # methods and attributes get an additional _? prefix 117 | # 118 | 119 | # Regular expression matching correct attribute names 120 | attr-rgx =_?([a-z]+[0-9]*[A-Z]?){1,6}$ 121 | attr-name-hint=_?([a-z]+[0-9]*[A-Z]?){1,6}$ 122 | 123 | # Regular expression matching correct class names 124 | class-rgx =[A-Z_][a-zA-Z0-9]+$ 125 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 126 | 127 | # Regular expression matching correct argument names 128 | argument-rgx =([a-z]+[0-9]*[A-Z]?){1,6}$ 129 | argument-name-hint=([a-z]+[0-9]*[A-Z]?){1,6}$ 130 | 131 | # Regular expression matching correct function names 132 | function-rgx =(([a-z]+[0-9]*[A-Z]?){1,6}|(__[a-z]+__))$ 133 | function-name-hint=(([a-z]+[0-9]*[A-Z]?){1,6}|(__[a-z]+__))$ 134 | 135 | # Regular expression matching correct constant names 136 | const-rgx =(([A-Z_][A-Z0-9_]*)|(__.*__))$ 137 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 138 | 139 | # Regular expression matching correct method names 140 | method-rgx =(_?([a-z]+[0-9]*[A-Z]?){1,6}$|(__[a-z]+__))$ 141 | method-name-hint=(_?([a-z]+[0-9]*[A-Z]?){1,6}$|(__[a-z]+__))$ 142 | 143 | # Regular expression matching correct module names 144 | module-rgx =(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 145 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 146 | 147 | # Regular expression matching correct inline iteration names 148 | inlinevar-rgx =([a-z]+[0-9]*[A-Z]?){1,6}$ 149 | inlinevar-name-hint=([a-z]+[0-9]*[A-Z]?){1,6}$ 150 | 151 | # Regular expression matching correct variable names 152 | variable-rgx =([a-z]+[0-9]*[A-Z]?){1,6}$ 153 | variable-name-hint=([a-z]+[0-9]*[A-Z]?){1,6}$ 154 | 155 | # Regular expression matching correct class attribute names 156 | class-attribute-rgx =([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$ 157 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$ 158 | 159 | # Regular expression which should only match function or class names that do 160 | # not require a docstring. 161 | no-docstring-rgx =^_ 162 | no-docstring-name-hint=^_ 163 | 164 | # Minimum line length for functions/classes that require docstrings, shorter 165 | # ones are exempt. 166 | docstring-min-length=-1 167 | 168 | 169 | [ELIF] 170 | 171 | # Maximum number of nested blocks for function / method body 172 | max-nested-blocks=5 173 | 174 | 175 | [FORMAT] 176 | 177 | # Maximum number of characters on a single line. 178 | max-line-length=120 179 | 180 | # Regexp for a line that is allowed to be longer than the limit. 181 | ignore-long-lines=^\s*(# )??$ 182 | 183 | # Allow the body of an if to be on the same line as the test if there is no 184 | # else. 185 | single-line-if-stmt=no 186 | 187 | # List of optional constructs for which whitespace checking is disabled. `dict- 188 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 189 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 190 | # `empty-line` allows space-only lines. 191 | no-space-check=trailing-comma,dict-separator 192 | 193 | # Maximum number of lines in a module 194 | max-module-lines=2000 195 | 196 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 197 | # tab). 198 | indent-string=' ' 199 | 200 | # Number of spaces of indent required inside a hanging or continued line. 201 | indent-after-paren=4 202 | 203 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 204 | expected-line-ending-format= 205 | 206 | 207 | [LOGGING] 208 | 209 | # Logging modules to check that the string format arguments are in logging 210 | # function parameter format 211 | logging-modules=logging 212 | 213 | 214 | [MISCELLANEOUS] 215 | 216 | # List of note tags to take in consideration, separated by a comma. 217 | notes=FIXME,XXX,TODO 218 | 219 | 220 | [SIMILARITIES] 221 | 222 | # Minimum lines number of a similarity. 223 | min-similarity-lines=4 224 | 225 | # Ignore comments when computing similarities. 226 | ignore-comments=yes 227 | 228 | # Ignore docstrings when computing similarities. 229 | ignore-docstrings=yes 230 | 231 | # Ignore imports when computing similarities. 232 | ignore-imports=no 233 | 234 | 235 | [SPELLING] 236 | 237 | # Spelling dictionary name. Available dictionaries: none. To make it working 238 | # install python-enchant package. 239 | spelling-dict= 240 | 241 | # List of comma separated words that should not be checked. 242 | spelling-ignore-words= 243 | 244 | # A path to a file that contains private dictionary; one word per line. 245 | spelling-private-dict-file= 246 | 247 | # Tells whether to store unknown words to indicated private dictionary in 248 | # --spelling-private-dict-file option instead of raising a message. 249 | spelling-store-unknown-words=no 250 | 251 | 252 | [TYPECHECK] 253 | 254 | # Tells whether missing members accessed in mixin class should be ignored. A 255 | # mixin class is detected if its name ends with "mixin" (case insensitive). 256 | ignore-mixin-members=yes 257 | 258 | # List of module names for which member attributes should not be checked 259 | # (useful for modules/projects where namespaces are manipulated during runtime 260 | # and thus existing member attributes cannot be deduced by static analysis. It 261 | # supports qualified module names, as well as Unix pattern matching. 262 | ignored-modules= 263 | 264 | # List of classes names for which member attributes should not be checked 265 | # (useful for classes with attributes dynamically set). This supports can work 266 | # with qualified names. 267 | ignored-classes= 268 | 269 | # List of members which are set dynamically and missed by pylint inference 270 | # system, and so shouldn't trigger E1101 when accessed. Python regular 271 | # expressions are accepted. 272 | generated-members= 273 | 274 | 275 | [VARIABLES] 276 | 277 | # Tells whether we should check for unused import in __init__ files. 278 | init-import=no 279 | 280 | # A regular expression matching the name of dummy variables (i.e. expectedly 281 | # not used). 282 | dummy-variables-rgx=_$|dummy 283 | 284 | # List of additional names supposed to be defined in builtins. Remember that 285 | # you should avoid to define new builtins when possible. 286 | # 287 | # 'unicode' is defined in Python 2 and clcache only uses it inside of a case distinction. 288 | # Still, pylint running on Python 3 calls this an error. Silence it. 289 | additional-builtins=unicode 290 | 291 | # List of strings which can identify a callback function by name. A callback 292 | # name must start or end with one of those strings. 293 | callbacks=cb_,_cb 294 | 295 | 296 | [CLASSES] 297 | 298 | # List of method names used to declare (i.e. assign) instance attributes. 299 | defining-attr-methods=__init__,__new__,setUp 300 | 301 | # List of valid names for the first argument in a class method. 302 | valid-classmethod-first-arg=cls 303 | 304 | # List of valid names for the first argument in a metaclass class method. 305 | valid-metaclass-classmethod-first-arg=mcs 306 | 307 | # List of member names, which should be excluded from the protected access 308 | # warning. 309 | exclude-protected=_asdict,_fields,_replace,_source,_make 310 | 311 | 312 | [DESIGN] 313 | 314 | # Maximum number of arguments for function / method 315 | max-args=8 316 | 317 | # Argument names that match this expression will be ignored. Default to name 318 | # with leading underscore 319 | ignored-argument-names=_.* 320 | 321 | # Maximum number of locals for function / method body 322 | max-locals=20 323 | 324 | # Maximum number of return / yield for function / method body 325 | max-returns=20 326 | 327 | # Maximum number of branch for function / method body 328 | max-branches=20 329 | 330 | # Maximum number of statements in function / method body 331 | max-statements=100 332 | 333 | # Maximum number of parents for a class (see R0901). 334 | max-parents=7 335 | 336 | # Maximum number of attributes for a class (see R0902). 337 | max-attributes=7 338 | 339 | # Minimum number of public methods for a class (see R0903). 340 | min-public-methods=0 341 | 342 | # Maximum number of public methods for a class (see R0904). 343 | max-public-methods=50 344 | 345 | # Maximum number of boolean expressions in a if statement 346 | max-bool-expr=5 347 | 348 | 349 | [IMPORTS] 350 | 351 | # Deprecated modules which should not be used, separated by a comma 352 | deprecated-modules=optparse 353 | 354 | # Create a graph of every (i.e. internal and external) dependencies in the 355 | # given file (report RP0402 must not be disabled) 356 | import-graph= 357 | 358 | # Create a graph of external dependencies in the given file (report RP0402 must 359 | # not be disabled) 360 | ext-import-graph= 361 | 362 | # Create a graph of internal dependencies in the given file (report RP0402 must 363 | # not be disabled) 364 | int-import-graph= 365 | 366 | 367 | [EXCEPTIONS] 368 | 369 | # Exceptions that will emit a warning when being caught. Defaults to 370 | # "Exception" 371 | overgeneral-exceptions=Exception 372 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | clcache changelog 2 | ================= 3 | 4 | ## Upcoming release 5 | 6 | * Feature: Allow compressing cache content via new `CLCACHE_COMPRESS` and 7 | `CLCACHE_COMPRESSLEVEL` environment variables. (GH #328) 8 | * Bugfix: Fixed a race condition resulting in clcache to fail with an error 9 | message like `PermissionError: [WinError 5] Access is denied: 10 | 'XXXX\\stats.txt.new' -> 'XXXX\\stats.txt'` (GH #334) 11 | * The path to the compiler executable can optionally be specified on the 12 | command line, instead of with an environment variable, or searching the PATH. 13 | * Added support for clang-cl 14 | 15 | ## clcache 4.2.0 (2018-09-06) 16 | 17 | * Feature: Enable installation directly from GitHub via 'pip install' 18 | * Bugfix: Fixed potential corruption of cache in case clcache is terminated 19 | while writing a cache entry or updating the cache statistics. 20 | * Internal: The automatic clcache tests are now executed with Python 3.6, too. 21 | * Internal: Support for Python 3.3 and Python 3.4 has been dropped. 22 | * Bugfix: Fixed invoking the real compiler when using the `/Tc` or `/Tp` 23 | switches (GH #289) 24 | * Feature: Allow specifying just a file name via `CLCACHE_CL`, the absolute 25 | path will be computed automatically by scanning the `PATH`. 26 | * Bugfix: clcache correctly honours termination signals such as Ctrl+C again 27 | (GH #261). 28 | * Bugfix: Fixed some cases in which clcache might have written corrupted cache 29 | entries in case it gets terminated in the middle of copying a file (GH #263). 30 | * Feature: Added 'monkey' script to simplify integrating clcache with distutils-based 31 | workflows (GH #284). 32 | * Feature: Drop version in directory structure of chocolatey (nuget) target 33 | package (GH #318). 34 | 35 | ## clcache 4.1.0 (2017-05-23) 36 | 37 | * Bugfix: Fixed resource leak in clcachesrv. 38 | * Improvement: clcachesrv now shuts down cleanly when receiving SIGTERM. 39 | * Feature: clcachesrv now supports blacklisting directories to watch for file 40 | changes; this avoids problems with deleting such directories because clcachesrv 41 | still has them opened (in order to get notified to changes to header files). 42 | * Internal: The clcache source code now passes the checks performed by pylint 1.7.1 43 | * Documentation: Installation & integration instructions have been moved from 44 | the README file, into the clcache wiki at https://github.com/frerich/clcache/wiki 45 | * Feature: clcache now supports using a [memcached](https://memcached.org/) 46 | setup as the storage mechanism in addition to storing cached files in the file 47 | system. 48 | 49 | ## clcache 4.0.0 (2016-12-15) 50 | 51 | * Bugfix: Fixed occasional 'Cannot create a file when that file already 52 | exists' error when adding new objects to the cache (GH #155). 53 | * Improvement: Extended handling of CLCACHE_BASEDIR variable to improve cache 54 | hit rates when using different build directories. 55 | * Improvement: Improved interaction between concurrent clcache invocations 56 | when encountering multiple cache misses in parallel; the risk of triggering 57 | timeout exceptions is greatly reduced (GH #229). 58 | * Bugfix: Improved error handling in case statistics files or manifest files 59 | in the cache are malformed, which could happen when clcache got terminated 60 | abnormally. 61 | * Bugfix: Fixed off-by-one error when compiling source files concurrently, 62 | clcache would always launch one concurrent instance more than intended. 63 | * Feature: Added support for a 'CLCACHE_SERVER' environment variable which, when 64 | set to '1', makes 'clcache' use a separate 'clcachesrv' server process to cache 65 | hash sums. This improves performance, especially so for warm caches. 66 | * Improvement: Improved performance when invoking clcache with multiple source 67 | files; it will no longer launch itself recursively but rather use multiple 68 | threads to process the individual source files. 69 | * Internal: clcache now uses semantic versioning. 70 | 71 | ## clcache 3.3.1 (2016-10-25) 72 | 73 | * Bugfix: Aborting clcache via Ctrl+C or SIGTERM will no longer have the risk 74 | of leaving the cache in a defective state. 75 | * Internal: Fixed running integration tests when the clcache source code is 76 | stored in a path with spaces (GH #206). 77 | * Improvement: Optimized communicating with real compiler. 78 | * Bugfix: Fixed a potential CacheLockException apparently caused by 79 | a race condition between calling CreateMutexW() and 80 | WaitForSingleObject(). 81 | 82 | ## clcache 3.3.0 (2016-09-07) 83 | 84 | * Bugfix: /MP no longer causes a greatly reduced cache hit rate. 85 | * Bugfix: In direct mode, clcache will no longer try to access non-existant 86 | header files (GH #200, GH #209). 87 | * Bugfix: Correctly cache and restore stdout/stderr output of compiler when 88 | building via the Visual Studio IDE. 89 | * Add support for the `CL` and `_CL_` environment variables (GH #196). 90 | * Feature: A new `CLCACHE_PROFILE` environment variable is now recognised 91 | which can be used to make clcache generate profiling information. The 92 | generated data can be processed into a final report using a new 93 | `showprofilereport.py` script. 94 | * Improvement: Timeout errors when accessing the cache now generate friendlier 95 | error messages mentioning the possibility to work around the issue using the 96 | `CLCACHE_OBJECT_CACHE_TIMEOUT_MS` environment variable. 97 | * Improvement: Greatly improved concurrency of clcache such that concurrent 98 | invocations of the tool no longer block each other. 99 | * Improvement: Improve hit rate when alternating between two identical 100 | versions of the same source file that transitively get different contents of 101 | the included files (a common case when switching back and forth between 102 | branches). 103 | 104 | ## clcache 3.2.0 (2016-07-28) 105 | 106 | * Bugfix: When preprocessing was used together with an /Fo argument (which makes 107 | no sense), the handling was wrong. 108 | * Bugfix: Properly handle /Fi arguments 109 | * Bugfix: Fixed printing cached compiler output when using Python 2.x. 110 | * Dropped support for caching preprocessor invocations. The number of such 111 | invocations is now printed in the statistics (`ccache -s`). 112 | * Bugfix: In MSVS, arguments use the formats `/NAMEparameter` (no space, required value), 113 | `/NAME[parameter]` (no space, optional value), `/NAME[ ]parameter` (optional space), 114 | and `/NAME parameter` (required space). Before we always tried `/NAMEparameter` 115 | and if there if no parameter, tried `/NAME parameter`. This strategy was too simple 116 | and failed for like e.g. `/Fo`, which must not consume the following argument when 117 | no parameter is set. 118 | * Rework manifests: use JSON to store manifests. This makes all existing manifests 119 | invalid. Cleaning and clearing now removes old manifest files as well, so the old 120 | .dat files are automatically removed. 121 | * clcache now requires Python 3.3 or newer. 122 | * py2exe support was dropped, PyInstaller is now recommended for generating 123 | .exe files. 124 | 125 | ## clcache 3.1.1 (2016-06-25) 126 | 127 | * Improvement: better protection against storing corrupt objects in the cache 128 | in case clcache is terminated in the middle of storing a new cache entry. 129 | * Improvement: The README now explains an approach to making Visual Studio 130 | pick up clcache. 131 | * Bugfix: Command files with multiple lines are now handled correctly. 132 | * Bugfix: Command files which contribute arguments ending in a backslash are 133 | now parsed correctly (GH #108). 134 | * Bugfix: Properly handle non-ASCII compiler output (GH #64) 135 | 136 | ## clcache 3.1.0 (2016-06-09) 137 | 138 | * Cached objects are no longer shared between different clcache versions to 139 | avoid restoring objects which were stored incorrectly in older clcache 140 | versions. 141 | * Feature: The cache statistics now count the number of calls with /Zi (which 142 | causes debug information to be stored in separate `.pdb` files) 143 | * Feature: a new `-c` switch is now recognized which can be used to clean the 144 | cache. Cleaning the cache means trimming the cache to 90% of it's maximum 145 | size by deleting the oldest object. Doing this explicitly avoids that this 146 | happens automatically during a build. 147 | * Feature: a new `-C` switch was added which can be used to clear the cache. 148 | Clearing the cache removes all cached objects, but keeps all hit/miss 149 | statistics. 150 | * Improvement: The `ccache -s` output is now prettier. 151 | * Improvement: cleaning outdated cache entries is now a lot faster 152 | * Bugfix: Support use of py2exe with Python 3.4 153 | * Bugfix: Support includes parsing when cl language is not English (GH #65) 154 | * Bugfix: Fix bug causing statistics to get corrupted after concurrent invocations (GH #70). 155 | * Bugfix: Invalid values passed via `-M` are now handle gracefully. 156 | * Bugfix: The cache size and the number of cached entries is now updated 157 | correctly after pruning outdated objects. 158 | * Internal: major overhaul to the test infrastructure: a lot more tests are 159 | now executed on each change, and they are executed automatically via 160 | AppVeyor. 161 | * Internal: a lot of issues (both cosmetic as well as semantic) reported by 162 | pylint have been fixed. pylint is now also executed for each change to the 163 | codebase via AppVeyor. 164 | 165 | ## clcache 3.0.2 (2016-01-29) 166 | 167 | * Python 3 compatibility 168 | * Add new env variable to control clcache lock timeout 169 | * Bugfix: Fix recompile bug in direct mode when header changed 170 | * Bugfix: Fix compile error when clcache is used with precompiled headers 171 | * Bugfix: `/MP[processMax]` was not properly parsed when `processMax` was unset 172 | * Bugfix: Properly handle arguments including `\"` (e.g. `/Fo"Release\"`) 173 | * Bugfix: Ensure the destination folder exists when copying object 174 | * Bugfix: Fix crash when using CLCACHE_NODIRECT by restoring missing argument 175 | * Bugfix: Fix fork-bomb when py2exe is used 176 | 177 | **Implementation details** 178 | 179 | * Setup CI system 180 | * Add some basic tests 181 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at frerich.raabe@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please see the [clcache Wiki](https://github.com/frerich/clcache/wiki/Contributing) on guidelines 2 | how to contribute to the project. 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2 | 2010, 2011, 2012, 2013, 2016 froglogic GmbH 3 | 2016 Simon Warta (Kullo GmbH) 4 | 2016 Tim Blechmann 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the names of its contributors 18 | may be used to endorse or promote products derived from this software without 19 | specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lint-clcache lint-unittests lint-integrationtests lint 2 | 3 | lint-clcache: 4 | pylint --rcfile .pylintrc clcache.py 5 | 6 | lint-unittests: 7 | pylint --rcfile .pylintrc unittests.py 8 | 9 | lint-integrationtests: 10 | pylint --rcfile .pylintrc integrationtests.py 11 | 12 | lint: lint-clcache lint-unittests lint-integrationtests 13 | -------------------------------------------------------------------------------- /README.asciidoc: -------------------------------------------------------------------------------- 1 | clcache.py - a compiler cache for Microsoft Visual Studio 2 | --------------------------------------------------------- 3 | 4 | clcache.py is a little Python script which attempts to avoid unnecessary 5 | recompilation by reusing previously cached object files if possible. It 6 | is meant to be called instead of the original 'cl.exe' executable. The 7 | script analyses the command line to decide whether source code is 8 | to be compiled. If so, a cache will be queried for a previously stored 9 | object file. 10 | 11 | If the script is called in an unsupported way (e.g. if the compiler is 12 | called for linking), the script will simply relay the invocation to the real 13 | 'cl.exe' program. 14 | 15 | image:https://ci.appveyor.com/api/projects/status/sf98y2686r00q6ga/branch/master?svg=true[Build status, link="https://ci.appveyor.com/project/frerich/clcache"] 16 | image:https://codecov.io/gh/frerich/clcache/branch/master/graph/badge.svg[Code coverage, link="https://codecov.io/gh/frerich/clcache"] 17 | 18 | Installation 19 | ~~~~~~~~~~~~ 20 | 21 | Please see the https://github.com/frerich/clcache/wiki[Wiki] for instructions 22 | on how to https://github.com/frerich/clcache/wiki/Installation[install] clcache 23 | and different approaches on how to 24 | https://github.com/frerich/clcache/wiki/Integration[integrate] it into a build 25 | system. 26 | 27 | Options 28 | ~~~~~~~ 29 | 30 | --help:: 31 | Print usage information 32 | -s:: 33 | Print some statistics about the cache (cache hits, cache misses, cache 34 | size etc.) 35 | -c:: 36 | Clean the cache: trim the cache size to 90% of its maximum by removing 37 | the oldest objects. 38 | -C:: 39 | Clear the cache: remove all cached objects, but keep the cache statistics 40 | (hits, misses, etc.). 41 | -z:: 42 | Reset the cache statistics, i.e. number of cache hits, cache misses etc.. 43 | Doesn't actually clear the cache, so the number of cached objects and the 44 | cache size will remain unchanged. 45 | -M :: 46 | Sets the maximum size of the cache in bytes. 47 | The default value is 1073741824 (1 GiB). 48 | 49 | compiler:: 50 | It is, optionally, possible to specify the full path to the compiler as the 51 | first argument on the command line, in the style of ccache, instead of using 52 | the CLCACHE_CL environment variable or searching the path for cl.exe 53 | 54 | Environment Variables 55 | ~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | CLCACHE_DIR:: 58 | If set, points to the directory within which all the cached object files 59 | should be stored. This defaults to `%HOME%\clcache` 60 | CLCACHE_CL:: 61 | Can be set to the actual 'cl.exe' executable to use. If this variable is 62 | not set, the 'clcache.py' script will scan the directories listed in the 63 | +PATH+ environment variable for 'cl.exe'. In case this is just a file name 64 | (as opposed to an absolute path), 'clcache.py' will scan the directories 65 | mentioned by the `%PATH%` environment variable to compute the absolute 66 | path. 67 | CLCACHE_LOG:: 68 | If this variable is set, a bit of diagnostic information is printed which 69 | can help with debugging cache problems. 70 | CLCACHE_DISABLE:: 71 | Setting this variable will disable 'clcache.py' completely. The script will 72 | relay all calls to the real compiler. 73 | CLCACHE_HARDLINK:: 74 | If this variable is set, cached object files won't be copied to their 75 | final location. Instead, hard links pointing to the cached object files 76 | will be created. This is more efficient (faster, and uses less disk space) 77 | but doesn't work if the cache directory is on a different drive than the 78 | build directory. 79 | CLCACHE_COMPRESS:: 80 | If true, clcache will compress object files it puts in the cache. If the cache 81 | was filled without compression it can't be used with compression and vice versa 82 | (i.e. you have to clear the cache when changing this setting). The default is false. 83 | CLCACHE_COMPRESSLEVEL:: 84 | This setting determines the level at which clcache will compress object files. 85 | It only has effect if compression is enabled. The value defaults to 6, and 86 | must be no lower than 1 (fastest, worst compression) and no higher than 9 87 | (slowest, best compression). 88 | CLCACHE_NODIRECT:: 89 | Disable direct mode. If this variable is set, clcache will always run 90 | preprocessor on source file and will hash preprocessor output to get cache 91 | key. Use this if you experience problems with direct mode or if you need 92 | built-in macroses like \__TIME__ to work correctly. 93 | CLCACHE_BASEDIR:: 94 | Has effect only when direct mode is on. Set this to path to root directory 95 | of your project. This allows clcache to cache relative paths, so if you 96 | move your project to different directory, clcache will produce cache hits as 97 | before. 98 | CLCACHE_OBJECT_CACHE_TIMEOUT_MS:: 99 | Overrides the default ObjectCacheLock timeout (Default is 10 * 1000 ms). 100 | The ObjectCacheLock is used to give exclusive access to the cache, which is 101 | used by the clcache script. You may override this variable if you are 102 | getting ObjectCacheLockExceptions with return code 258 (which is the 103 | WAIT_TIMEOUT return code). 104 | CLCACHE_PROFILE:: 105 | If this variable is set, clcache will generate profiling information about 106 | how the runtime is spent in the clcache code. For each invocation, clcache 107 | will generate a file with a name similiar to 'clcache-.prof'. You 108 | can aggregate these files and generate a report by running the 109 | 'showprofilereport.py' script. 110 | CLCACHE_SERVER:: 111 | Setting this environment variable will make clcache use (and expect) a 112 | running `clcachesrv.py` script which takes care of caching file hashes. 113 | This greatly improves performance of cache hits, but only has an effect in 114 | direct mode (i.e. when `CLCACHE_NODIRECT` is not set). 115 | CLCACHE_MEMCACHED:: 116 | This variable can be used to make clcache use a 117 | memcached[https://memcached.org/] backend for saving and restoring cached 118 | data. The variable is assumed to hold the host and port information of the 119 | memcached server, e.g. `127.0.0.1:11211`. 120 | 121 | 122 | Known limitations 123 | ~~~~~~~~~~~~~~~~~ 124 | 125 | * https://msdn.microsoft.com/en-us/library/kezkeayy.aspx[+INCLUDE+ and +LIBPATH+] 126 | environment variables are not supported. 127 | 128 | How clcache works 129 | ~~~~~~~~~~~~~~~~~ 130 | 131 | clcache.py was designed to intercept calls to the actual cl.exe compiler 132 | binary. Once an invocation has been intercepted, the command line is analyzed for 133 | whether it is a command line which just compiles a single source file into an 134 | object file. This means that all of the following requirements on the command 135 | line must be true: 136 | 137 | * The +/link+ switch must not be present 138 | * The +/c+ switch must be present 139 | * The +/Zi+ switch must not be present (+/Z7+ is okay though) 140 | 141 | If multiple source files are given on the command line, clcache.py wil invoke 142 | itself multiple times while respecting an optional +/MP+ switch. 143 | 144 | If all the above requirements are met, clcache forwards the call to the 145 | preprocessor by replacing +/c+ with +/EP+ in the command line and then 146 | invoking it. This will cause the complete preprocessed source code to be 147 | printed. clcache then generates a hash sum out of 148 | 149 | * The complete preprocessed source code 150 | * The `normalized' command line 151 | * The file size of the compiler binary 152 | * The modification time of the compiler binary 153 | 154 | The `normalized' command line is the given command line minus all switches 155 | which either don't influence the generated object file (such as +/Fo+) or 156 | which have already been covered otherwise. For instance, all switches which 157 | merely influence the preprocessor can be skipped since their effect is already 158 | implicitly contained in the preprocessed source code. 159 | 160 | Once the hash sum is computed, it is used as a key (actually, a directory 161 | name) in the cache (which is a directory itself). If the cache entry exists 162 | already, it is supposed to contain a file with the stdout output of the 163 | compiler as well as the previously generated object file. clcache will 164 | copy the previously generated object file to the designated output path and 165 | then print the contents of the stdout text file. That way, the script 166 | behaves as if the actual compiler was invoked. 167 | 168 | If the hash sum is not yet used in the cache, clcache will forward the 169 | invocation to the actual compiler. Once the real compiler successfully 170 | finished its work, the generated object file (as well as the output printed 171 | by the compiler) is copied to the cache. 172 | 173 | Caveats 174 | ~~~~~~~ 175 | For known caveats, please see the 176 | https://github.com/frerich/clcache/wiki/Caveats[Caveats wiki page]. 177 | 178 | License Terms 179 | ~~~~~~~~~~~~~ 180 | The source code of this project is - unless explicitly noted otherwise in the 181 | respective files - subject to the 182 | https://opensource.org/licenses/BSD-3-Clause[BSD 3-Clause License]. 183 | 184 | Credits 185 | ~~~~~~~ 186 | clcache.py was written by mailto:raabe@froglogic.com[Frerich Raabe] with a lot 187 | of help by mailto:vchigrin@yandex-team.ru[Slava Chigrin], Simon Warta, Tim 188 | Blechmann, Tilo Wiedera and other contributors. 189 | 190 | This program was heavily inspired by http://ccache.samba.org[ccache], a 191 | compiler cache for the http://gcc.gnu.org[GNU Compiler Collection]. 192 | 193 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # whitelist branches to avoid testing feature branches twice (as branch and as pull request) 2 | branches: 3 | only: 4 | - master 5 | 6 | environment: 7 | matrix: 8 | # AppVeyor installed Python versions 9 | # http://www.appveyor.com/docs/installed-software#python 10 | 11 | - PYTHON_INSTALL: "C:\\Python35" 12 | - PYTHON_INSTALL: "C:\\Python36" 13 | 14 | install: 15 | - appveyor DownloadFile "http://s3.amazonaws.com/downloads.northscale.com/memcached-win64-1.4.4-14.zip" -FileName memcached.zip 16 | - 7z x memcached.zip -y 17 | - ps: $Memcached = Start-Process memcached\memcached.exe -PassThru 18 | 19 | # Make compiler available (use MSVC 2013, 32 bit) 20 | - call "%ProgramFiles(x86)%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x86 21 | 22 | # Check compiler version 23 | - cl 24 | 25 | # Prepend Python installation to PATH 26 | - set PATH=%PYTHON_INSTALL%;%PATH% 27 | 28 | # Prepend Python scripts to PATH (e.g. pip, py.test, pylint) 29 | - set PATH=%PYTHON_INSTALL%\Scripts;%PATH% 30 | 31 | # Check Python version 32 | - python --version 33 | 34 | # Upgrade the Python build tools 35 | - python -m pip install --upgrade pip setuptools wheel 36 | 37 | # Check pip version 38 | - pip --version 39 | 40 | # Install pytest 41 | - pip install pytest 42 | 43 | # Install mypy 44 | - pip install mypy 45 | 46 | # Install pylint (installs pylint.exe in %PYTHON_INSTALL%\Scripts) 47 | - pip install pylint 48 | - pylint --version 49 | 50 | # Install coverage plugin for pytest 51 | - pip install pytest-cov 52 | 53 | # Install the package 54 | - pip install . 55 | 56 | # Install tool for uploading coverage data to codecov.io; select version 57 | # 2.0.10 specifically since later versions seem to have a bug due to 58 | # which calling 'codecov' in a PowerShell script fails. 59 | - pip install https://github.com/codecov/codecov-python/tarball/v2.0.10 60 | - coverage --version 61 | 62 | # Install dependencies for clcachesrv such that we can lint the source code 63 | - pip install -r server\requirements.txt 64 | 65 | # Install pymemcache for memcache storage 66 | - pip install pymemcache 67 | 68 | cache: 69 | - '%LOCALAPPDATA%\pip\Cache -> appveyor.yml' 70 | - memcached -> appveyor.yml 71 | 72 | build_script: 73 | - clcache --help 74 | - clcache -s 75 | # - python clcachesrv.py 76 | - pylint --rcfile=.pylintrc clcache\__main__.py 77 | - pylint --rcfile=.pylintrc clcache\storage.py 78 | - pylint --rcfile=.pylintrc tests\test_unit.py 79 | - pylint --rcfile=.pylintrc --disable=no-member tests\test_integration.py 80 | - pylint --rcfile=.pylintrc tests\test_performance.py 81 | 82 | # Disable no-member test here to work around issue in Pylint 1.7.1 83 | - pylint --rcfile=.pylintrc --disable=no-member clcache\server\__main__.py 84 | 85 | - mypy --ignore-missing-imports . 86 | 87 | test_script: 88 | # Run test files via py.test and generate JUnit XML. Then push test results 89 | # to appveyor. The plugin pytest-cov takes care of coverage. 90 | - ps: | 91 | & py.test --junitxml .\unittests.xml --cov 92 | $testsExitCode = $lastexitcode 93 | & coverage report 94 | & coverage xml 95 | 96 | $wc = New-Object 'System.Net.WebClient' 97 | $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\unittests.xml)) 98 | 99 | & codecov --no-color -X gcov -F unittests -e PYTHON_INSTALL -f coverage.xml 100 | 101 | if ($testsExitCode -ne 0) {exit $testsExitCode} 102 | 103 | - coverage erase 104 | - del /Q coverage.xml 105 | 106 | - set CLCACHE_MEMCACHED=127.0.0.1:11211 107 | - ps: | 108 | & py.test --junitxml .\integrationtests.xml --cov 109 | $testsExitCode = $lastexitcode 110 | & coverage report 111 | & coverage xml 112 | 113 | $wc = New-Object 'System.Net.WebClient' 114 | $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\integrationtests.xml)) 115 | 116 | & codecov --no-color -X gcov -F integrationtests_memcached -e PYTHON_INSTALL -f coverage.xml 117 | 118 | if ($testsExitCode -ne 0) {exit $testsExitCode} 119 | 120 | - coverage erase 121 | - del /Q coverage.xml 122 | - set "CLCACHE_MEMCACHED=" 123 | 124 | on_finish: 125 | - ps: Stop-Process -Id $Memcached.Id 126 | 127 | on_success: 128 | # Creates executable with PyInstaller 129 | - pip install pyinstaller pypiwin32 130 | - pyinstaller pyinstaller\clcache_main.py 131 | - for /F %%i in ('python -c "import clcache; print(clcache.VERSION)"') do set CLCACHE_VERSION=%%i 132 | - "echo Version: %CLCACHE_VERSION" 133 | - ps: mv dist\clcache_main\clcache_main.exe dist\clcache_main\clcache.exe 134 | - move dist\clcache_main clcache-%CLCACHE_VERSION% 135 | 136 | # Build the Chocolatey package 137 | - sed -i "s/{VERSION}/%CLCACHE_VERSION%/g" clcache.nuspec 138 | - choco pack 139 | 140 | # Test the compiled extension 141 | - cd clcache-%CLCACHE_VERSION% 142 | - clcache.exe --help 143 | - clcache.exe -s 144 | - cd .. 145 | 146 | # Test the chocolatey package 147 | - ps: ls clcache.${env:CLCACHE_VERSION}.nupkg 148 | - choco install "clcache.%CLCACHE_VERSION%.nupkg" --version=%CLCACHE_VERSION% 149 | - C:\ProgramData\chocolatey\bin\clcache.exe --help 150 | - C:\ProgramData\chocolatey\bin\clcache.exe -s 151 | 152 | # Publish artifacts 153 | - cd clcache-%CLCACHE_VERSION% 154 | - 7z a -y clcache-%CLCACHE_VERSION%.zip 155 | - move clcache-%CLCACHE_VERSION%.zip .. 156 | - cd .. 157 | - echo PYTHON_INSTALL=%PYTHON_INSTALL% 158 | # Disable for Python 3.5, because of PyInstaller problems on older Windows 159 | - if not [%PYTHON_INSTALL%]==["C:\Python35"] set PUBLISH=1 160 | - sha256sum clcache.%CLCACHE_VERSION%.nupkg clcache-%CLCACHE_VERSION%.zip 161 | - sha256sum clcache.%CLCACHE_VERSION%.nupkg clcache-%CLCACHE_VERSION%.zip > SHA256SUM 162 | - if "%PUBLISH%"=="1" appveyor PushArtifact clcache.%CLCACHE_VERSION%.nupkg 163 | - if "%PUBLISH%"=="1" appveyor PushArtifact clcache-%CLCACHE_VERSION%.zip 164 | - if "%PUBLISH%"=="1" appveyor PushArtifact SHA256SUM 165 | -------------------------------------------------------------------------------- /clcache.nuspec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/clcache.nuspec -------------------------------------------------------------------------------- /clcache.pth: -------------------------------------------------------------------------------- 1 | import sys; import site; sys.path.extend(site.getsitepackages()); import clcache.monkey; clcache.monkey.main() 2 | -------------------------------------------------------------------------------- /clcache/__init__.py: -------------------------------------------------------------------------------- 1 | from .__main__ import VERSION 2 | -------------------------------------------------------------------------------- /clcache/monkey.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | 5 | from typing import List 6 | from contextlib import suppress 7 | from os.path import join, dirname, isfile 8 | 9 | def patch_distutils() -> None: 10 | # Try to import numpy.distutils first so that we 11 | # can patch after numpy does (We're very early in 12 | # the import cycle) 13 | 14 | with suppress(ImportError): 15 | from numpy.distutils import ccompiler as _ 16 | 17 | from distutils import ccompiler 18 | from clcache import __main__ 19 | 20 | clcache_main = [sys.executable, __main__.__file__] 21 | ccompiler_spawn = ccompiler.CCompiler.spawn 22 | def msvc_compiler_spawn(self: ccompiler.CCompiler, cmd: List[str]) -> None: 23 | if not hasattr(self, 'cc'): # type: ignore 24 | return ccompiler_spawn(self, cmd) 25 | 26 | if os.path.basename(self.cc) not in ['cl', 'cl.exe']: # type: ignore 27 | return ccompiler_spawn(self, cmd) 28 | 29 | if cmd[0] != self.cc: # type: ignore 30 | # We're not running the compiler 31 | return ccompiler_spawn(self, cmd) 32 | 33 | cmd = clcache_main + cmd[1:] 34 | # Set the environment variables so that clcache can run 35 | os.environ['CLCACHE_CL'] = self.cc # type: ignore 36 | print('Note: patching distutils because $env:USE_CLCACHE is set') 37 | 38 | return ccompiler_spawn(self, cmd) 39 | 40 | ccompiler.CCompiler.spawn = msvc_compiler_spawn # type: ignore 41 | 42 | 43 | def main() -> None: 44 | if os.environ.get('USE_CLCACHE') != '1': 45 | return 46 | 47 | patch_distutils() 48 | -------------------------------------------------------------------------------- /clcache/server/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /clcache/server/__main__.py: -------------------------------------------------------------------------------- 1 | # We often don't use all members of all the pyuv callbacks 2 | # pylint: disable=unused-argument 3 | import hashlib 4 | import logging 5 | import os 6 | import pickle 7 | import signal 8 | import argparse 9 | import re 10 | 11 | import pyuv 12 | 13 | class HashCache: 14 | def __init__(self, loop, excludePatterns, disableWatching): 15 | self._loop = loop 16 | self._watchedDirectories = {} 17 | self._handlers = [] 18 | self._excludePatterns = excludePatterns or [] 19 | self._disableWatching = disableWatching 20 | 21 | def getFileHash(self, path): 22 | logging.debug("getting hash for %s", path) 23 | dirname, basename = os.path.split(os.path.normcase(path)) 24 | 25 | watchedDirectory = self._watchedDirectories.get(dirname, {}) 26 | hashsum = watchedDirectory.get(basename) 27 | if hashsum: 28 | logging.debug("using cached hashsum %s", hashsum) 29 | return hashsum 30 | 31 | with open(path, 'rb') as f: 32 | hashsum = hashlib.md5(f.read()).hexdigest() 33 | 34 | watchedDirectory[basename] = hashsum 35 | if dirname not in self._watchedDirectories and not self.isExcluded(dirname) and not self._disableWatching: 36 | logging.debug("starting to watch directory %s for changes", dirname) 37 | self._startWatching(dirname) 38 | 39 | self._watchedDirectories[dirname] = watchedDirectory 40 | 41 | logging.debug("calculated and stored hashsum %s", hashsum) 42 | return hashsum 43 | 44 | def _startWatching(self, dirname): 45 | ev = pyuv.fs.FSEvent(self._loop) 46 | ev.start(dirname, 0, self._onPathChange) 47 | self._handlers.append(ev) 48 | 49 | def _onPathChange(self, handle, filename, events, error): 50 | watchedDirectory = self._watchedDirectories[handle.path] 51 | logging.debug("detected modifications in %s", handle.path) 52 | if filename in watchedDirectory: 53 | logging.debug("invalidating cached hashsum for %s", os.path.join(handle.path, filename)) 54 | del watchedDirectory[filename] 55 | 56 | def __del__(self): 57 | for ev in self._handlers: 58 | ev.stop() 59 | 60 | def isExcluded(self, dirname): 61 | # as long as we do not have more than _MAXCACHE regex we can 62 | # rely on the internal cacheing of re.match 63 | excluded = any(re.search(pattern, dirname, re.IGNORECASE) for pattern in self._excludePatterns) 64 | if excluded: 65 | logging.debug("NOT watching %s", dirname) 66 | return excluded 67 | 68 | 69 | class Connection: 70 | def __init__(self, pipe, cache, onCloseCallback): 71 | self._readBuffer = b'' 72 | self._pipe = pipe 73 | self._cache = cache 74 | self._onCloseCallback = onCloseCallback 75 | pipe.start_read(self._onClientRead) 76 | 77 | def _onClientRead(self, pipe, data, error): 78 | self._readBuffer += data 79 | if self._readBuffer.endswith(b'\x00'): 80 | paths = self._readBuffer[:-1].decode('utf-8').splitlines() 81 | logging.debug("received request to hash %d paths", len(paths)) 82 | try: 83 | hashes = map(self._cache.getFileHash, paths) 84 | response = '\n'.join(hashes).encode('utf-8') 85 | except OSError as e: 86 | response = b'!' + pickle.dumps(e) 87 | pipe.write(response + b'\x00', self._onWriteDone) 88 | 89 | def _onWriteDone(self, pipe, error): 90 | logging.debug("sent response to client, closing connection") 91 | self._pipe.close() 92 | self._onCloseCallback(self) 93 | 94 | 95 | class PipeServer: 96 | def __init__(self, loop, address, cache): 97 | self._pipeServer = pyuv.Pipe(loop) 98 | self._pipeServer.bind(address) 99 | self._connections = [] 100 | self._cache = cache 101 | 102 | def listen(self): 103 | self._pipeServer.listen(self._onConnection) 104 | 105 | def _onConnection(self, pipe, error): 106 | logging.debug("detected incoming connection") 107 | client = pyuv.Pipe(self._pipeServer.loop) 108 | pipe.accept(client) 109 | self._connections.append(Connection(client, self._cache, self._connections.remove)) 110 | 111 | 112 | def closeHandlers(handle): 113 | for h in handle.loop.handles: 114 | h.close() 115 | 116 | 117 | def onSigint(handle, signum): 118 | logging.info("Ctrl+C detected, shutting down") 119 | closeHandlers(handle) 120 | 121 | 122 | def onSigterm(handle, signum): 123 | logging.info("Server was killed by SIGTERM") 124 | closeHandlers(handle) 125 | 126 | 127 | def main(): 128 | logging.basicConfig(format='%(asctime)s [%(levelname)s]: %(message)s', level=logging.INFO) 129 | 130 | parser = argparse.ArgumentParser(description='Server process for clcache to cache hash values of headers \ 131 | and observe them for changes.') 132 | parser.add_argument('--exclude', metavar='REGEX', action='append', \ 133 | help='Regex ( re.search() ) for exluding of directory watching. Can be specified \ 134 | multiple times. Example: --exclude \\\\build\\\\') 135 | parser.add_argument('--disable_watching', action='store_true', help='Disable watching of directories which \ 136 | we have in the cache.') 137 | args = parser.parse_args() 138 | 139 | for pattern in args.exclude or []: 140 | logging.info("Not watching paths which match: %s", pattern) 141 | 142 | if args.disable_watching: 143 | logging.info("Disabled directory watching") 144 | 145 | eventLoop = pyuv.Loop.default_loop() 146 | 147 | cache = HashCache(eventLoop, vars(args)['exclude'], args.disable_watching) 148 | 149 | server = PipeServer(eventLoop, r'\\.\pipe\clcache_srv', cache) 150 | server.listen() 151 | 152 | signalHandle = pyuv.Signal(eventLoop) 153 | signalHandle.start(onSigint, signal.SIGINT) 154 | signalHandle.start(onSigterm, signal.SIGTERM) 155 | 156 | logging.info("clcachesrv started") 157 | eventLoop.run() 158 | 159 | 160 | if __name__ == '__main__': 161 | main() 162 | -------------------------------------------------------------------------------- /clcache/storage.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | from pymemcache.client.base import Client 4 | from pymemcache.serde import (python_memcache_serializer, 5 | python_memcache_deserializer) 6 | 7 | from .__main__ import CacheFileStrategy, getStringHash, printTraceStatement, CompilerArtifacts, \ 8 | CACHE_COMPILER_OUTPUT_STORAGE_CODEC 9 | 10 | 11 | class CacheDummyLock: 12 | def __enter__(self): 13 | pass 14 | 15 | def __exit__(self, typ, value, traceback): 16 | pass 17 | 18 | 19 | class CacheMemcacheStrategy: 20 | def __init__(self, server, cacheDirectory=None, manifestPrefix='manifests_', objectPrefix='objects_'): 21 | self.fileStrategy = CacheFileStrategy(cacheDirectory=cacheDirectory) 22 | # XX Memcache Strategy should be independent 23 | 24 | self.lock = CacheDummyLock() 25 | self.localCache = {} 26 | self.localManifest = {} 27 | self.objectPrefix = objectPrefix 28 | self.manifestPrefix = manifestPrefix 29 | 30 | self.connect(server) 31 | 32 | def connect(self, server): 33 | server = CacheMemcacheStrategy.splitHosts(server) 34 | assert server, "{} is not a suitable server".format(server) 35 | if len(server) == 1: 36 | clientClass = Client 37 | server = server[0] 38 | else: 39 | from pymemcache.client.hash import HashClient 40 | clientClass = HashClient 41 | self.client = clientClass(server, ignore_exc=True, 42 | serializer=python_memcache_serializer, 43 | deserializer=python_memcache_deserializer, 44 | timeout=5, 45 | connect_timeout=5, 46 | key_prefix=(getStringHash(self.fileStrategy.dir) + "_").encode("UTF-8") 47 | ) 48 | # XX key_prefix ties fileStrategy cache to memcache entry 49 | # because tests currently the integration tests use this to start with clean cache 50 | # Prevents from having cache hits in when code base is in different locations 51 | # adding code to production just for testing purposes 52 | 53 | def server(self): 54 | return self.client.server 55 | 56 | @staticmethod 57 | def splitHost(host): 58 | port = 11211 59 | index = host.rfind(':') 60 | if index != -1: 61 | host, port = host[:index], int(host[index + 1:]) 62 | if not host or port > 65535: 63 | raise ValueError 64 | return host.strip(), port 65 | 66 | @staticmethod 67 | def splitHosts(hosts): 68 | """ 69 | :param hosts: A string in the format of HOST:PORT[,HOST:PORT] 70 | :return: a list [(HOST, int(PORT)), ..] of tuples that can be consumed by socket.connect() 71 | """ 72 | return [CacheMemcacheStrategy.splitHost(h) for h in hosts.split(',')] 73 | 74 | def __str__(self): 75 | return "Remote Memcache @{} object-prefix: {}".format(self.server, self.objectPrefix) 76 | 77 | @property 78 | def statistics(self): 79 | return self.fileStrategy.statistics 80 | 81 | @property 82 | def configuration(self): 83 | return self.fileStrategy.configuration 84 | 85 | @staticmethod 86 | def lockFor(_): 87 | return CacheDummyLock() 88 | 89 | @staticmethod 90 | def manifestLockFor(_): 91 | return CacheDummyLock() 92 | 93 | def _fetchEntry(self, key): 94 | data = self.client.get((self.objectPrefix + key).encode("UTF-8")) 95 | if data is not None: 96 | self.localCache[key] = data 97 | return True 98 | self.localCache[key] = None 99 | return None 100 | 101 | def hasEntry(self, key): 102 | localCache = key in self.localCache and self.localCache[key] is not None 103 | return localCache or self._fetchEntry(key) is not None 104 | 105 | def getEntry(self, key): 106 | if key not in self.localCache: 107 | self._fetchEntry(key) 108 | if self.localCache[key] is None: 109 | return None 110 | data = self.localCache[key] 111 | 112 | printTraceStatement("{} remote cache hit for {} dumping into local cache".format(self, key)) 113 | 114 | assert len(data) == 3 115 | 116 | # XX this is writing the remote objectfile into the local cache 117 | # because the current cache lookup assumes that getEntry gives us an Entry in local cache 118 | # so it can copy it to the build destination later 119 | 120 | with self.fileStrategy.lockFor(key): 121 | objectFilePath = self.fileStrategy.deserializeCacheEntry(key, data[0]) 122 | 123 | return CompilerArtifacts(objectFilePath, 124 | data[1].decode(CACHE_COMPILER_OUTPUT_STORAGE_CODEC), 125 | data[2].decode(CACHE_COMPILER_OUTPUT_STORAGE_CODEC) 126 | ) 127 | 128 | def setEntry(self, key, artifacts): 129 | assert artifacts.objectFilePath 130 | with open(artifacts.objectFilePath, 'rb') as objectFile: 131 | self._setIgnoreExc(self.objectPrefix + key, 132 | [objectFile.read(), 133 | artifacts.stdout.encode(CACHE_COMPILER_OUTPUT_STORAGE_CODEC), 134 | artifacts.stderr.encode(CACHE_COMPILER_OUTPUT_STORAGE_CODEC)], 135 | ) 136 | 137 | def setManifest(self, manifestHash, manifest): 138 | self._setIgnoreExc(self.manifestPrefix + manifestHash, manifest) 139 | 140 | def _setIgnoreExc(self, key, value): 141 | try: 142 | self.client.set(key.encode("UTF-8"), value) 143 | except Exception: 144 | self.client.close() 145 | if self.client.ignore_exc: 146 | printTraceStatement("Could not set {} in memcache {}".format(key, self.server())) 147 | return None 148 | raise 149 | return None 150 | 151 | def getManifest(self, manifestHash): 152 | return self.client.get((self.manifestPrefix + manifestHash).encode("UTF-8")) 153 | 154 | def clean(self, stats, maximumSize): 155 | self.fileStrategy.clean(stats, 156 | maximumSize) 157 | 158 | 159 | class CacheFileWithMemcacheFallbackStrategy: 160 | def __init__(self, server, cacheDirectory=None, manifestPrefix='manifests_', objectPrefix='objects_'): 161 | self.localCache = CacheFileStrategy(cacheDirectory=cacheDirectory) 162 | self.remoteCache = CacheMemcacheStrategy(server, cacheDirectory=cacheDirectory, 163 | manifestPrefix=manifestPrefix, 164 | objectPrefix=objectPrefix) 165 | 166 | def __str__(self): 167 | return "CacheFileWithMemcacheFallbackStrategy local({}) and remote({})".format(self.localCache, 168 | self.remoteCache) 169 | 170 | def hasEntry(self, key): 171 | return self.localCache.hasEntry(key) or self.remoteCache.hasEntry(key) 172 | 173 | def getEntry(self, key): 174 | if self.localCache.hasEntry(key): 175 | printTraceStatement("Getting object {} from local cache".format(key)) 176 | return self.localCache.getEntry(key) 177 | remote = self.remoteCache.getEntry(key) 178 | if remote: 179 | printTraceStatement("Getting object {} from remote cache".format(key)) 180 | return remote 181 | return None 182 | 183 | def setEntry(self, key, artifacts): 184 | self.localCache.setEntry(key, artifacts) 185 | self.remoteCache.setEntry(key, artifacts) 186 | 187 | def setManifest(self, manifestHash, manifest): 188 | with self.localCache.manifestLockFor(manifestHash): 189 | self.localCache.setManifest(manifestHash, manifest) 190 | self.remoteCache.setManifest(manifestHash, manifest) 191 | 192 | def getManifest(self, manifestHash): 193 | local = self.localCache.getManifest(manifestHash) 194 | if local: 195 | printTraceStatement("{} local manifest hit for {}".format(self, manifestHash)) 196 | return local 197 | remote = self.remoteCache.getManifest(manifestHash) 198 | if remote: 199 | with self.localCache.manifestLockFor(manifestHash): 200 | self.localCache.setManifest(manifestHash, remote) 201 | printTraceStatement("{} remote manifest hit for {} writing into local cache".format(self, manifestHash)) 202 | return remote 203 | return None 204 | 205 | @property 206 | def statistics(self): 207 | return self.localCache.statistics 208 | 209 | @property 210 | def configuration(self): 211 | return self.localCache.configuration 212 | 213 | @staticmethod 214 | def lockFor(_): 215 | return CacheDummyLock() 216 | 217 | @staticmethod 218 | def manifestLockFor(_): 219 | return CacheDummyLock() 220 | 221 | @property # type: ignore 222 | @contextlib.contextmanager 223 | def lock(self): 224 | with self.remoteCache.lock, self.localCache.lock: 225 | yield 226 | 227 | def clean(self, stats, maximumSize): 228 | self.localCache.clean(stats, 229 | maximumSize) 230 | -------------------------------------------------------------------------------- /clcachesrv.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.insert(0, os.path.dirname(__file__)) 5 | 6 | from clcache.server.__main__ import main 7 | main() 8 | -------------------------------------------------------------------------------- /pyinstaller/clcache_main.py: -------------------------------------------------------------------------------- 1 | from clcache.__main__ import main 2 | main() 3 | -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | pyuv>=1.3.0,<=1.4.0 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | from setuptools import setup, find_packages 6 | 7 | setup( 8 | name='clcache', 9 | description='MSVC compiler cache', 10 | author='Frerich Raabe', 11 | author_email='raabe@froglogic.com', 12 | url='https://github.com/frerich/clcache', 13 | packages=find_packages(), 14 | platforms='any', 15 | keywords=[], 16 | install_requires=[ 17 | 'typing; python_version < "3.5"', 18 | 'subprocess.run; python_version < "3.5"', 19 | 'atomicwrites', 20 | 'pymemcache', 21 | 'pyuv', 22 | ], 23 | entry_points={ 24 | 'console_scripts': [ 25 | 'clcache = clcache.__main__:main', 26 | 'clcache-server = clcache.server.__main__:main', 27 | ] 28 | }, 29 | setup_requires=[ 30 | 'setuptools_scm', 31 | ], 32 | data_files=[ 33 | ('', ('clcache.pth',)), 34 | ], 35 | use_scm_version=True) 36 | -------------------------------------------------------------------------------- /showprofilereport.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This file is part of the clcache project. 4 | # 5 | # The contents of this file are subject to the BSD 3-Clause License, the 6 | # full text of which is available in the accompanying LICENSE file at the 7 | # root directory of this project. 8 | # 9 | import os 10 | import fnmatch 11 | import pstats 12 | 13 | stats = pstats.Stats() 14 | 15 | for basedir, _, filenames in os.walk(os.getcwd()): 16 | for filename in filenames: 17 | if fnmatch.fnmatch(filename, 'clcache-*.prof'): 18 | path = os.path.join(basedir, filename) 19 | print('Reading {}...'.format(path)) 20 | stats.add(path) 21 | 22 | stats.strip_dirs() 23 | stats.sort_stats('cumulative') 24 | stats.print_stats() 25 | stats.print_callers() 26 | 27 | -------------------------------------------------------------------------------- /tests/distutils/example.c: -------------------------------------------------------------------------------- 1 | #include "Python.h" 2 | 3 | static PyObject * 4 | keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds) 5 | { 6 | int voltage; 7 | char *state = "a stiff"; 8 | char *action = "voom"; 9 | char *type = "Norwegian Blue"; 10 | 11 | static char *kwlist[] = {"voltage", "state", "action", "type", NULL}; 12 | 13 | if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist, 14 | &voltage, &state, &action, &type)) 15 | return NULL; 16 | 17 | printf("-- This parrot wouldn't %s if you put %i Volts through it.\n", 18 | action, voltage); 19 | printf("-- Lovely plumage, the %s -- It's %s!\n", type, state); 20 | 21 | Py_RETURN_NONE; 22 | } 23 | 24 | static PyMethodDef keywdarg_methods[] = { 25 | /* The cast of the function is necessary since PyCFunction values 26 | * only take two PyObject* parameters, and keywdarg_parrot() takes 27 | * three. 28 | */ 29 | {"parrot", (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS, 30 | "Print a lovely skit to standard output."}, 31 | {NULL, NULL, 0, NULL} /* sentinel */ 32 | }; 33 | 34 | static struct PyModuleDef keywdargmodule = { 35 | PyModuleDef_HEAD_INIT, 36 | "keywdarg", 37 | NULL, 38 | -1, 39 | keywdarg_methods 40 | }; 41 | 42 | PyMODINIT_FUNC 43 | PyInit_keywdarg(void) 44 | { 45 | return PyModule_Create(&keywdargmodule); 46 | } 47 | -------------------------------------------------------------------------------- /tests/distutils/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup, Extension 2 | 3 | module1 = Extension('demo', 4 | sources = ['example.c']) 5 | 6 | setup (name = 'PackageName', 7 | version = '1.0', 8 | description = 'This is a demo package', 9 | ext_modules = [module1]) 10 | -------------------------------------------------------------------------------- /tests/integrationtests/basedir/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_H 2 | #define CONSTANTS_H 3 | 4 | #include 5 | 6 | std::string databaseId() { return "opm"; } 7 | 8 | #endif // !defined(CONSTANTS_H) 9 | -------------------------------------------------------------------------------- /tests/integrationtests/basedir/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "constants.h" 3 | 4 | int main() 5 | { 6 | std::cout << databaseId() << '\n'; 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/integrationtests/compiler-encoding/.gitignore: -------------------------------------------------------------------------------- 1 | *.obj 2 | -------------------------------------------------------------------------------- /tests/integrationtests/compiler-encoding/non-ascii-message-ansi.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/integrationtests/compiler-encoding/non-ascii-message-ansi.c -------------------------------------------------------------------------------- /tests/integrationtests/compiler-encoding/non-ascii-message-utf16.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/integrationtests/compiler-encoding/non-ascii-message-utf16.c -------------------------------------------------------------------------------- /tests/integrationtests/compiler-encoding/non-ascii-message-utf8.c: -------------------------------------------------------------------------------- 1 | // file encoding: UTF-8 2 | #pragma message("Message containing special chars: ö ä ü ß â") 3 | -------------------------------------------------------------------------------- /tests/integrationtests/fibonacci.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void print_fib(int n) 4 | { 5 | int a = 0; 6 | int b = 1; 7 | while (a < n) 8 | { 9 | int old_a = a; 10 | printf("%d ", a); 11 | a = b; 12 | b = old_a + b; 13 | } 14 | } 15 | 16 | int main() 17 | { 18 | print_fib(500); 19 | printf("\n"); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /tests/integrationtests/fibonacci.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(500); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/header-change/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is auto-generated by tests 2 | version.h 3 | -------------------------------------------------------------------------------- /tests/integrationtests/header-change/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "version.h" 3 | 4 | int main() 5 | { 6 | std::cout << VERSION << std::endl; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/integrationtests/header-miss-obsolete/.gitignore: -------------------------------------------------------------------------------- 1 | *.obj 2 | 3 | # headers auto-generated by tests 4 | A.h 5 | B.h 6 | -------------------------------------------------------------------------------- /tests/integrationtests/header-miss-obsolete/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "A.h" 3 | 4 | int main() 5 | { 6 | std::cout << INFO << std::endl; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/integrationtests/header-miss/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is auto-generated by tests 2 | info.h 3 | -------------------------------------------------------------------------------- /tests/integrationtests/header-miss/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "info.h" 3 | 4 | int main() 5 | { 6 | std::cout << INFO << std::endl; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/integrationtests/hits-and-misses/.gitignore: -------------------------------------------------------------------------------- 1 | *.obj 2 | 3 | # written by the tests 4 | stable-source-with-alternating-header.h 5 | -------------------------------------------------------------------------------- /tests/integrationtests/hits-and-misses/hit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | std::cout << "A C++ file we compile twice and expect a hit" << std::endl; 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/integrationtests/hits-and-misses/stable-source-transitive-header.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "transitive-header.h" 4 | 5 | int main() 6 | { 7 | std::cout << "A C++ file we compile twice and expect a hit" << std::endl; 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /tests/integrationtests/hits-and-misses/stable-source-with-alternating-header.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "stable-source-with-alternating-header.h" 4 | 5 | int main() 6 | { 7 | std::cout << "A C++ file we compile twice and expect a hit" << std::endl; 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /tests/integrationtests/hits-and-misses/transitive-header.h: -------------------------------------------------------------------------------- 1 | #include "alternating-header.h" 2 | -------------------------------------------------------------------------------- /tests/integrationtests/minimal.cpp: -------------------------------------------------------------------------------- 1 | void f() {} 2 | -------------------------------------------------------------------------------- /tests/integrationtests/mutiple-sources/.gitignore: -------------------------------------------------------------------------------- 1 | *.obj 2 | -------------------------------------------------------------------------------- /tests/integrationtests/mutiple-sources/fibonacci01.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(1); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/mutiple-sources/fibonacci02.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(2); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/mutiple-sources/fibonacci03.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(3); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/mutiple-sources/fibonacci04.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(4); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/mutiple-sources/fibonacci05.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(5); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/.gitignore: -------------------------------------------------------------------------------- 1 | *.obj 2 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci01.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(1); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci02.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(2); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci03.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(3); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci04.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(4); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci05.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(5); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci06.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(6); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci07.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(7); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci08.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(8); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci09.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(9); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/parallel/fibonacci10.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | void print_fib(int n) 7 | { 8 | std::int32_t a = 0; 9 | std::int32_t b = 1; 10 | while (a < n) 11 | { 12 | std::cout << a << " "; 13 | 14 | auto old_a = a; 15 | a = b; 16 | b = old_a + b; 17 | } 18 | 19 | std::cout << std::endl; 20 | } 21 | 22 | } 23 | 24 | int main() 25 | { 26 | print_fib(10); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/integrationtests/precompiled-headers/Makefile: -------------------------------------------------------------------------------- 1 | RM = @del /q 2 | 3 | OBJS = myapp.obj applib.obj 4 | APP = myapp.exe 5 | 6 | # List all stable header files in the STABLEHDRS macro. 7 | STABLEHDRS = stable.h another.h 8 | 9 | # List the final header file to be precompiled here: 10 | BOUNDRY = stable.h 11 | 12 | # List header files under development here: 13 | UNSTABLEHDRS = unstable.h 14 | 15 | CPPFLAGS = /nologo /EHsc /c /W3 /Gs /MT 16 | LINKFLAGS = 17 | LIBS = 18 | 19 | $(APP): $(OBJS) 20 | link $(LINKFLAGS) /OUT:$(APP) $(OBJS) $(LIBS) 21 | 22 | # Compile myapp 23 | myapp.obj : myapp.cpp $(UNSTABLEHDRS) stable.pch 24 | $(CPP) $(CPPFLAGS) /Yu$(BOUNDRY) myapp.cpp 25 | 26 | # Compile applib 27 | #the_applib.obj : applib.cpp $(UNSTABLEHDRS) stable.pch 28 | # $(CPP) $(CPPFLAGS) /Yu$(BOUNDRY) /Fothe_applib.obj applib.cpp 29 | 30 | # Compile headers 31 | stable.pch : $(STABLEHDRS) 32 | $(CPP) $(CPPFLAGS) /Yc$(BOUNDRY) applib.cpp 33 | 34 | .PHONY: clean 35 | 36 | clean: 37 | $(RM) $(OBJS) $(APP) stable.pch 38 | -------------------------------------------------------------------------------- /tests/integrationtests/precompiled-headers/README.txt: -------------------------------------------------------------------------------- 1 | This is a sample project to test creating and using (/Yc and /Yu) 2 | of precompiled headers. 3 | 4 | It is based on "Example Code for PCH" and "Sample Makefile for PCH": 5 | * https://msdn.microsoft.com/en-us/library/de9s74bh.aspx 6 | * https://msdn.microsoft.com/en-us/library/d9b6wk21.aspx 7 | The tutorial was writte a decade ago using MSVC 2005, so the author 8 | had to adjust a lot of things to make it run using modern compilers. 9 | 10 | It is suspected that the original content from Microsoft does not 11 | satisfy the threshold of originality. All modifications are licensed 12 | the same way as the rest of the clcache project. 13 | -------------------------------------------------------------------------------- /tests/integrationtests/precompiled-headers/another.h: -------------------------------------------------------------------------------- 1 | // ANOTHER.H : Contains the interface to code that is not 2 | // likely to change. 3 | // 4 | #ifndef ANOTHER_H 5 | #define ANOTHER_H 6 | 7 | #include 8 | void savemoretime( void ); 9 | 10 | #endif // ANOTHER_H 11 | -------------------------------------------------------------------------------- /tests/integrationtests/precompiled-headers/applib.cpp: -------------------------------------------------------------------------------- 1 | // APPLIB.CPP : This file contains the code that implements 2 | // the interface code declared in the header 3 | // files STABLE.H, ANOTHER.H, and UNSTABLE.H. 4 | // 5 | #include "another.h" 6 | #include "stable.h" 7 | #include "unstable.h" 8 | 9 | // The following code represents code that is deemed stable and 10 | // not likely to change. The associated interface code is 11 | // precompiled. In this example, the header files STABLE.H and 12 | // ANOTHER.H are precompiled. 13 | void savetime( void ) 14 | { 15 | std::cout << "Why recompile stable code?" << std::endl; 16 | } 17 | 18 | void savemoretime( void ) 19 | { 20 | std::cout << "Why, indeed?" << std::endl; 21 | } 22 | 23 | // The following code represents code that is still under 24 | // development. The associated header file is not precompiled. 25 | void notstable( void ) 26 | { 27 | std::cout << "Unstable code requires" 28 | << " frequent recompilation." << std::endl; 29 | } 30 | -------------------------------------------------------------------------------- /tests/integrationtests/precompiled-headers/myapp.cpp: -------------------------------------------------------------------------------- 1 | // MYAPP.CPP : Sample application 2 | // All precompiled code other than the file listed 3 | // in the makefile's BOUNDRY macro (stable.h in 4 | // this example) must be included before the file 5 | // listed in the BOUNDRY macro. Unstable code must 6 | // be included after the precompiled code. 7 | // 8 | #include "another.h" 9 | #include "stable.h" 10 | #include "unstable.h" 11 | 12 | int main() 13 | { 14 | savetime(); 15 | savemoretime(); 16 | notstable(); 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /tests/integrationtests/precompiled-headers/stable.h: -------------------------------------------------------------------------------- 1 | // STABLE.H : Contains the interface to code that is not likely 2 | // to change. List code that is likely to change 3 | // in the makefile's STABLEHDRS macro. 4 | // 5 | #ifndef STABLE_H 6 | #define STABLE_H 7 | 8 | #include 9 | void savetime( void ); 10 | 11 | #endif // STABLE_H 12 | -------------------------------------------------------------------------------- /tests/integrationtests/precompiled-headers/unstable.h: -------------------------------------------------------------------------------- 1 | // UNSTABLE.H : Contains the interface to code that is 2 | // likely to change. As the code in a header 3 | // file becomes stable, remove the header file 4 | // from the makefile's UNSTABLEHDR macro and list 5 | // it in the STABLEHDRS macro. 6 | // 7 | #ifndef UNSTABLE_H 8 | #define UNSTABLE_H 9 | 10 | #include 11 | void notstable( void ); 12 | 13 | #endif // UNSTABLE_H 14 | -------------------------------------------------------------------------------- /tests/integrationtests/recompile1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | std::cout << "Sample file for recompile test." << std::endl; 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/integrationtests/recompile2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | std::cout << "Sample file for recompile test." << std::endl; 6 | std::cout << "This time we set a specific object file in the same dir" << std::endl; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/integrationtests/recompile3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | std::cout << "Sample file for recompile test." << std::endl; 6 | std::cout << "This time we set a specific object file in another dir" << std::endl; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/output/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/output/.gitkeep -------------------------------------------------------------------------------- /tests/performancetests/concurrency/file01.cpp: -------------------------------------------------------------------------------- 1 | // Include a couple of headers so that the compiler has some work to do 2 | #include 3 | #include 4 | 5 | static void f() 6 | { 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This file is part of the clcache project. 4 | # 5 | # The contents of this file are subject to the BSD 3-Clause License, the 6 | # full text of which is available in the accompanying LICENSE file at the 7 | # root directory of this project. 8 | # 9 | # In Python unittests are always members, not functions. Silence lint in this file. 10 | # pylint: disable=no-self-use 11 | # 12 | from contextlib import contextmanager 13 | import copy 14 | import glob 15 | import os 16 | import shutil 17 | import subprocess 18 | import sys 19 | import tempfile 20 | import unittest 21 | import time 22 | import pytest 23 | 24 | from clcache import __main__ as clcache 25 | 26 | PYTHON_BINARY = sys.executable 27 | ASSETS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "integrationtests") 28 | DISTUTILS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "distutils") 29 | CLCACHE_MEMCACHED = "CLCACHE_MEMCACHED" in os.environ 30 | MONKEY_LOADED = "clcache.monkey" in sys.modules 31 | 32 | # pytest-cov note: subprocesses are coverage tested by default with some limitations 33 | # "For subprocess measurement environment variables must make it from the main process to the 34 | # subprocess. The python used by the subprocess must have pytest-cov installed. The subprocess 35 | # must do normal site initialisation so that the environment variables can be detected and 36 | # coverage started." 37 | CLCACHE_CMD = ['clcache'] 38 | 39 | 40 | @contextmanager 41 | def cd(targetDirectory): 42 | oldDirectory = os.getcwd() 43 | os.chdir(os.path.expanduser(targetDirectory)) 44 | try: 45 | yield 46 | finally: 47 | os.chdir(oldDirectory) 48 | 49 | 50 | class TestCommandLineArguments(unittest.TestCase): 51 | def testValidMaxSize(self): 52 | with tempfile.TemporaryDirectory() as tempDir: 53 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 54 | validValues = ["1", " 10", "42 ", "22222222"] 55 | for value in validValues: 56 | cmd = CLCACHE_CMD + ["-M", value] 57 | self.assertEqual( 58 | subprocess.call(cmd, env=customEnv), 59 | 0, 60 | "Command must not fail for max size: '" + value + "'") 61 | 62 | def testInvalidMaxSize(self): 63 | invalidValues = ["ababa", "-1", "0", "1000.0"] 64 | for value in invalidValues: 65 | cmd = CLCACHE_CMD + ["-M", value] 66 | self.assertNotEqual(subprocess.call(cmd), 0, "Command must fail for max size: '" + value + "'") 67 | 68 | def testPrintStatistics(self): 69 | with tempfile.TemporaryDirectory() as tempDir: 70 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 71 | cmd = CLCACHE_CMD + ["-s"] 72 | self.assertEqual( 73 | subprocess.call(cmd, env=customEnv), 74 | 0, 75 | "Command must be able to print statistics") 76 | 77 | class TestDistutils(unittest.TestCase): 78 | @pytest.mark.skipif(not MONKEY_LOADED, reason="Monkeypatch not loaded") 79 | @pytest.mark.skipif(CLCACHE_MEMCACHED, reason="Fails with memcached") 80 | def testBasicCompileCc(self): 81 | with cd(DISTUTILS_DIR): 82 | customEnv = dict(os.environ, USE_CLCACHE="1") 83 | try: 84 | output = subprocess.check_output( 85 | [sys.executable, 'setup.py', 'build'], 86 | env=customEnv, 87 | stderr=subprocess.STDOUT) 88 | except subprocess.CalledProcessError as processError: 89 | output = processError.output 90 | output = output.decode("utf-8") 91 | 92 | print(output) 93 | assert "__main__.py" in output 94 | 95 | 96 | class TestCompileRuns(unittest.TestCase): 97 | def testBasicCompileCc(self): 98 | cmd = CLCACHE_CMD + ["/nologo", "/c", os.path.join(ASSETS_DIR, "fibonacci.c")] 99 | subprocess.check_call(cmd) 100 | 101 | def testBasicCompileCpp(self): 102 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", os.path.join(ASSETS_DIR, "fibonacci.cpp")] 103 | subprocess.check_call(cmd) 104 | 105 | def testCompileLinkRunCc(self): 106 | with cd(ASSETS_DIR): 107 | cmd = CLCACHE_CMD + ["/nologo", "/c", "fibonacci.c", "/Fofibonacci_c.obj"] 108 | subprocess.check_call(cmd) 109 | cmd = ["link", "/nologo", "/OUT:fibonacci_c.exe", "fibonacci_c.obj"] 110 | subprocess.check_call(cmd) 111 | cmd = ["fibonacci_c.exe"] 112 | output = subprocess.check_output(cmd).decode("ascii").strip() 113 | self.assertEqual(output, "0 1 1 2 3 5 8 13 21 34 55 89 144 233 377") 114 | 115 | def testCompileLinkRunCpp(self): 116 | with cd(ASSETS_DIR): 117 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", "fibonacci.cpp", "/Fofibonacci_cpp.obj"] 118 | subprocess.check_call(cmd) 119 | cmd = ["link", "/nologo", "/OUT:fibonacci_cpp.exe", "fibonacci_cpp.obj"] 120 | subprocess.check_call(cmd) 121 | cmd = ["fibonacci_cpp.exe"] 122 | output = subprocess.check_output(cmd).decode("ascii").strip() 123 | self.assertEqual(output, "0 1 1 2 3 5 8 13 21 34 55 89 144 233 377") 124 | 125 | def testRecompile(self): 126 | cmd = CLCACHE_CMD + [ 127 | "/nologo", 128 | "/EHsc", 129 | "/c", 130 | os.path.join(ASSETS_DIR, "recompile1.cpp") 131 | ] 132 | subprocess.check_call(cmd) # Compile once 133 | subprocess.check_call(cmd) # Compile again 134 | 135 | def testRecompileObjectSetSameDir(self): 136 | cmd = CLCACHE_CMD + [ 137 | "/nologo", 138 | "/EHsc", 139 | "/c", 140 | os.path.join(ASSETS_DIR, "recompile2.cpp"), 141 | "/Forecompile2_custom_object_name.obj" 142 | ] 143 | subprocess.check_call(cmd) # Compile once 144 | subprocess.check_call(cmd) # Compile again 145 | 146 | def testRecompileObjectSetOtherDir(self): 147 | cmd = CLCACHE_CMD + [ 148 | "/nologo", 149 | "/EHsc", 150 | "/c", 151 | os.path.join(ASSETS_DIR, "recompile3.cpp"), 152 | "/Fotests\\output\\recompile2_custom_object_name.obj" 153 | ] 154 | subprocess.check_call(cmd) # Compile once 155 | subprocess.check_call(cmd) # Compile again 156 | 157 | def testPipedOutput(self): 158 | def debugLinebreaks(text): 159 | out = [] 160 | lines = text.splitlines(True) 161 | for line in lines: 162 | out.append(line.replace("\r", "").replace("\n", "")) 163 | return "\n".join(out) 164 | 165 | commands = [ 166 | # just show cl.exe version 167 | { 168 | 'directMode': True, 169 | 'compileFails': False, 170 | 'cmd': CLCACHE_CMD 171 | }, 172 | # passed to real compiler 173 | { 174 | 'directMode': True, 175 | 'compileFails': False, 176 | 'cmd': CLCACHE_CMD + ['/E', 'fibonacci.c'] 177 | }, 178 | # Unique parameters ensure this was not cached yet (at least in CI) 179 | { 180 | 'directMode': True, 181 | 'compileFails': False, 182 | 'cmd': CLCACHE_CMD + ['/wd4267', '/wo4018', '/c', 'fibonacci.c'] 183 | }, 184 | # Cache hit 185 | { 186 | 'directMode': True, 187 | 'compileFails': False, 188 | 'cmd': CLCACHE_CMD + ['/wd4267', '/wo4018', '/c', 'fibonacci.c'] 189 | }, 190 | # Unique parameters ensure this was not cached yet (at least in CI) 191 | { 192 | 'directMode': False, 193 | 'compileFails': False, 194 | 'cmd': CLCACHE_CMD + ['/wd4269', '/wo4019', '/c', 'fibonacci.c'] 195 | }, 196 | # Cache hit 197 | { 198 | 'directMode': False, 199 | 'compileFails': False, 200 | 'cmd': CLCACHE_CMD + ['/wd4269', '/wo4019', '/c', 'fibonacci.c'] 201 | }, 202 | # Compile fails in NODIRECT mode. This will trigger a preprocessor fail via 203 | # cl.exe /EP /w1NONNUMERIC fibonacci.c 204 | { 205 | 'directMode': False, 206 | 'compileFails': True, 207 | 'cmd': CLCACHE_CMD + ['/w1NONNUMERIC', '/c', 'fibonacci.c'] 208 | }, 209 | ] 210 | 211 | for command in commands: 212 | with cd(ASSETS_DIR): 213 | if command['directMode']: 214 | testEnvironment = dict(os.environ) 215 | else: 216 | testEnvironment = dict(os.environ, CLCACHE_NODIRECT="1") 217 | 218 | proc = subprocess.Popen(command['cmd'], env=testEnvironment, 219 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 220 | stdoutBinary, stderrBinary = proc.communicate() 221 | stdout = stdoutBinary.decode(clcache.CL_DEFAULT_CODEC) 222 | stderr = stderrBinary.decode(clcache.CL_DEFAULT_CODEC) 223 | 224 | if not command['compileFails'] and proc.returncode != 0: 225 | self.fail( 226 | 'Compile failed with return code {}.\n'.format(proc.returncode) + 227 | 'Command: {}\nEnvironment: {}\nStdout: {}\nStderr: {}'.format( 228 | command['cmd'], testEnvironment, stdout, stderr)) 229 | 230 | if command['compileFails'] and proc.returncode == 0: 231 | self.fail('Compile was expected to fail but did not. {}'.format(command['cmd'])) 232 | 233 | for output in [stdout, stderr]: 234 | if output: 235 | self.assertTrue('\r\r\n' not in output, 236 | 'Output has duplicated CR.\nCommand: {}\nOutput: {}'.format( 237 | command['cmd'], debugLinebreaks(output))) 238 | # Just to be sure we have newlines 239 | self.assertTrue('\r\n' in output, 240 | 'Output has no CRLF.\nCommand: {}\nOutput: {}'.format( 241 | command['cmd'], debugLinebreaks(output))) 242 | 243 | def testBasicCompileCcSpecifiedCompiler(self): 244 | clCommand = clcache.findCompilerBinary() 245 | self.assertIsNotNone(clCommand, "Could not locate cl.exe") 246 | self.assertTrue(clCommand.endswith(".exe"), "Compiler executable is not an exe file") 247 | cmd = CLCACHE_CMD + [clCommand, "/nologo", "/c", os.path.join(ASSETS_DIR, "fibonacci.c")] 248 | subprocess.check_call(cmd) 249 | 250 | def testBasicCompileCppSpecifiedCompiler(self): 251 | clCommand = clcache.findCompilerBinary() 252 | self.assertIsNotNone(clCommand, "Could not locate cl.exe") 253 | self.assertTrue(clCommand.endswith(".exe"), "Compiler executable is not an exe file") 254 | cmd = CLCACHE_CMD + [clCommand, "/nologo", "/EHsc", "/c", os.path.join(ASSETS_DIR, "fibonacci.cpp")] 255 | subprocess.check_call(cmd) 256 | 257 | class TestCompilerEncoding(unittest.TestCase): 258 | def testNonAsciiMessage(self): 259 | with cd(os.path.join(ASSETS_DIR, "compiler-encoding")): 260 | for filename in ['non-ascii-message-ansi.c', 'non-ascii-message-utf16.c']: 261 | cmd = CLCACHE_CMD + ["/nologo", "/c", filename] 262 | subprocess.check_call(cmd) 263 | 264 | 265 | class TestHits(unittest.TestCase): 266 | def testHitsSimple(self): 267 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")): 268 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", 'hit.cpp'] 269 | subprocess.check_call(cmd) # Ensure it has been compiled before 270 | 271 | cache = clcache.Cache() 272 | with cache.statistics as stats: 273 | oldHits = stats.numCacheHits() 274 | subprocess.check_call(cmd) # This must hit now 275 | with cache.statistics as stats: 276 | newHits = stats.numCacheHits() 277 | self.assertEqual(newHits, oldHits + 1) 278 | 279 | def testAlternatingHeadersHit(self): 280 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")), tempfile.TemporaryDirectory() as tempDir: 281 | cache = clcache.Cache(tempDir) 282 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 283 | baseCmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 284 | 285 | with cache.statistics as stats: 286 | self.assertEqual(stats.numCacheHits(), 0) 287 | self.assertEqual(stats.numCacheMisses(), 0) 288 | self.assertEqual(stats.numCacheEntries(), 0) 289 | 290 | # VERSION 1 291 | with open('stable-source-with-alternating-header.h', 'w') as f: 292 | f.write("#define VERSION 1\n") 293 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 294 | 295 | with cache.statistics as stats: 296 | self.assertEqual(stats.numCacheHits(), 0) 297 | self.assertEqual(stats.numCacheMisses(), 1) 298 | self.assertEqual(stats.numCacheEntries(), 1) 299 | 300 | # VERSION 2 301 | with open('stable-source-with-alternating-header.h', 'w') as f: 302 | f.write("#define VERSION 2\n") 303 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 304 | 305 | with cache.statistics as stats: 306 | self.assertEqual(stats.numCacheHits(), 0) 307 | self.assertEqual(stats.numCacheMisses(), 2) 308 | self.assertEqual(stats.numCacheEntries(), 2) 309 | 310 | # VERSION 1 again 311 | with open('stable-source-with-alternating-header.h', 'w') as f: 312 | f.write("#define VERSION 1\n") 313 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 314 | 315 | with cache.statistics as stats: 316 | self.assertEqual(stats.numCacheHits(), 1) 317 | self.assertEqual(stats.numCacheMisses(), 2) 318 | self.assertEqual(stats.numCacheEntries(), 2) 319 | 320 | # VERSION 2 again 321 | with open('stable-source-with-alternating-header.h', 'w') as f: 322 | f.write("#define VERSION 1\n") 323 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 324 | 325 | with cache.statistics as stats: 326 | self.assertEqual(stats.numCacheHits(), 2) 327 | self.assertEqual(stats.numCacheMisses(), 2) 328 | self.assertEqual(stats.numCacheEntries(), 2) 329 | 330 | def testRemovedHeader(self): 331 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")), tempfile.TemporaryDirectory() as tempDir: 332 | cache = clcache.Cache(tempDir) 333 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 334 | baseCmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 335 | 336 | with cache.statistics as stats: 337 | self.assertEqual(stats.numCacheHits(), 0) 338 | self.assertEqual(stats.numCacheMisses(), 0) 339 | self.assertEqual(stats.numCacheEntries(), 0) 340 | 341 | # VERSION 1 342 | with open('stable-source-with-alternating-header.h', 'w') as f: 343 | f.write("#define VERSION 1\n") 344 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 345 | 346 | with cache.statistics as stats: 347 | self.assertEqual(stats.numCacheHits(), 0) 348 | self.assertEqual(stats.numCacheMisses(), 1) 349 | self.assertEqual(stats.numCacheEntries(), 1) 350 | 351 | # Remove header, trigger the compiler which should fail 352 | os.remove('stable-source-with-alternating-header.h') 353 | with self.assertRaises(subprocess.CalledProcessError): 354 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 355 | 356 | with cache.statistics as stats: 357 | self.assertEqual(stats.numCacheHits(), 0) 358 | self.assertEqual(stats.numCacheMisses(), 2) 359 | self.assertEqual(stats.numCacheEntries(), 1) 360 | 361 | # VERSION 1 again 362 | with open('stable-source-with-alternating-header.h', 'w') as f: 363 | f.write("#define VERSION 1\n") 364 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 365 | 366 | with cache.statistics as stats: 367 | self.assertEqual(stats.numCacheHits(), 1) 368 | self.assertEqual(stats.numCacheMisses(), 2) 369 | self.assertEqual(stats.numCacheEntries(), 1) 370 | 371 | # Remove header again, trigger the compiler which should fail 372 | os.remove('stable-source-with-alternating-header.h') 373 | with self.assertRaises(subprocess.CalledProcessError): 374 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 375 | 376 | with cache.statistics as stats: 377 | self.assertEqual(stats.numCacheHits(), 1) 378 | self.assertEqual(stats.numCacheMisses(), 3) 379 | self.assertEqual(stats.numCacheEntries(), 1) 380 | 381 | def testAlternatingTransitiveHeader(self): 382 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")), tempfile.TemporaryDirectory() as tempDir: 383 | cache = clcache.Cache(tempDir) 384 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 385 | baseCmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 386 | 387 | with cache.statistics as stats: 388 | self.assertEqual(stats.numCacheHits(), 0) 389 | self.assertEqual(stats.numCacheMisses(), 0) 390 | self.assertEqual(stats.numCacheEntries(), 0) 391 | 392 | # VERSION 1 393 | with open('alternating-header.h', 'w') as f: 394 | f.write("#define VERSION 1\n") 395 | subprocess.check_call(baseCmd + ["stable-source-transitive-header.cpp"], env=customEnv) 396 | 397 | with cache.statistics as stats: 398 | self.assertEqual(stats.numCacheHits(), 0) 399 | self.assertEqual(stats.numCacheMisses(), 1) 400 | self.assertEqual(stats.numCacheEntries(), 1) 401 | 402 | # VERSION 2 403 | with open('alternating-header.h', 'w') as f: 404 | f.write("#define VERSION 2\n") 405 | subprocess.check_call(baseCmd + ["stable-source-transitive-header.cpp"], env=customEnv) 406 | 407 | with cache.statistics as stats: 408 | self.assertEqual(stats.numCacheHits(), 0) 409 | self.assertEqual(stats.numCacheMisses(), 2) 410 | self.assertEqual(stats.numCacheEntries(), 2) 411 | 412 | # VERSION 1 again 413 | with open('alternating-header.h', 'w') as f: 414 | f.write("#define VERSION 1\n") 415 | subprocess.check_call(baseCmd + ["stable-source-transitive-header.cpp"], env=customEnv) 416 | 417 | with cache.statistics as stats: 418 | self.assertEqual(stats.numCacheHits(), 1) 419 | self.assertEqual(stats.numCacheMisses(), 2) 420 | self.assertEqual(stats.numCacheEntries(), 2) 421 | 422 | # VERSION 2 again 423 | with open('alternating-header.h', 'w') as f: 424 | f.write("#define VERSION 1\n") 425 | subprocess.check_call(baseCmd + ["stable-source-transitive-header.cpp"], env=customEnv) 426 | 427 | with cache.statistics as stats: 428 | self.assertEqual(stats.numCacheHits(), 2) 429 | self.assertEqual(stats.numCacheMisses(), 2) 430 | self.assertEqual(stats.numCacheEntries(), 2) 431 | 432 | def testRemovedTransitiveHeader(self): 433 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")), tempfile.TemporaryDirectory() as tempDir: 434 | cache = clcache.Cache(tempDir) 435 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 436 | baseCmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 437 | 438 | with cache.statistics as stats: 439 | self.assertEqual(stats.numCacheHits(), 0) 440 | self.assertEqual(stats.numCacheMisses(), 0) 441 | self.assertEqual(stats.numCacheEntries(), 0) 442 | 443 | # VERSION 1 444 | with open('alternating-header.h', 'w') as f: 445 | f.write("#define VERSION 1\n") 446 | subprocess.check_call(baseCmd + ["stable-source-transitive-header.cpp"], env=customEnv) 447 | 448 | with cache.statistics as stats: 449 | self.assertEqual(stats.numCacheHits(), 0) 450 | self.assertEqual(stats.numCacheMisses(), 1) 451 | self.assertEqual(stats.numCacheEntries(), 1) 452 | 453 | # Remove header, trigger the compiler which should fail 454 | os.remove('alternating-header.h') 455 | with self.assertRaises(subprocess.CalledProcessError): 456 | subprocess.check_call(baseCmd + ["stable-source-transitive-header.cpp"], env=customEnv) 457 | 458 | with cache.statistics as stats: 459 | self.assertEqual(stats.numCacheHits(), 0) 460 | self.assertEqual(stats.numCacheMisses(), 2) 461 | self.assertEqual(stats.numCacheEntries(), 1) 462 | 463 | # VERSION 1 again 464 | with open('alternating-header.h', 'w') as f: 465 | f.write("#define VERSION 1\n") 466 | subprocess.check_call(baseCmd + ["stable-source-transitive-header.cpp"], env=customEnv) 467 | 468 | with cache.statistics as stats: 469 | self.assertEqual(stats.numCacheHits(), 1) 470 | self.assertEqual(stats.numCacheMisses(), 2) 471 | self.assertEqual(stats.numCacheEntries(), 1) 472 | 473 | # Remove header again, trigger the compiler which should fail 474 | os.remove('alternating-header.h') 475 | with self.assertRaises(subprocess.CalledProcessError): 476 | subprocess.check_call(baseCmd + ["stable-source-transitive-header.cpp"], env=customEnv) 477 | 478 | with cache.statistics as stats: 479 | self.assertEqual(stats.numCacheHits(), 1) 480 | self.assertEqual(stats.numCacheMisses(), 3) 481 | self.assertEqual(stats.numCacheEntries(), 1) 482 | 483 | def testAlternatingIncludeOrder(self): 484 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")), tempfile.TemporaryDirectory() as tempDir: 485 | cache = clcache.Cache(tempDir) 486 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 487 | baseCmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 488 | 489 | with open('A.h', 'w') as header: 490 | header.write('#define A 1\n') 491 | with open('B.h', 'w') as header: 492 | header.write('#define B 1\n') 493 | 494 | with cache.statistics as stats: 495 | self.assertEqual(stats.numCacheHits(), 0) 496 | self.assertEqual(stats.numCacheMisses(), 0) 497 | self.assertEqual(stats.numCacheEntries(), 0) 498 | 499 | # VERSION 1 500 | with open('stable-source-with-alternating-header.h', 'w') as f: 501 | f.write('#include "A.h"\n') 502 | f.write('#include "B.h"\n') 503 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 504 | 505 | with cache.statistics as stats: 506 | self.assertEqual(stats.numCacheHits(), 0) 507 | self.assertEqual(stats.numCacheMisses(), 1) 508 | self.assertEqual(stats.numCacheEntries(), 1) 509 | 510 | # VERSION 2 511 | with open('stable-source-with-alternating-header.h', 'w') as f: 512 | f.write('#include "B.h"\n') 513 | f.write('#include "A.h"\n') 514 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 515 | 516 | with cache.statistics as stats: 517 | self.assertEqual(stats.numCacheHits(), 0) 518 | self.assertEqual(stats.numCacheMisses(), 2) 519 | self.assertEqual(stats.numCacheEntries(), 2) 520 | 521 | # VERSION 1 again 522 | with open('stable-source-with-alternating-header.h', 'w') as f: 523 | f.write('#include "A.h"\n') 524 | f.write('#include "B.h"\n') 525 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 526 | 527 | with cache.statistics as stats: 528 | self.assertEqual(stats.numCacheHits(), 1) 529 | self.assertEqual(stats.numCacheMisses(), 2) 530 | self.assertEqual(stats.numCacheEntries(), 2) 531 | 532 | # VERSION 2 again 533 | with open('stable-source-with-alternating-header.h', 'w') as f: 534 | f.write('#include "B.h"\n') 535 | f.write('#include "A.h"\n') 536 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 537 | 538 | with cache.statistics as stats: 539 | self.assertEqual(stats.numCacheHits(), 2) 540 | self.assertEqual(stats.numCacheMisses(), 2) 541 | self.assertEqual(stats.numCacheEntries(), 2) 542 | 543 | def testRepeatedIncludes(self): 544 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")), tempfile.TemporaryDirectory() as tempDir: 545 | cache = clcache.Cache(tempDir) 546 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 547 | baseCmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 548 | 549 | with open('A.h', 'w') as header: 550 | header.write('#define A 1\n') 551 | with open('B.h', 'w') as header: 552 | header.write('#define B 1\n') 553 | 554 | with cache.statistics as stats: 555 | self.assertEqual(stats.numCacheHits(), 0) 556 | self.assertEqual(stats.numCacheMisses(), 0) 557 | self.assertEqual(stats.numCacheEntries(), 0) 558 | 559 | # VERSION 1 560 | with open('stable-source-with-alternating-header.h', 'w') as f: 561 | f.write('#include "A.h"\n') 562 | f.write('#include "A.h"\n') 563 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 564 | 565 | with cache.statistics as stats: 566 | self.assertEqual(stats.numCacheHits(), 0) 567 | self.assertEqual(stats.numCacheMisses(), 1) 568 | self.assertEqual(stats.numCacheEntries(), 1) 569 | 570 | # VERSION 2 571 | with open('stable-source-with-alternating-header.h', 'w') as f: 572 | f.write('#include "A.h"\n') 573 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 574 | 575 | with cache.statistics as stats: 576 | self.assertEqual(stats.numCacheHits(), 0) 577 | self.assertEqual(stats.numCacheMisses(), 2) 578 | self.assertEqual(stats.numCacheEntries(), 2) 579 | 580 | # VERSION 1 again 581 | with open('stable-source-with-alternating-header.h', 'w') as f: 582 | f.write('#include "A.h"\n') 583 | f.write('#include "A.h"\n') 584 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 585 | 586 | with cache.statistics as stats: 587 | self.assertEqual(stats.numCacheHits(), 1) 588 | self.assertEqual(stats.numCacheMisses(), 2) 589 | self.assertEqual(stats.numCacheEntries(), 2) 590 | 591 | # VERSION 2 again 592 | with open('stable-source-with-alternating-header.h', 'w') as f: 593 | f.write('#include "A.h"\n') 594 | subprocess.check_call(baseCmd + ["stable-source-with-alternating-header.cpp"], env=customEnv) 595 | 596 | with cache.statistics as stats: 597 | self.assertEqual(stats.numCacheHits(), 2) 598 | self.assertEqual(stats.numCacheMisses(), 2) 599 | self.assertEqual(stats.numCacheEntries(), 2) 600 | 601 | 602 | class TestPrecompiledHeaders(unittest.TestCase): 603 | def testSampleproject(self): 604 | with cd(os.path.join(ASSETS_DIR, "precompiled-headers")): 605 | cpp = subprocess.list2cmdline(CLCACHE_CMD) 606 | 607 | testEnvironment = dict(os.environ, CPP=cpp) 608 | 609 | cmd = ["nmake", "/nologo"] 610 | subprocess.check_call(cmd, env=testEnvironment) 611 | 612 | cmd = ["myapp.exe"] 613 | subprocess.check_call(cmd) 614 | 615 | cmd = ["nmake", "/nologo", "clean"] 616 | subprocess.check_call(cmd, env=testEnvironment) 617 | 618 | cmd = ["nmake", "/nologo"] 619 | subprocess.check_call(cmd, env=testEnvironment) 620 | 621 | 622 | class TestHeaderChange(unittest.TestCase): 623 | def _clean(self): 624 | # It seems that subprocess.check_output() occasionally returns before 625 | # windows fully releases the respective executable. 626 | # This pause prevents failing tests because of missing permissions to remove the file. 627 | time.sleep(.1) 628 | 629 | if os.path.isfile("main.obj"): 630 | os.remove("main.obj") 631 | if os.path.isfile("main.exe"): 632 | os.remove("main.exe") 633 | 634 | def _compileAndLink(self, environment): 635 | cmdCompile = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", "main.cpp"] 636 | cmdLink = ["link", "/nologo", "/OUT:main.exe", "main.obj"] 637 | subprocess.check_call(cmdCompile, env=environment) 638 | subprocess.check_call(cmdLink, env=environment) 639 | 640 | def _performTest(self, env): 641 | with cd(os.path.join(ASSETS_DIR, "header-change")): 642 | self._clean() 643 | 644 | with open("version.h", "w") as header: 645 | header.write("#define VERSION 1") 646 | 647 | self._compileAndLink(env) 648 | cmdRun = [os.path.abspath("main.exe")] 649 | output = subprocess.check_output(cmdRun).decode("ascii").strip() 650 | self.assertEqual(output, "1") 651 | 652 | self._clean() 653 | 654 | with open("version.h", "w") as header: 655 | header.write("#define VERSION 2") 656 | 657 | self._compileAndLink(env) 658 | cmdRun = [os.path.abspath("main.exe")] 659 | output = subprocess.check_output(cmdRun).decode("ascii").strip() 660 | self.assertEqual(output, "2") 661 | 662 | def testDirect(self): 663 | self._performTest(dict(os.environ)) 664 | 665 | def testNoDirect(self): 666 | self._performTest(dict(os.environ, CLCACHE_NODIRECT="1")) 667 | 668 | 669 | class TestHeaderMiss(unittest.TestCase): 670 | # When a required header disappears, we must fall back to real compiler 671 | # complaining about the miss 672 | def testRequiredHeaderDisappears(self): 673 | with cd(os.path.join(ASSETS_DIR, "header-miss")), tempfile.TemporaryDirectory() as tempDir: 674 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 675 | compileCmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", "main.cpp"] 676 | 677 | with open("info.h", "w") as header: 678 | header.write("#define INFO 1337\n") 679 | subprocess.check_call(compileCmd, env=customEnv) 680 | 681 | os.remove("info.h") 682 | 683 | # real compiler fails 684 | process = subprocess.Popen(compileCmd, stdout=subprocess.PIPE, env=customEnv) 685 | stdout, _ = process.communicate() 686 | self.assertEqual(process.returncode, 2) 687 | self.assertTrue("C1083" in stdout.decode(clcache.CL_DEFAULT_CODEC)) 688 | 689 | # When a header included by another header becomes obsolete and disappers, 690 | # we must fall back to real compiler. 691 | def testObsoleteHeaderDisappears(self): 692 | # A includes B 693 | with cd(os.path.join(ASSETS_DIR, "header-miss-obsolete")), tempfile.TemporaryDirectory() as tempDir: 694 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 695 | compileCmd = CLCACHE_CMD + ["/I.", "/nologo", "/EHsc", "/c", "main.cpp"] 696 | cache = clcache.Cache(tempDir) 697 | 698 | with open("A.h", "w") as header: 699 | header.write('#define INFO 1337\n') 700 | header.write('#include "B.h"\n') 701 | with open("B.h", "w") as header: 702 | header.write('#define SOMETHING 1\n') 703 | 704 | subprocess.check_call(compileCmd, env=customEnv) 705 | 706 | with cache.statistics as stats: 707 | headerChangedMisses1 = stats.numHeaderChangedMisses() 708 | hits1 = stats.numCacheHits() 709 | misses1 = stats.numCacheMisses() 710 | 711 | # Make include B.h obsolete 712 | with open("A.h", "w") as header: 713 | header.write('#define INFO 1337\n') 714 | header.write('\n') 715 | os.remove("B.h") 716 | 717 | subprocess.check_call(compileCmd, env=customEnv) 718 | 719 | with cache.statistics as stats: 720 | headerChangedMisses2 = stats.numHeaderChangedMisses() 721 | hits2 = stats.numCacheHits() 722 | misses2 = stats.numCacheMisses() 723 | 724 | self.assertEqual(headerChangedMisses2, headerChangedMisses1+1) 725 | self.assertEqual(misses2, misses1+1) 726 | self.assertEqual(hits2, hits1) 727 | 728 | # Ensure the new manifest was stored 729 | subprocess.check_call(compileCmd, env=customEnv) 730 | 731 | with cache.statistics as stats: 732 | headerChangedMisses3 = stats.numHeaderChangedMisses() 733 | hits3 = stats.numCacheHits() 734 | misses3 = stats.numCacheMisses() 735 | 736 | self.assertEqual(headerChangedMisses3, headerChangedMisses2) 737 | self.assertEqual(misses3, misses2) 738 | self.assertEqual(hits3, hits2+1) 739 | 740 | class RunParallelBase: 741 | def _zeroStats(self): 742 | subprocess.check_call(CLCACHE_CMD + ["-z"]) 743 | 744 | def _buildAll(self): 745 | processes = [] 746 | 747 | for sourceFile in glob.glob('*.cpp'): 748 | print("Starting compilation of {}".format(sourceFile)) 749 | cxxflags = ["/c", "/nologo", "/EHsc"] 750 | cmd = CLCACHE_CMD + cxxflags + [sourceFile] 751 | processes.append(subprocess.Popen(cmd, env=self.env)) 752 | 753 | for p in processes: 754 | p.wait() 755 | 756 | def _createEnv(self, directory): 757 | return dict(self.env, CLCACHE_DIR=directory) 758 | 759 | # Test counting of misses and hits in a parallel environment 760 | def testParallel(self): 761 | with cd(os.path.join(ASSETS_DIR, "parallel")): 762 | self._zeroStats() 763 | 764 | # Compile first time 765 | self._buildAll() 766 | 767 | cache = clcache.Cache() 768 | with cache.statistics as stats: 769 | hits = stats.numCacheHits() 770 | misses = stats.numCacheMisses() 771 | self.assertEqual(hits + misses, 10) 772 | 773 | # Compile second time 774 | self._buildAll() 775 | 776 | cache = clcache.Cache() 777 | with cache.statistics as stats: 778 | hits = stats.numCacheHits() 779 | misses = stats.numCacheMisses() 780 | self.assertEqual(hits + misses, 20) 781 | 782 | def testHitViaMpSequential(self): 783 | with cd(os.path.join(ASSETS_DIR, "parallel")), tempfile.TemporaryDirectory() as tempDir: 784 | cache = clcache.Cache(tempDir) 785 | 786 | customEnv = self._createEnv(tempDir) 787 | 788 | with cache.statistics as stats: 789 | self.assertEqual(stats.numCacheHits(), 0) 790 | self.assertEqual(stats.numCacheMisses(), 0) 791 | self.assertEqual(stats.numCacheEntries(), 0) 792 | 793 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 794 | 795 | # Compile random file, filling cache 796 | subprocess.check_call(cmd + ["fibonacci01.cpp"], env=customEnv) 797 | 798 | with cache.statistics as stats: 799 | self.assertEqual(stats.numCacheHits(), 0) 800 | self.assertEqual(stats.numCacheMisses(), 1) 801 | self.assertEqual(stats.numCacheEntries(), 1) 802 | 803 | # Compile same files with specifying /MP, this should hit 804 | subprocess.check_call(cmd + ["/MP", "fibonacci01.cpp"], env=customEnv) 805 | 806 | with cache.statistics as stats: 807 | self.assertEqual(stats.numCacheHits(), 1) 808 | self.assertEqual(stats.numCacheMisses(), 1) 809 | self.assertEqual(stats.numCacheEntries(), 1) 810 | 811 | def testHitsViaMpConcurrent(self): 812 | with cd(os.path.join(ASSETS_DIR, "parallel")), tempfile.TemporaryDirectory() as tempDir: 813 | cache = clcache.Cache(tempDir) 814 | 815 | customEnv = self._createEnv(tempDir) 816 | 817 | with cache.statistics as stats: 818 | self.assertEqual(stats.numCacheHits(), 0) 819 | self.assertEqual(stats.numCacheMisses(), 0) 820 | self.assertEqual(stats.numCacheEntries(), 0) 821 | 822 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 823 | 824 | # Compile two random files 825 | subprocess.check_call(cmd + ["fibonacci01.cpp"], env=customEnv) 826 | subprocess.check_call(cmd + ["fibonacci02.cpp"], env=customEnv) 827 | 828 | with cache.statistics as stats: 829 | self.assertEqual(stats.numCacheHits(), 0) 830 | self.assertEqual(stats.numCacheMisses(), 2) 831 | self.assertEqual(stats.numCacheEntries(), 2) 832 | 833 | # Compile same two files concurrently, this should hit twice. 834 | subprocess.check_call(cmd + ["/MP2", "fibonacci01.cpp", "fibonacci02.cpp"], env=customEnv) 835 | 836 | with cache.statistics as stats: 837 | self.assertEqual(stats.numCacheHits(), 2) 838 | self.assertEqual(stats.numCacheMisses(), 2) 839 | self.assertEqual(stats.numCacheEntries(), 2) 840 | 841 | def testOutput(self): 842 | # type: () -> None 843 | with cd(os.path.join(ASSETS_DIR, "parallel")), tempfile.TemporaryDirectory() as tempDir: 844 | sources = glob.glob("*.cpp") 845 | clcache.Cache(tempDir) 846 | customEnv = self._createEnv(tempDir) 847 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 848 | mpFlag = "/MP" + str(len(sources)) 849 | out = subprocess.check_output(cmd + [mpFlag] + sources, env=customEnv).decode("ascii") 850 | # print the output so that it shows up in py.test 851 | print(out) 852 | 853 | for s in sources: 854 | self.assertEqual(out.count(s), 1) 855 | 856 | class TestRunParallel(RunParallelBase, unittest.TestCase): 857 | env = dict(os.environ) 858 | 859 | # Compiler calls with multiple sources files at once, e.g. 860 | # cl file1.c file2.c 861 | class TestMultipleSources(unittest.TestCase): 862 | def testTwo(self): 863 | with cd(os.path.join(ASSETS_DIR, "mutiple-sources")), tempfile.TemporaryDirectory() as tempDir: 864 | cache = clcache.Cache(tempDir) 865 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 866 | baseCmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 867 | 868 | with cache.statistics as stats: 869 | self.assertEqual(stats.numCacheHits(), 0) 870 | self.assertEqual(stats.numCacheMisses(), 0) 871 | self.assertEqual(stats.numCacheEntries(), 0) 872 | 873 | subprocess.check_call(baseCmd + ["fibonacci01.cpp", "fibonacci02.cpp"], env=customEnv) 874 | 875 | with cache.statistics as stats: 876 | self.assertEqual(stats.numCacheHits(), 0) 877 | self.assertEqual(stats.numCacheMisses(), 2) 878 | self.assertEqual(stats.numCacheEntries(), 2) 879 | 880 | subprocess.check_call(baseCmd + ["fibonacci01.cpp", "fibonacci02.cpp"], env=customEnv) 881 | 882 | with cache.statistics as stats: 883 | self.assertEqual(stats.numCacheHits(), 2) 884 | self.assertEqual(stats.numCacheMisses(), 2) 885 | self.assertEqual(stats.numCacheEntries(), 2) 886 | 887 | def testFive(self): 888 | with cd(os.path.join(ASSETS_DIR, "mutiple-sources")), tempfile.TemporaryDirectory() as tempDir: 889 | cache = clcache.Cache(tempDir) 890 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 891 | baseCmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 892 | 893 | with cache.statistics as stats: 894 | self.assertEqual(stats.numCacheHits(), 0) 895 | self.assertEqual(stats.numCacheMisses(), 0) 896 | self.assertEqual(stats.numCacheEntries(), 0) 897 | 898 | subprocess.check_call(baseCmd + [ 899 | "fibonacci01.cpp", 900 | "fibonacci02.cpp", 901 | "fibonacci03.cpp", 902 | "fibonacci04.cpp", 903 | "fibonacci05.cpp", 904 | ], env=customEnv) 905 | 906 | with cache.statistics as stats: 907 | self.assertEqual(stats.numCacheHits(), 0) 908 | self.assertEqual(stats.numCacheMisses(), 5) 909 | self.assertEqual(stats.numCacheEntries(), 5) 910 | 911 | subprocess.check_call(baseCmd + [ 912 | "fibonacci01.cpp", 913 | "fibonacci02.cpp", 914 | "fibonacci03.cpp", 915 | "fibonacci04.cpp", 916 | "fibonacci05.cpp", 917 | ], env=customEnv) 918 | 919 | with cache.statistics as stats: 920 | self.assertEqual(stats.numCacheHits(), 5) 921 | self.assertEqual(stats.numCacheMisses(), 5) 922 | self.assertEqual(stats.numCacheEntries(), 5) 923 | 924 | class TestMultipleSourceWithClEnv(unittest.TestCase): 925 | def testAppend(self): 926 | with cd(os.path.join(ASSETS_DIR)): 927 | customEnv = dict(os.environ, _CL_="minimal.cpp") 928 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 929 | subprocess.check_call(cmd + ["fibonacci.cpp"], env=customEnv) 930 | 931 | 932 | class TestClearing(unittest.TestCase): 933 | def _clearCache(self): 934 | subprocess.check_call(CLCACHE_CMD + ["-C"]) 935 | 936 | def testClearIdempotency(self): 937 | cache = clcache.Cache() 938 | 939 | self._clearCache() 940 | with cache.statistics as stats: 941 | self.assertEqual(stats.currentCacheSize(), 0) 942 | self.assertEqual(stats.numCacheEntries(), 0) 943 | 944 | # Clearing should be idempotent 945 | self._clearCache() 946 | with cache.statistics as stats: 947 | self.assertEqual(stats.currentCacheSize(), 0) 948 | self.assertEqual(stats.numCacheEntries(), 0) 949 | 950 | @pytest.mark.skipif("CLCACHE_MEMCACHED" in os.environ, 951 | reason="clearing on memcached not implemented") 952 | def testClearPostcondition(self): 953 | cache = clcache.Cache() 954 | 955 | # Compile a random file to populate cache 956 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", os.path.join(ASSETS_DIR, "fibonacci.cpp")] 957 | subprocess.check_call(cmd) 958 | 959 | # Now there should be something in the cache 960 | with cache.statistics as stats: 961 | self.assertTrue(stats.currentCacheSize() > 0) 962 | self.assertTrue(stats.numCacheEntries() > 0) 963 | 964 | # Now, clear the cache: the stats should remain unchanged except for 965 | # the cache size and number of cache entries. 966 | oldStats = copy.copy(cache.statistics) 967 | self._clearCache() 968 | with cache.statistics as stats: 969 | self.assertEqual(stats.currentCacheSize(), 0) 970 | self.assertEqual(stats.numCacheEntries(), 0) 971 | self.assertEqual(stats.numCallsWithoutSourceFile(), oldStats.numCallsWithoutSourceFile()) 972 | self.assertEqual(stats.numCallsWithMultipleSourceFiles(), oldStats.numCallsWithMultipleSourceFiles()) 973 | self.assertEqual(stats.numCallsWithPch(), oldStats.numCallsWithPch()) 974 | self.assertEqual(stats.numCallsForLinking(), oldStats.numCallsForLinking()) 975 | self.assertEqual(stats.numCallsForPreprocessing(), oldStats.numCallsForPreprocessing()) 976 | self.assertEqual(stats.numCallsForExternalDebugInfo(), oldStats.numCallsForExternalDebugInfo()) 977 | self.assertEqual(stats.numEvictedMisses(), oldStats.numEvictedMisses()) 978 | self.assertEqual(stats.numHeaderChangedMisses(), oldStats.numHeaderChangedMisses()) 979 | self.assertEqual(stats.numSourceChangedMisses(), oldStats.numSourceChangedMisses()) 980 | self.assertEqual(stats.numCacheHits(), oldStats.numCacheHits()) 981 | self.assertEqual(stats.numCacheMisses(), oldStats.numCacheMisses()) 982 | 983 | 984 | class TestAnalysisErrorsCalls(unittest.TestCase): 985 | def testAllKnownAnalysisErrors(self): 986 | # This ensures all AnalysisError cases are run once without crashes 987 | 988 | with cd(os.path.join(ASSETS_DIR)): 989 | baseCmd = CLCACHE_CMD + ['/nologo'] 990 | 991 | # NoSourceFileError 992 | # This must fail because cl.exe: "cl : Command line error D8003 : missing source filename" 993 | # Make sure it was cl.exe that failed and not clcache 994 | process = subprocess.Popen(baseCmd + [], stderr=subprocess.PIPE) 995 | _, stderr = process.communicate() 996 | self.assertEqual(process.returncode, 2) 997 | self.assertTrue("D8003" in stderr.decode(clcache.CL_DEFAULT_CODEC)) 998 | 999 | # InvalidArgumentError 1000 | # This must fail because cl.exe: "cl : Command line error D8004 : '/Zm' requires an argument" 1001 | # Make sure it was cl.exe that failed and not clcache 1002 | process = subprocess.Popen(baseCmd + ['/c', '/Zm', 'bar', "minimal.cpp"], stderr=subprocess.PIPE) 1003 | _, stderr = process.communicate() 1004 | self.assertEqual(process.returncode, 2) 1005 | self.assertTrue("D8004" in stderr.decode(clcache.CL_DEFAULT_CODEC)) 1006 | 1007 | # MultipleSourceFilesComplexError 1008 | subprocess.check_call(baseCmd + ['/c', '/Tcfibonacci.c', "minimal.cpp"]) 1009 | # CalledForLinkError 1010 | subprocess.check_call(baseCmd + ["fibonacci.cpp"]) 1011 | # CalledWithPchError 1012 | subprocess.check_call(baseCmd + ['/c', '/Yc', "minimal.cpp"]) 1013 | # ExternalDebugInfoError 1014 | subprocess.check_call(baseCmd + ['/c', '/Zi', "minimal.cpp"]) 1015 | # CalledForPreprocessingError 1016 | subprocess.check_call(baseCmd + ['/E', "minimal.cpp"]) 1017 | 1018 | 1019 | class TestPreprocessorCalls(unittest.TestCase): 1020 | def testHitsSimple(self): 1021 | invocations = [ 1022 | ["/nologo", "/E"], 1023 | ["/nologo", "/EP", "/c"], 1024 | ["/nologo", "/P", "/c"], 1025 | ["/nologo", "/E", "/EP"], 1026 | ] 1027 | 1028 | cache = clcache.Cache() 1029 | with cache.statistics as stats: 1030 | oldPreprocessorCalls = stats.numCallsForPreprocessing() 1031 | 1032 | for i, invocation in enumerate(invocations, 1): 1033 | cmd = CLCACHE_CMD + invocation + [os.path.join(ASSETS_DIR, "minimal.cpp")] 1034 | subprocess.check_call(cmd) 1035 | with cache.statistics as stats: 1036 | newPreprocessorCalls = stats.numCallsForPreprocessing() 1037 | self.assertEqual(newPreprocessorCalls, oldPreprocessorCalls + i, str(cmd)) 1038 | 1039 | 1040 | class TestNoDirectCalls(RunParallelBase, unittest.TestCase): 1041 | env = dict(os.environ, CLCACHE_NODIRECT="1") 1042 | 1043 | def testPreprocessorFailure(self): 1044 | cache = clcache.Cache() 1045 | oldStats = copy.copy(cache.statistics) 1046 | 1047 | cmd = CLCACHE_CMD + ["/nologo", "/c", "doesnotexist.cpp"] 1048 | 1049 | self.assertNotEqual(subprocess.call(cmd, env=self.env), 0) 1050 | self.assertEqual(cache.statistics, oldStats) 1051 | 1052 | def testHit(self): 1053 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")): 1054 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", "hit.cpp"] 1055 | 1056 | self.assertEqual(subprocess.call(cmd, env=self.env), 0) 1057 | 1058 | cache = clcache.Cache() 1059 | with cache.statistics as stats: 1060 | oldHits = stats.numCacheHits() 1061 | 1062 | self.assertEqual(subprocess.call(cmd, env=self.env), 0) # This should hit now 1063 | with cache.statistics as stats: 1064 | self.assertEqual(stats.numCacheHits(), oldHits + 1) 1065 | 1066 | class TestBasedir(unittest.TestCase): 1067 | def setUp(self): 1068 | self.projectDir = os.path.join(ASSETS_DIR, "basedir") 1069 | self.tempDir = tempfile.TemporaryDirectory() 1070 | self.clcacheDir = os.path.join(self.tempDir.name, "clcache") 1071 | self.savedCwd = os.getcwd() 1072 | 1073 | os.chdir(self.tempDir.name) 1074 | 1075 | # First, create two separate build directories with the same sources 1076 | for buildDir in ["builddir_a", "builddir_b"]: 1077 | shutil.copytree(self.projectDir, buildDir) 1078 | 1079 | self.cache = clcache.Cache(self.clcacheDir) 1080 | 1081 | def tearDown(self): 1082 | os.chdir(self.savedCwd) 1083 | self.tempDir.cleanup() 1084 | 1085 | def _runCompiler(self, cppFile, extraArgs=None): 1086 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c"] 1087 | if extraArgs: 1088 | cmd.extend(extraArgs) 1089 | cmd.append(cppFile) 1090 | env = dict(os.environ, CLCACHE_DIR=self.clcacheDir, CLCACHE_BASEDIR=os.getcwd()) 1091 | self.assertEqual(subprocess.call(cmd, env=env), 0) 1092 | 1093 | def expectHit(self, runCompiler): 1094 | # Build once in one directory 1095 | with cd("builddir_a"): 1096 | runCompiler[0]() 1097 | with self.cache.statistics as stats: 1098 | self.assertEqual(stats.numCacheMisses(), 1) 1099 | self.assertEqual(stats.numCacheHits(), 0) 1100 | 1101 | # Build again in a different directory, this should hit now because of CLCACHE_BASEDIR 1102 | with cd("builddir_b"): 1103 | runCompiler[1]() 1104 | with self.cache.statistics as stats: 1105 | self.assertEqual(stats.numCacheMisses(), 1) 1106 | self.assertEqual(stats.numCacheHits(), 1) 1107 | 1108 | def expectMiss(self, runCompiler): 1109 | # Build once in one directory 1110 | with cd("builddir_a"): 1111 | runCompiler[0]() 1112 | with self.cache.statistics as stats: 1113 | self.assertEqual(stats.numCacheMisses(), 1) 1114 | self.assertEqual(stats.numCacheHits(), 0) 1115 | 1116 | # Build again in a different directory, this should hit now because of CLCACHE_BASEDIR 1117 | with cd("builddir_b"): 1118 | runCompiler[1]() 1119 | with self.cache.statistics as stats: 1120 | self.assertEqual(stats.numCacheMisses(), 2) 1121 | self.assertEqual(stats.numCacheHits(), 0) 1122 | 1123 | def testBasedirRelativePaths(self): 1124 | def runCompiler(): 1125 | self._runCompiler("main.cpp") 1126 | self.expectHit([runCompiler, runCompiler]) 1127 | 1128 | def testBasedirAbsolutePaths(self): 1129 | def runCompiler(): 1130 | self._runCompiler(os.path.join(os.getcwd(), "main.cpp")) 1131 | self.expectHit([runCompiler, runCompiler]) 1132 | 1133 | def testBasedirIncludeArg(self): 1134 | def runCompiler(): 1135 | self._runCompiler("main.cpp", ["/I{}".format(os.getcwd())]) 1136 | self.expectHit([runCompiler, runCompiler]) 1137 | 1138 | def testBasedirIncludeSlashes(self): 1139 | def runCompiler(includePath): 1140 | self._runCompiler("main.cpp", ["/I{}".format(includePath)]) 1141 | self.expectHit([ 1142 | lambda: runCompiler(os.getcwd() + "/"), 1143 | lambda: runCompiler(os.getcwd()) 1144 | ]) 1145 | 1146 | def testBasedirIncludeArgDifferentCapitalization(self): 1147 | def runCompiler(): 1148 | self._runCompiler("main.cpp", ["/I{}".format(os.getcwd().upper())]) 1149 | self.expectHit([runCompiler, runCompiler]) 1150 | 1151 | def testBasedirDefineArg(self): 1152 | def runCompiler(): 1153 | self._runCompiler("main.cpp", ["/DRESOURCES_DIR={}".format(os.getcwd())]) 1154 | self.expectMiss([runCompiler, runCompiler]) 1155 | 1156 | def testBasedirRelativeIncludeArg(self): 1157 | basedir = os.getcwd() 1158 | 1159 | def runCompiler(cppFile="main.cpp"): 1160 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", "/I."] 1161 | cmd.append(cppFile) 1162 | env = dict(os.environ, CLCACHE_DIR=self.clcacheDir, CLCACHE_BASEDIR=basedir) 1163 | self.assertEqual(subprocess.call(cmd, env=env), 0) 1164 | 1165 | self.expectMiss([runCompiler, runCompiler]) 1166 | 1167 | 1168 | class TestCleanCache(unittest.TestCase): 1169 | def testEvictedObject(self): 1170 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")), tempfile.TemporaryDirectory() as tempDir: 1171 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 1172 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", 'hit.cpp'] 1173 | 1174 | # Compile once to insert the object in the cache 1175 | subprocess.check_call(cmd, env=customEnv) 1176 | 1177 | # Remove object 1178 | cache = clcache.Cache(tempDir) 1179 | clcache.cleanCache(cache) 1180 | 1181 | self.assertEqual(subprocess.call(cmd, env=customEnv), 0) 1182 | 1183 | def testEvictedManifest(self): 1184 | with cd(os.path.join(ASSETS_DIR, "hits-and-misses")), tempfile.TemporaryDirectory() as tempDir: 1185 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 1186 | cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", 'hit.cpp'] 1187 | 1188 | # Compile once to insert the object in the cache 1189 | subprocess.check_call(cmd, env=customEnv) 1190 | 1191 | # Remove manifest 1192 | cache = clcache.Cache(tempDir) 1193 | clcache.clearCache(cache) 1194 | 1195 | self.assertEqual(subprocess.call(cmd, env=customEnv), 0) 1196 | 1197 | 1198 | if __name__ == '__main__': 1199 | unittest.TestCase.longMessage = True 1200 | unittest.main() 1201 | -------------------------------------------------------------------------------- /tests/test_performance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This file is part of the clcache project. 4 | # 5 | # The contents of this file are subject to the BSD 3-Clause License, the 6 | # full text of which is available in the accompanying LICENSE file at the 7 | # root directory of this project. 8 | # 9 | # In Python unittests are always members, not functions. Silence lint in this file. 10 | # pylint: disable=no-self-use 11 | # 12 | from multiprocessing import cpu_count 13 | import os 14 | import shutil 15 | import subprocess 16 | import sys 17 | import tempfile 18 | import timeit 19 | import unittest 20 | 21 | from clcache import __main__ as clcache 22 | 23 | PYTHON_BINARY = sys.executable 24 | CLCACHE_SCRIPT = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "clcache", "clcache.py") 25 | ASSETS_DIR = os.path.join(os.path.join(os.path.dirname(__file__), "performancetests")) 26 | 27 | if "CLCACHE_CMD" in os.environ: 28 | CLCACHE_CMD = os.environ['CLCACHE_CMD'].split() 29 | else: 30 | CLCACHE_CMD = ['clcache'] 31 | 32 | def takeTime(code): 33 | start = timeit.default_timer() 34 | code() 35 | return timeit.default_timer() - start 36 | 37 | class TestConcurrency(unittest.TestCase): 38 | NUM_SOURCE_FILES = 30 39 | 40 | @classmethod 41 | def setUpClass(cls): 42 | for i in range(1, TestConcurrency.NUM_SOURCE_FILES): 43 | shutil.copyfile( 44 | os.path.join(ASSETS_DIR, 'concurrency', 'file01.cpp'), 45 | os.path.join(ASSETS_DIR, 'concurrency', 'file{:02d}.cpp'.format(i+1)) 46 | ) 47 | 48 | cls.sources = [] 49 | for i in range(1, TestConcurrency.NUM_SOURCE_FILES+1): 50 | cls.sources.append(os.path.join(ASSETS_DIR, 'concurrency', 'file{:02d}.cpp'.format(i))) 51 | 52 | def testConcurrentHitsScaling(self): 53 | with tempfile.TemporaryDirectory() as tempDir: 54 | customEnv = dict(os.environ, CLCACHE_DIR=tempDir) 55 | 56 | cache = clcache.Cache(tempDir) 57 | 58 | with cache.statistics as stats: 59 | self.assertEqual(stats.numCacheHits(), 0) 60 | self.assertEqual(stats.numCacheMisses(), 0) 61 | self.assertEqual(stats.numCacheEntries(), 0) 62 | 63 | # Populate cache 64 | cmd = CLCACHE_CMD + ['/nologo', '/EHsc', '/c'] + TestConcurrency.sources 65 | coldCacheSequential = takeTime(lambda: subprocess.check_call(cmd, env=customEnv)) 66 | 67 | with cache.statistics as stats: 68 | self.assertEqual(stats.numCacheHits(), 0) 69 | self.assertEqual(stats.numCacheMisses(), len(TestConcurrency.sources)) 70 | self.assertEqual(stats.numCacheEntries(), len(TestConcurrency.sources)) 71 | 72 | # Compile one-by-one, measuring the time. 73 | cmd = CLCACHE_CMD + ['/nologo', '/EHsc', '/c'] + TestConcurrency.sources 74 | hotCacheSequential = takeTime(lambda: subprocess.check_call(cmd, env=customEnv)) 75 | 76 | with cache.statistics as stats: 77 | self.assertEqual(stats.numCacheHits(), len(TestConcurrency.sources)) 78 | self.assertEqual(stats.numCacheMisses(), len(TestConcurrency.sources)) 79 | self.assertEqual(stats.numCacheEntries(), len(TestConcurrency.sources)) 80 | 81 | # Recompile with many concurrent processes, measuring time 82 | cmd = CLCACHE_CMD + ['/nologo', '/EHsc', '/c', '/MP{}'.format(cpu_count())] + TestConcurrency.sources 83 | hotCacheConcurrent = takeTime(lambda: subprocess.check_call(cmd, env=customEnv)) 84 | 85 | with cache.statistics as stats: 86 | self.assertEqual(stats.numCacheHits(), len(TestConcurrency.sources) * 2) 87 | self.assertEqual(stats.numCacheMisses(), len(TestConcurrency.sources)) 88 | self.assertEqual(stats.numCacheEntries(), len(TestConcurrency.sources)) 89 | 90 | print("Compiling {} source files sequentially, cold cache: {} seconds" 91 | .format(len(TestConcurrency.sources), coldCacheSequential)) 92 | print("Compiling {} source files sequentially, hot cache: {} seconds" 93 | .format(len(TestConcurrency.sources), hotCacheSequential)) 94 | print("Compiling {} source files concurrently via /MP{}, hot cache: {} seconds" 95 | .format(len(TestConcurrency.sources), cpu_count(), hotCacheConcurrent)) 96 | 97 | 98 | if __name__ == '__main__': 99 | unittest.TestCase.longMessage = True 100 | unittest.main() 101 | -------------------------------------------------------------------------------- /tests/unittests/broken_json.txt: -------------------------------------------------------------------------------- 1 | {"a": 1 2 | -------------------------------------------------------------------------------- /tests/unittests/compiler-artifacts-repository/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/compiler-artifacts-repository/.gitkeep -------------------------------------------------------------------------------- /tests/unittests/configuration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/configuration/.gitkeep -------------------------------------------------------------------------------- /tests/unittests/empty_file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/empty_file.txt -------------------------------------------------------------------------------- /tests/unittests/fi-build-debug/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/fi-build-debug/.gitkeep -------------------------------------------------------------------------------- /tests/unittests/files-beneath/a/1.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/files-beneath/a/1.txt -------------------------------------------------------------------------------- /tests/unittests/files-beneath/a/2.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/files-beneath/a/2.txt -------------------------------------------------------------------------------- /tests/unittests/files-beneath/b/c/3.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/files-beneath/b/c/3.txt -------------------------------------------------------------------------------- /tests/unittests/files-beneath/d/4.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/files-beneath/d/4.txt -------------------------------------------------------------------------------- /tests/unittests/files-beneath/d/e/5.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/files-beneath/d/e/5.txt -------------------------------------------------------------------------------- /tests/unittests/fo-build-debug/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/fo-build-debug/.gitkeep -------------------------------------------------------------------------------- /tests/unittests/manifests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/manifests/.gitkeep -------------------------------------------------------------------------------- /tests/unittests/manifests/br/brokenmanifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "includeFiles": [ 3 | "somepath\\myinclude.h" 4 | ], 5 | "includesContentToObjectMap": { 6 | "fdde59862785f9f0ad6e661b9b5746b7" "a649723940dc975ebd17167d29a532f8" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/unittests/parse-includes/compiler_output.txt: -------------------------------------------------------------------------------- 1 | version.cpp 2 | Note: including file: C:\Projects\test\smartsqlite\include\smartsqlite/version.h 3 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\string 4 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\iterator 5 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\istream 6 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ostream 7 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ios 8 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xlocnum 9 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\climits 10 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\yvals.h 11 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xkeycheck.h 12 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 13 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\sal.h 14 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ConcurrencySal.h 15 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\vadefs.h 16 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\use_ansi.h 17 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\limits.h 18 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 19 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cmath 20 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\math.h 21 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 22 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xtgmath.h 23 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xtr1common 24 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cstdio 25 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stdio.h 26 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 27 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\swprintf.inl 28 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cstdlib 29 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stdlib.h 30 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 31 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\streambuf 32 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xiosbase 33 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xlocale 34 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cstring 35 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\string.h 36 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 37 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stdexcept 38 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\exception 39 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xstddef 40 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cstddef 41 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stddef.h 42 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 43 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\initializer_list 44 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\eh.h 45 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 46 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\malloc.h 47 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 48 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xstring 49 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xmemory0 50 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\limits 51 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ymath.h 52 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cfloat 53 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\float.h 54 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 55 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtwrn.h 56 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 57 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cwchar 58 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\wchar.h 59 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 60 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\wtime.inl 61 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\new 62 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xutility 63 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\utility 64 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\iosfwd 65 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdbg.h 66 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 67 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\type_traits 68 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xrefwrap 69 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xatomic0.h 70 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\intrin.h 71 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 72 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\setjmp.h 73 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 74 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\immintrin.h 75 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\wmmintrin.h 76 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\nmmintrin.h 77 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\smmintrin.h 78 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\tmmintrin.h 79 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\pmmintrin.h 80 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\emmintrin.h 81 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xmmintrin.h 82 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\mmintrin.h 83 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 84 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ammintrin.h 85 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\mm3dnow.h 86 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 87 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\typeinfo 88 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xlocinfo 89 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xlocinfo.h 90 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ctype.h 91 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 92 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\locale.h 93 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 94 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xdebug 95 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xfacet 96 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\system_error 97 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cerrno 98 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\errno.h 99 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 100 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\share.h 101 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\iostream 102 | Note: including file: C:\Projects\test\smartsqlite\include\smartsqlite/sqlite3.h 103 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stdarg.h 104 | Note: including file: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 105 | -------------------------------------------------------------------------------- /tests/unittests/parse-includes/compiler_output_lang_de.txt: -------------------------------------------------------------------------------- 1 | version.cpp 2 | Hinweis: Einlesen der Datei: C:\Projects\test\smartsqlite\include\smartsqlite/version.h 3 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\string 4 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\iterator 5 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\istream 6 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ostream 7 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ios 8 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xlocnum 9 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\climits 10 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\yvals.h 11 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xkeycheck.h 12 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 13 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\sal.h 14 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ConcurrencySal.h 15 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\vadefs.h 16 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\use_ansi.h 17 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\limits.h 18 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 19 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cmath 20 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\math.h 21 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 22 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xtgmath.h 23 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xtr1common 24 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cstdio 25 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stdio.h 26 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 27 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\swprintf.inl 28 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cstdlib 29 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stdlib.h 30 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 31 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\streambuf 32 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xiosbase 33 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xlocale 34 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cstring 35 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\string.h 36 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 37 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stdexcept 38 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\exception 39 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xstddef 40 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cstddef 41 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stddef.h 42 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 43 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\initializer_list 44 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\eh.h 45 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 46 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\malloc.h 47 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 48 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xstring 49 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xmemory0 50 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\limits 51 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ymath.h 52 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cfloat 53 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\float.h 54 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 55 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtwrn.h 56 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 57 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cwchar 58 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\wchar.h 59 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 60 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\wtime.inl 61 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\new 62 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xutility 63 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\utility 64 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\iosfwd 65 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdbg.h 66 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 67 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\type_traits 68 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xrefwrap 69 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xatomic0.h 70 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\intrin.h 71 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 72 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\setjmp.h 73 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 74 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\immintrin.h 75 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\wmmintrin.h 76 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\nmmintrin.h 77 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\smmintrin.h 78 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\tmmintrin.h 79 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\pmmintrin.h 80 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\emmintrin.h 81 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xmmintrin.h 82 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\mmintrin.h 83 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 84 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ammintrin.h 85 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\typeinfo 86 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xlocinfo 87 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xlocinfo.h 88 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\ctype.h 89 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 90 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\locale.h 91 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 92 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xdebug 93 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\xfacet 94 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\system_error 95 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\cerrno 96 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\errno.h 97 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 98 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\share.h 99 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\iostream 100 | Hinweis: Einlesen der Datei: C:\Projects\test\smartsqlite\include\smartsqlite/sqlite3.h 101 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\stdarg.h 102 | Hinweis: Einlesen der Datei: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\crtdefs.h 103 | -------------------------------------------------------------------------------- /tests/unittests/parse-includes/compiler_output_no_includes.txt: -------------------------------------------------------------------------------- 1 | main.cpp 2 | -------------------------------------------------------------------------------- /tests/unittests/response-files/default_encoded.rsp: -------------------------------------------------------------------------------- 1 | /DPASSWORD=Käse /nologo 2 | 3 | -------------------------------------------------------------------------------- /tests/unittests/response-files/nested_response_file.rsp: -------------------------------------------------------------------------------- 1 | /O2 @nested_response_file_2.rsp /nologo 2 | -------------------------------------------------------------------------------- /tests/unittests/response-files/nested_response_file_2.rsp: -------------------------------------------------------------------------------- 1 | /DSOMETHING=foo /DANOTHERTHING=bar 2 | -------------------------------------------------------------------------------- /tests/unittests/response-files/utf16_encoded.rsp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/response-files/utf16_encoded.rsp -------------------------------------------------------------------------------- /tests/unittests/statistics/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frerich/clcache/cae73d8255d78db8ba11e23c51fd2c9a89e7475b/tests/unittests/statistics/.gitkeep --------------------------------------------------------------------------------