1#!/usr/bin/env python3 2# 3# Generation of common Makefile for CBMC proofs. 4# 5# Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 6# 7# Permission is hereby granted, free of charge, to any person obtaining a copy 8# of this software and associated documentation files (the "Software"), to deal 9# in the Software without restriction, including without limitation the rights 10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11# copies of the Software, and to permit persons to whom the Software is 12# furnished to do so, subject to the following conditions: 13# 14# The above copyright notice and this permission notice shall be included in all 15# copies or substantial portions of the Software. 16# 17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23# SOFTWARE. 24 25from pprint import pprint 26import json 27import sys 28import re 29import os 30import argparse 31 32def cleanup_whitespace(string): 33 return re.sub('\s+', ' ', string.strip()) 34 35################################################################ 36# Operating system specific values 37 38platform_definitions = { 39 "linux": { 40 "platform": "linux", 41 "separator": "/", 42 "define": "-D", 43 "include": "-I", 44 }, 45 "macos": { 46 "platform": "darwin", 47 "separator": "/", 48 "define": "-D", 49 "include": "-I", 50 }, 51 "windows": { 52 "platform": "win32", 53 "separator": "\\", 54 "define": "/D", 55 "include": "/I", 56 }, 57} 58 59 60def default_platform(): 61 for platform, definition in platform_definitions.items(): 62 if sys.platform == definition["platform"]: 63 return platform 64 return "linux" 65 66 67def patch_path_separator(opsys, string): 68 from_separator = '/' 69 to_separator = platform_definitions[opsys]["separator"] 70 71 def escape_separator(string): 72 return string.split(from_separator + from_separator) 73 74 def change_separator(string): 75 return string.replace(from_separator, to_separator) 76 77 return from_separator.join([change_separator(escaped) 78 for escaped in escape_separator(string)]) 79 80def patch_compile_output(opsys, line, key, value): 81 if opsys != "windows": 82 return line 83 84 if key in ["COMPILE_ONLY", "COMPILE_LINK"] and value is not None: 85 if value[-1] == '/Fo': 86 return re.sub('/Fo\s+', '/Fo', line) 87 if value[-1] == '/Fe': 88 return re.sub('/Fe\s+', '/Fe', line) 89 return line 90 91################################################################ 92# Argument parsing 93# 94 95def get_arguments(): 96 parser = argparse.ArgumentParser() 97 parser.add_argument( 98 "--system", 99 metavar="OS", 100 choices=platform_definitions, 101 default=str(default_platform()), 102 help="Generate makefiles for operating system OS" 103 ) 104 return parser.parse_args() 105 106################################################################ 107# Variable definitions 108# 109# JSON files give variable definitions for common, operating system, 110# and harness specfic values 111# 112 113def read_variable_definitions(filename): 114 variable = {} 115 with open(filename) as _file: 116 for key, values in json.load(_file).items(): 117 variable[cleanup_whitespace(key)] = [cleanup_whitespace(value) 118 for value in values] 119 return variable 120 121def find_definition_once(key, defines, prefix=None): 122 123 # Try looking up key with and without prefix 124 prefix = "{}_".format(prefix.rstrip('_')) if prefix else "" 125 key2 = key[len(prefix):] if key.startswith(prefix) else prefix + key 126 127 for _key in [key, key2]: 128 _value = defines.get(_key) 129 if _value is not None: 130 return _value 131 132 return None 133 134def find_definition(key, defines): 135 common_defines, opsys_defines, harness_defines = defines 136 return (find_definition_once(key, harness_defines, "H") or 137 find_definition_once(key, opsys_defines, "O") or 138 find_definition_once(key, common_defines, "C")) 139 140################################################################ 141# Makefile generation 142 143def construct_definition(opsys, key_prefix, value_prefix, key, definitions): 144 values = definitions.get(key) 145 if not values: 146 return "" 147 if key in ["INC", "DEF"]: 148 values = [patch_path_separator(opsys, value) 149 for value in values] 150 lines = ["\t{}{} \\".format(value_prefix, value) for value in values] 151 return "{}_{} = \\\n{}\n\t# empty\n".format(key_prefix, 152 key, 153 '\n'.join(lines)) 154 155def write_define(opsys, define, defines, makefile): 156 value = find_definition(define, defines) 157 if value: 158 if define in ["FREERTOS_PLUS_TCP", "PROOFS"]: 159 value = os.path.abspath(value[0]) 160 makefile.write("{} = {}\n".format(define, value)) 161 162def write_common_defines(opsys, defines, makefile): 163 common_defines, opsys_defines, harness_defines = defines 164 165 for key_prefix, defines in zip(["C", "O", "H"], 166 [common_defines, 167 opsys_defines, 168 harness_defines]): 169 for value_prefix, key in zip([platform_definitions[opsys]["include"], 170 platform_definitions[opsys]["define"], 171 "", ""], 172 ["INC", "DEF", "OPT", "CBMCFLAGS"]): 173 define = construct_definition(opsys, 174 key_prefix, value_prefix, 175 key, defines) 176 if define: 177 makefile.write(define + "\n") 178 179 180def write_makefile(opsys, template, defines, makefile): 181 with open(template) as _template: 182 for line in _template: 183 line = patch_path_separator(opsys, line) 184 keys = re.findall('@(\w+)@', line) 185 values = [find_definition(key, defines) for key in keys] 186 for key, value in zip(keys, values): 187 if value is not None: 188 line = line.replace('@{}@'.format(key), " ".join(value)) 189 line = patch_compile_output(opsys, line, key, value) 190 makefile.write(line) 191 192def write_cbmcbatchyaml_target(opsys, _makefile): 193 target = """ 194################################################################ 195# Build configuration file to run cbmc under cbmc-batch in CI 196 197define encode_options 198'=$(shell echo $(1) | sed 's/ ,/ /g' | sed 's/ /;/g')=' 199endef 200 201cbmc-batch.yaml: 202 @echo "Building $@" 203 @$(RM) $@ 204 @echo "jobos: ubuntu16" >> $@ 205 @echo "cbmcflags: $(call encode_options,$(CBMCFLAGS) --unwinding-assertions)" >> $@ 206 @echo "goto: $(ENTRY).goto" >> $@ 207 @echo "expected: $(H_EXPECTED)" >> $@ 208 209################################################################ 210""" 211 if opsys != "windows": 212 _makefile.write(target) 213 214def write_solver(opsys, _makefile): 215 conditional = """ 216ifneq ($(strip $(EXTERNAL_SAT_SOLVER)),) 217 SOLVER = --external-sat-solver $(EXTERNAL_SAT_SOLVER) 218endif 219""" 220 if opsys != "windows": 221 _makefile.write(conditional) 222 223def makefile_from_template(opsys, template, defines, makefile="Makefile"): 224 with open(makefile, "w") as _makefile: 225 write_define(opsys, "FREERTOS_PLUS_TCP", defines, _makefile) 226 write_define(opsys, "PROOFS", defines, _makefile) 227 write_common_defines(opsys, defines, _makefile) 228 write_makefile(opsys, template, defines, _makefile) 229 write_cbmcbatchyaml_target(opsys, _makefile) 230 write_solver(opsys, _makefile) 231 232################################################################ 233# Main 234 235def main(): 236 args = get_arguments() 237 238 common_defines = read_variable_definitions("MakefileCommon.json") 239 opsys_defines = read_variable_definitions("MakefileWindows.json" 240 if args.system == "windows" 241 else "MakefileLinux.json") 242 harness_defines = {} 243 244 makefile_from_template(args.system, 245 "Makefile.template", 246 (common_defines, opsys_defines, harness_defines), 247 "Makefile.common") 248 249if __name__ == "__main__": 250 main() 251