github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/makelib/misc.mk (about) 1 # check if we have undefine feature (only in make >=3.82) 2 ifneq ($(filter undefine,$(.FEATURES)),) 3 4 # we have undefine 5 define undef 6 $(eval undefine $1) 7 endef 8 9 else 10 11 # no undefine available, simply set the variable to empty value 12 define undef 13 $(eval $1 :=) 14 endef 15 16 endif 17 18 # 1 - a list of variables to undefine 19 # 20 # Simply uses undefine directive on all passed variables. 21 # 22 # It does not check if variables are in any way special (like being 23 # special make variables or else). 24 # 25 # Example: $(call undefine-variables-unchecked,VAR1 VAR2 VAR3) 26 define undefine-variables-unchecked 27 $(strip \ 28 $(foreach v,$1, \ 29 $(call undef,$v))) 30 endef 31 32 # 1 - a list of variable namespaces 33 # 2 - a list of excluded variables 34 # 35 # Undefines all variables in all given namespaces (which basically 36 # means variables with names prefixed with <namespace>_) except for 37 # ones listed in a given exclusions list. 38 # 39 # It does not check if variables are in any way special (like being 40 # special make variables or else). 41 # 42 # It is a bit of make-golf to avoid using variables. See 43 # undefine-namespaces below, which has clearer code, is doing exactly 44 # the same, but calls undefine-variables instead (which changes its 45 # behaviour wrt. the origin of the variables). 46 # 47 # Example: $(call undefine-namespaces-unchecked,NS1 NS2 NS3,N1_KEEP_THIS N3_THIS_TOO) 48 define undefine-namespaces-unchecked 49 $(strip \ 50 $(foreach ,x x, \ 51 $(call undefine-variables-unchecked, \ 52 $(filter-out $2, \ 53 $(filter $(foreach n,$1,$n_%),$(.VARIABLES)))))) 54 endef 55 56 # 1 - a list of variables to undefine 57 # 58 # Undefines those variables from a given list, which have origin 59 # "file". If the origin of a variable is different, it is left 60 # untouched. 61 # 62 # This function will bail out if any of the variables starts with a 63 # dot or MAKE. 64 # 65 # Example: $(call undefine-variables,VAR1 VAR2 VAR3) 66 define undefine-variables 67 $(strip \ 68 $(foreach p,.% MAKE%, \ 69 $(eval _MISC_UV_FORBIDDEN_ := $(strip $(filter $p,$1))) \ 70 $(if $(_MISC_UV_FORBIDDEN_), \ 71 $(eval _MISC_UV_ERROR_ += Trying to undefine $(_MISC_UV_FORBIDDEN_) variables which match the forbidden pattern $p.))) \ 72 $(if $(_MISC_UV_ERROR_), \ 73 $(error $(_MISC_UV_ERROR_))) \ 74 $(foreach v,$1, \ 75 $(if $(filter-out file,$(origin $v)), \ 76 $(eval _MISC_UV_EXCLUDES_ += $v))) \ 77 $(eval _MISC_UV_VARS_ := $(filter-out $(_MISC_UV_EXCLUDES_), $1)) \ 78 $(call undefine-variables-unchecked,$(_MISC_UV_VARS_)) \ 79 $(call undefine-namespaces-unchecked,_MISC_UV)) 80 endef 81 82 # 1 - a list of variable namespaces 83 # 2 - a list of excluded variables 84 # 85 # Undefines those variables in all given namespaces (which basically 86 # means variables with names prefixed with <namespace>_), which have 87 # origin "file". If the origin of the variable is different or the 88 # variable is a part of exclusions list, it is left untouched. 89 # 90 # This function will bail out if any of the variables starts with a 91 # dot or MAKE. 92 # 93 # The function performs the action twice - sometimes defined variables 94 # are not listed in .VARIABLES list initially, but they do show up 95 # there after first iteration, so we can remove them then. It is 96 # likely a make bug. 97 # 98 # Example: $(call undefine-namespaces,NS1 NS2 NS3,N1_KEEP_THIS N3_THIS_TOO) 99 define undefine-namespaces 100 $(strip \ 101 $(foreach ,x x, \ 102 $(eval _MISC_UN_VARS_ := $(filter $(foreach n,$1,$n_%),$(.VARIABLES))) \ 103 $(eval _MISC_UN_VARS_ := $(filter-out $2,$(_MISC_UN_VARS_))) \ 104 $(call undefine-variables,$(_MISC_UN_VARS_)) \ 105 $(call undefine-namespaces-unchecked,_MISC_UN))) 106 endef 107 108 define multi-subst 109 $(strip \ 110 $(eval _MISC_MS_TMP_ := $(strip $3)) \ 111 $(eval $(foreach s,$1, \ 112 $(eval _MISC_MS_TMP_ := $(subst $s,$2,$(_MISC_MS_TMP_))))) \ 113 $(_MISC_MS_TMP_) \ 114 $(call undefine-namespaces,_MISC_MS)) 115 endef 116 117 # When updating replaced chars here, remember to update them in 118 # libdepsgen.pm in escape_path sub. 119 define escape-for-file 120 $(call multi-subst,- / . : +,_,$1) 121 endef 122 123 define path-to-file-with-suffix 124 $(call escape-for-file,$1).$2 125 endef 126 127 define stamp-file 128 $(STAMPSDIR)/$(call path-to-file-with-suffix,$1,stamp) 129 endef 130 131 # Generates a stamp filename and assigns it to passed variable 132 # name. Generates a stamp's dependency on stamps directory. Adds stamp 133 # to CLEAN_FILES. Optional second parameter is for adding a suffix to 134 # stamp. 135 # Example: $(call setup-custom-stamp-file,FOO_STAMP,/some_suffix) 136 define setup-custom-stamp-file 137 $(strip \ 138 $(eval $1 := $(call stamp-file,$2)) \ 139 $(eval $($1): | $$(call to-dir,$($1))) \ 140 $(eval CLEAN_FILES += $($1))) 141 endef 142 143 # Generates a stamp filename and assigns it to passed variable 144 # name. Generates a stamp's dependency on stamps directory. Adds stamp 145 # to CLEAN_FILES. Optional second parameter is for adding a suffix to 146 # stamp. 147 # Example: $(call setup-stamp-file,FOO_STAMP,/some_suffix) 148 define setup-stamp-file 149 $(eval $(call setup-custom-stamp-file,$1,$(MK_PATH)$2)) 150 endef 151 152 define dep-file 153 $(DEPSDIR)/$(call path-to-file-with-suffix,$1,dep.mk) 154 endef 155 156 define setup-custom-dep-file 157 $(strip \ 158 $(eval $1 := $(call dep-file,$2)) \ 159 $(eval $($1): | $$(call to-dir,$($1))) \ 160 $(eval CLEAN_FILES += $($1))) 161 endef 162 163 define setup-dep-file 164 $(eval $(call setup-custom-dep-file,$1,$(MK_PATH)$2)) 165 endef 166 167 # Returns all not-excluded directories inside $REPO_PATH that have 168 # nonzero files matching given "go list -f {{.ITEM}}". 169 # 1 - where to look for files (./... to look for all files inside the project) 170 # 2 - a "go list -f {{.ITEM}}" item (GoFiles, TestGoFiles, etc) 171 # 3 - space-separated list of excluded directories 172 # Example: $(call go-find-directories,./...,TestGoFiles,tests) 173 define go-find-directories 174 $(strip \ 175 $(eval _MISC_GFD_ESCAPED_SRCDIR := $(MK_TOPLEVEL_ABS_SRCDIR)) \ 176 $(eval _MISC_GFD_ESCAPED_SRCDIR := $(subst .,\.,$(_MISC_GFD_ESCAPED_SRCDIR))) \ 177 $(eval _MISC_GFD_ESCAPED_SRCDIR := $(subst /,\/,$(_MISC_GFD_ESCAPED_SRCDIR))) \ 178 $(eval _MISC_GFD_SPACE_ :=) \ 179 $(eval _MISC_GFD_SPACE_ +=) \ 180 $(eval _MISC_GFD_FILES_ := $(shell $(GO_ENV) "$(GO)" list -f '{{.ImportPath}} {{.$2}}' $1 | \ 181 grep --invert-match '\[\]' | \ 182 sed -e 's/.*$(_MISC_GFD_ESCAPED_SRCDIR)\///' -e 's/[[:space:]]*\[.*\]$$//' \ 183 $(if $3,| grep --invert-match '^\($(subst $(_MISC_GFD_SPACE_),\|,$3)\)'))) \ 184 $(_MISC_GFD_FILES_) \ 185 $(call undefine-namespaces,_MISC_GFD)) 186 endef 187 188 # Returns 1 if both parameters are equal, otherwise returns empty 189 # string. 190 # Example: is_a_equal_to_b := $(if $(call equal,a,b),yes,no) 191 define equal 192 $(strip \ 193 $(eval _MISC_EQ_TMP_ := $(shell expr '$1' = '$2')) \ 194 $(filter $(_MISC_EQ_TMP_),1) \ 195 $(call undefine-namespaces,_MISC_EQ)) 196 endef 197 198 # Returns a string with all backslashes and double quotes escaped and 199 # wrapped in another double quotes. Useful for passing a string as a 200 # single parameter. In general the following should print the same: 201 # str := "aaa" 202 # $(info $(str)) 203 # $(shell echo $(call escape-and-wrap,$(str))) 204 define escape-and-wrap 205 "$(subst ",\",$(subst \,\\,$1))" 206 endef 207 # " 208 # the double quotes in comment above remove highlighting confusion 209 210 # Forwards given variables to a given rule. 211 # 1 - a rule target 212 # 2 - a list of variables to forward 213 # 214 # Example: $(call forward-vars,$(MY_TARGET),VAR1 VAR2 VAR3) 215 # 216 # The effect is basically: 217 # $(MY_TARGET): VAR1 := $(VAR1) 218 # $(MY_TARGET): VAR2 := $(VAR2) 219 # $(MY_TARGET): VAR3 := $(VAR3) 220 define forward-vars 221 $(strip \ 222 $(foreach v,$2, \ 223 $(eval $1: $v := $($v)))) 224 endef 225 226 # Returns a colon (:) if passed value is empty. Useful for avoiding 227 # shell errors about an empty body. 228 # 1 - bash code 229 define colon-if-empty 230 $(if $(strip $1),$1,:) 231 endef 232 233 # Used by generate-simple-rule, see its docs. 234 define simple-rule-template 235 $1: $2 $(if $(strip $3),| $3) 236 $(call colon-if-empty,$4) 237 endef 238 239 # Generates a simple rule - without variable forwarding and with only 240 # a single-command recipe. 241 # 1 - targets 242 # 2 - reqs 243 # 3 - order-only reqs 244 # 4 - recipe 245 define generate-simple-rule 246 $(eval $(call simple-rule-template,$1,$2,$3,$4)) 247 endef 248 249 # Generates a rule with a "set -e". 250 # 1 - target (stamp file) 251 # 2 - reqs 252 # 3 - order-only reqs 253 # 4 - recipe placed after 'set -e' 254 define generate-strict-rule 255 $(call generate-simple-rule,$1,$2,$3,$(VQ)set -e; $(call colon-if-empty,$4)) 256 endef 257 258 # Generates a rule for creating a stamp with additional actions to be 259 # performed before the actual stamp creation. 260 # 1 - target (stamp file) 261 # 2 - reqs 262 # 3 - order-only reqs 263 # 4 - recipe placed between 'set -e' and 'touch "$@"' 264 define generate-stamp-rule 265 $(call generate-strict-rule,$1,$2,$3,$(call colon-if-empty,$4); $$(call vb,v2,STAMP,$$(call vsp,$$@))touch "$$@") 266 endef 267 268 # 1 - from 269 # 2 - to 270 define bash-cond-rename 271 if cmp --silent "$1" "$2"; then rm -f "$1"; else mv "$1" "$2"; fi 272 endef 273 274 # Generates a rule for generating a depmk for a given go binary from a 275 # given package. It also tries to include the depmk. 276 # 277 # This function (and the other generate-*-deps) are stamp-based. It 278 # generates no rule for actual depmk. Instead it generates a rule for 279 # creating a stamp, which will also generate the depmk. This is to 280 # avoid generating depmk at make startup, when it parses the 281 # Makefile. At startup, make tries to rebuild all the files it tries 282 # to include if there is a rule for the file. We do not want that - 283 # that would override a depmk with a fresh one, so no file 284 # additions/deletions made before running make would be detected. 285 # 286 # 1 - a stamp file 287 # 2 - a binary name 288 # 3 - depmk name 289 # 4 - a package name 290 define generate-go-deps 291 $(strip \ 292 $(if $(call equal,$2,$(DEPSGENTOOL)), \ 293 $(eval _MISC_GGD_DEP_ := $(DEPSGENTOOL)), \ 294 $(eval _MISC_GGD_DEP_ := $(DEPSGENTOOL_STAMP))) \ 295 $(eval -include $3) \ 296 $(eval $(call generate-stamp-rule,$1,$(_MISC_GGD_DEP_),$(DEPSDIR),$(call vb,v2,DEPS GO,$(call vsg,$(REPO_PATH)/$4) => $(call vsp,$3))$(GO_ENV) "$(DEPSGENTOOL)" go --repo "$(REPO_PATH)" --module "$4" --target '$2 $1' >"$3.tmp"; $(call bash-cond-rename,$3.tmp,$3))) \ 297 $(call undefine-namespaces,_MISC_GGD)) 298 endef 299 300 # Generates a rule for generating a key-value depmk for a given target 301 # with given variable names to store. It also tries to include the 302 # depmk. 303 # 1 - a stamp file 304 # 2 - a target 305 # 3 - depmk name 306 # 4 - a list of variable names to store 307 define generate-kv-deps 308 $(strip \ 309 $(if $(call equal,$2,$(DEPSGENTOOL)), \ 310 $(eval _MISC_GKD_DEP_ := $(DEPSGENTOOL)), \ 311 $(eval _MISC_GKD_DEP_ := $(DEPSGENTOOL_STAMP))) \ 312 $(foreach v,$4, \ 313 $(eval _MISC_GKD_KV_ += $v $(call escape-and-wrap,$($v)))) \ 314 $(eval -include $3) \ 315 $(eval $(call generate-stamp-rule,$1,$(_MISC_GKD_DEP_),$(DEPSDIR),$(call vb,v2,DEPS KV,$4 => $(call vsp,$3))"$(DEPSGENTOOL)" kv --target '$2 $1' $(_MISC_GKD_KV_) >"$3.tmp"; $(call bash-cond-rename,$3.tmp,$3))) \ 316 $(call undefine-namespaces,_MISC_GKD)) 317 endef 318 319 define filelist-file 320 $(FILELISTDIR)/$(call path-to-file-with-suffix,$1,filelist) 321 endef 322 323 define setup-custom-filelist-file 324 $(eval $1 := $(call filelist-file,$2)) \ 325 $(eval $($1): | $$(call to-dir,$($1))) \ 326 $(eval CLEAN_FILES += $($1)) 327 endef 328 329 define setup-filelist-file 330 $(eval $(call setup-custom-filelist-file,$1,$(MK_PATH)$2)) 331 endef 332 333 # 1 - filelist 334 define generate-empty-filelist 335 $(eval $(call generate-strict-rule,$1,$(FILELISTGENTOOL_STAMP),$(FILELISTDIR),$(call vb,v2,FILELIST,<nothing> => $(call vsp,$1))"$(FILELISTGENTOOL)" --empty >"$1.tmp"; $(call bash-cond-rename,$1.tmp,$1))) 336 endef 337 338 # 1 - filelist 339 # 2 - directory 340 define generate-deep-filelist 341 $(eval $(call generate-strict-rule,$1,$(FILELISTGENTOOL_STAMP),$(FILELISTDIR),$(call vb,v2,FILELIST,$(call vsp,$2) => $(call vsp,$1))"$(FILELISTGENTOOL)" --directory="$2" >"$1.tmp"; $(call bash-cond-rename,$1.tmp,$1))) 342 endef 343 344 # 1 - filelist 345 # 2 - a directory 346 # 3 - a suffix 347 define generate-shallow-filelist 348 $(eval $(call generate-strict-rule,$1,$(FILELISTGENTOOL_STAMP),$(FILELISTDIR),$(call vb,v2,FILELIST,$(call vsp,$2/*$3) => $(call vsp,$1))"$(FILELISTGENTOOL)" --directory="$2" --suffix="$3" >"$1.tmp"; $(call bash-cond-rename,$1.tmp,$1))) 349 endef 350 351 # This is used for the truncated output - it takes a list of 352 # directories, shortens them and puts them in a comma-separated list 353 # between parens: (dir1,dir2,...,dirN) 354 define nice-dirs-output 355 $(strip \ 356 $(eval _MISC_NDO_COMMA_ := ,) \ 357 $(eval _MISC_NDO_SPACE_ := ) \ 358 $(eval _MISC_NDO_SPACE_ += ) \ 359 $(eval _MISC_NDO_DIRS_ := ($(subst $(_MISC_NDO_SPACE_),$(_MISC_NDO_COMMA_),$(call vsp,$1)))) \ 360 $(_MISC_NDO_DIRS_) \ 361 $(call undefine-namespaces,_MISC_NDO)) 362 endef 363 364 # Generates a rule for generating a glob depmk for a given target 365 # based on a given filelist. This is up to you to ensure that every 366 # file in a filelist ends with a given suffix. 367 # 1 - a stamp file 368 # 2 - a target 369 # 3 - depmk name 370 # 4 - a suffix 371 # 5 - a filelist 372 # 6 - a list of directories to map the files from filelist to 373 # 7 - glob mode, can be all, dot-files, normal (empty means all) 374 define generate-glob-deps 375 $(strip \ 376 $(if $(call equal,$2,$(DEPSGENTOOL)), \ 377 $(eval _MISC_GLDF_DEP_ := $(DEPSGENTOOL)), \ 378 $(eval _MISC_GLDF_DEP_ := $(DEPSGENTOOL_STAMP))) \ 379 $(if $(strip $7), \ 380 $(eval _MISC_GLDF_GLOB_ := --glob-mode="$(strip $7)")) \ 381 $(eval -include $3) \ 382 $(eval _MISC_GLDF_DIRS_ := $(call nice-dirs-output,$6)) \ 383 $(eval $(call generate-stamp-rule,$1,$(_MISC_GLDF_DEP_) $5,$(DEPSDIR),$(call vb,v2,DEPS GLOB,$(_MISC_GLDF_DIRS_) $(call vsp,$5) => $(call vsp,$3))"$(DEPSGENTOOL)" glob --target "$2 $1" --suffix="$4" --filelist="$5" $(foreach m,$6,--map-to="$m") $(_MISC_GLDF_GLOB_)>"$3.tmp"; $(call bash-cond-rename,$3.tmp,$3))) \ 384 $(call undefine-namespaces,_MISC_GLDF)) 385 endef 386 387 # Returns a list of directories starting from a subdirectory of given 388 # base up to the full path made of the given base and a rest. So, if 389 # base is "base" and rest is "r/e/s/t" then the returned list is 390 # "base/r base/r/e base/r/e/s base/r/e/s/t". 391 # 392 # Useful for getting a list of directories to be removed. 393 # 1 - a base 394 # 2 - a dir (or dirs) in base 395 # 396 # Example: CREATE_DIRS += $(call dir-chain,$(BASE),src/foo/bar/baz) 397 define dir-chain 398 $(strip \ 399 $(eval _MISC_DC_DIRS_ := $(subst /, ,$2)) \ 400 $(eval _MISC_DC_PATHS_ :=) \ 401 $(eval _MISC_DC_LIST_ :=) \ 402 $(eval $(foreach d,$(_MISC_DC_DIRS_), \ 403 $(eval _MISC_DC_LAST_ := $(lastword $(_MISC_DC_PATHS_))) \ 404 $(eval $(if $(_MISC_DC_LAST_), \ 405 $(eval _MISC_DC_PATHS_ += $(_MISC_DC_LAST_)/$d), \ 406 $(eval _MISC_DC_PATHS_ := $d))))) \ 407 $(eval $(foreach d,$(_MISC_DC_PATHS_), \ 408 $(eval _MISC_DC_LIST_ += $1/$d))) \ 409 $(_MISC_DC_LIST_) \ 410 $(call undefine-namespaces,_MISC_DC)) 411 endef 412 413 # 1 - variable for dirname 414 define setup-tmp-dir 415 $(strip \ 416 $(eval _MISC_TMP_DIR_ := $(MAINTEMPDIR)/$(subst .mk,,$(MK_FILENAME))) 417 $(eval CREATE_DIRS += $(_MISC_TMP_DIR_)) \ 418 $(eval $1 := $(_MISC_TMP_DIR_)) \ 419 $(call undefine-namespaces,_MISC_TMP)) 420 endef 421 422 define clean-file 423 $(CLEANDIR)/$(call path-to-file-with-suffix,$1,clean.mk) 424 endef 425 426 define setup-custom-clean-file 427 $(eval $1 := $(call clean-file,$2)) \ 428 $(eval $($1): | $$(call to-dir,$($1))) \ 429 $(eval CLEAN_FILES += $($1)) 430 endef 431 432 define setup-clean-file 433 $(eval $(call setup-custom-clean-file,$1,$(MK_PATH)$2)) 434 endef 435 436 # 1 - stamp file 437 # 2 - cleanmk file 438 # 3 - filelist name 439 # 4 - a list of directory mappings 440 define generate-clean-mk 441 $(strip \ 442 $(eval -include $2) \ 443 $(eval _MISC_GCM_DIRS_ := $(call nice-dirs-output,$4)) \ 444 $(eval $(call generate-stamp-rule,$1,$(CLEANGENTOOL_STAMP) $3,$(CLEANDIR),$(call vb,v2,CLEANFILE,$(_MISC_GCM_DIRS_) $(call vsp,$3) => $(call vsp,$2))"$(CLEANGENTOOL)" --filelist="$3" $(foreach m,$4,--map-to="$m") >"$2.tmp"; $(call bash-cond-rename,$2.tmp,$2))) \ 445 $(call undefine-namespaces,_MISC_GCM)) 446 endef 447 448 define sed-replacement-escape 449 $(strip $(shell echo $1 | sed -e 's/[\/&]/\\&/g')) 450 endef 451 452 define add-dependency-template 453 $1: $2 454 endef 455 456 # 1 - a target 457 # 2 - a dependency (or a prerequisite in makese) 458 define add-dependency 459 $(eval $(call add-dependency-template,$1,$2)) 460 endef 461 462 # Formats given lists of source and destination files for the 463 # INSTALL_FILES variable. 464 # 465 # 1 - list of src files 466 # 2 - list of target files 467 # 3 - mode 468 define install-file-triplets 469 $(strip $(join $(addsuffix :,$1),$(addsuffix :$3,$2))) 470 endef 471 472 define commas-to-spaces 473 $(strip \ 474 $(eval _MISC_CTS_COMMA_ := ,) \ 475 $(eval _MISC_CTS_SPACE_ := ) \ 476 $(eval _MISC_CTS_SPACE_ += ) \ 477 $(subst $(_MISC_CTS_COMMA_),$(_MISC_CTS_SPACE_),$1) \ 478 $(call undefine-namespaces,_MISC_CTS)) 479 endef 480 481 # Generates a rule for given stamp which removes given directory and 482 # adds a dependency to the directory on a given stamp. That way, the 483 # directory will be removed before it is created if the stamp does not 484 # exist or is invalidated. Additional dependencies for the stamp can 485 # be specified by using usual make syntax ($(stamp): $(dep)). 486 # 487 # 1 - stamp 488 # 2 - directory to remove 489 define generate-rm-dir-rule 490 $(strip \ 491 $(call add-dependency,$2,$1) \ 492 $(call generate-stamp-rule,$1,,, \ 493 $(call vb,v2,RM RF,$(call vsp,$2))rm -rf $2)) 494 endef 495 496 define go-pkg-from-dir 497 $(subst $(MK_TOPLEVEL_SRCDIR)/,,$(MK_SRCDIR)) 498 endef 499 500 # Generate a filelist for patches. Usually, when generating filelists, 501 # we require the directory to exist. In this case, the patches 502 # directory may not exist and it is fine. We generate an empty 503 # filelist. 504 # 505 # 1 - patches filelist 506 # 2 - patches dir 507 define generate-patches-filelist 508 $(strip \ 509 $(eval _MISC_GPF_FILELIST := $(strip $1)) \ 510 $(eval _MISC_GPF_DIR := $(strip $2)) \ 511 $(eval $(if $(shell test -d "$(_MISC_GPF_DIR)" && echo yes), \ 512 $(call generate-shallow-filelist,$(_MISC_GPF_FILELIST),$(_MISC_GPF_DIR),.patch), \ 513 $(call generate-empty-filelist,$(_MISC_GPF_FILELIST)))) \ 514 $(call undefine-namespaces,_MISC_GPF)) 515 endef