xref: /petsc/lib/petsc/conf/rules_util.mk (revision daba9d70159ea2f6905738fcbec7404635487b2b)
1# -*- mode: makefile-gmake -*-
2#
3#    Contains rules that are shared between SLEPc and PETSc
4#
5#    This file is not included by rules, or rules_doc.mk. In PETSc it is only included by the toplevel makefile
6
7# ********* Rules for printing PETSc library properties useful for building applications  ***********************************************************
8
9getversion:
10	-@${PETSC_DIR}/lib/petsc/bin/petscversion
11
12getmpilinklibs:
13	-@echo  ${MPI_LIB}
14
15getmpiincludedirs:
16	-@echo  ${MPI_INCLUDE}
17
18getmpiexec:
19	-@echo  ${MPIEXEC}
20
21getccompiler:
22	-@echo ${CC}
23
24getfortrancompiler:
25	-@echo ${FC}
26
27getcxxcompiler:
28	-@echo ${CXX}
29
30getlinklibs:
31	-@echo  ${C_SH_LIB_PATH} ${PETSC_TS_LIB}
32
33getincludedirs:
34	-@echo  ${PETSC_CC_INCLUDES}
35
36getcflags:
37	-@echo ${CC_FLAGS}
38
39getcxxflags:
40	-@echo ${CXX_FLAGS}
41
42getfortranflags:
43	-@echo ${FC_FLAGS}
44
45getblaslapacklibs:
46	-@echo ${BLASLAPACK_LIB}
47
48getautoconfargs:
49	-@echo CC='"${CC}"' CXX='"${CXX}"'  FC='"${FC}"' CFLAGS='"${CC_FLAGS}"' CXXFLAGS='"${CXX_FLAGS}"' FCFLAGS='"${FC_FLAGS}"' LIBS='"${C_SH_LIB_PATH} ${PETSC_TS_LIB}"'
50
51#********* Rules for checking the PETSc source code, and comments do not violate PETSc standards. And for properly formatting source code and manual pages
52PETSCCLANGFORMAT ?= clang-format
53# Check the version of clang-format matches PETSc requirement
54checkclangformatversion:
55	@version=`${PETSCCLANGFORMAT} --version | cut -d" " -f3 | cut -d"." -f 1` ;\
56         if [ "$$version" = "version" ]; then version=`${PETSCCLANGFORMAT} --version | cut -d" " -f4 | cut -d"." -f 1`; fi;\
57         if [ $$version != 21 ]; then echo "Require clang-format version 21! Currently used ${PETSCCLANGFORMAT} version is $$version" ;false ; fi
58
59# Format all the source code in the given directory and down according to the file $PETSC_DIR/.clang_format
60clangformat: checkclangformatversion
61	-@git --no-pager ls-files -z ${GITCFSRC} | xargs -0 -P $(MAKE_NP) -L 10 ${PETSCCLANGFORMAT} -i
62
63GITFSRC = '*.[hF]90' '*/finclude/*.h'
64GITSRC = '*.[ch]' '*.hpp' '*.cxx' '*.cu' ${GITFSRC} ${GITSRCEXCL}
65GITMAKE = '*[Mm]akefile*' 'lib/petsc/conf/*' 'lib/slepc/conf/*'
66GITSRCEXCL = \
67':!*khash/*' \
68':!*valgrind/*' \
69':!*yaml/*' \
70':!*perfstubs/*'
71GITCFSRC = '*.[ch]' '*.hpp' '*.cxx' '*.cu' ${GITSRCEXCL} ${GITCFSRCEXCL}
72GITCFSRCEXCL = \
73':!*petscversion.h' \
74':!*mpiunifdef.h' \
75':!*finclude/*' \
76':!systems/*' \
77':!*binding/*'
78
79# Check that copies of external source code that live in the PETSc repository have not been changed by developer
80checkbadFileChange:
81	@git diff --stat --exit-code `lib/petsc/bin/maint/check-merge-branch.sh`..HEAD -- src/sys/yaml/include src/sys/yaml/License include/petsc/private/valgrind include/petsc/private/kash
82
83vermin:
84	@vermin --violations -t=3.4- ${VERMIN_OPTIONS} ${PETSC_DIR}/config
85	@vermin --violations -t=3.6- --exclude-regex '\.pyi$$' ${VERMIN_OPTIONS} ${PETSC_DIR}/src/binding/petsc4py
86
87# Check that source code does not violate basic PETSc coding standards
88checkbadsource: checkbadSource
89
90checkbadSource:
91	@git --no-pager grep -n -P 'self\.gitcommit' -- config/BuildSystem/config/packages | grep 'origin/' ; if [[ "$$?" == "0" ]]; then echo "Error: Do not use a branch name in a configure package file"; false; fi
92	-@${RM} -f checkbadSource.out
93	-@touch checkbadSource.out
94	-@${PYTHON} ${PETSC_DIR}/lib/petsc/bin/maint/check_header_guard.py --action=check --kind=pragma_once -- ./src ./include >> checkbadSource.out
95	-@echo "----- Double blank lines in file -----------------------------------" >> checkbadSource.out
96	-@git --no-pager grep -n -P '^$$' -- ${GITSRC} ${GITMAKE} 'config/*' > doublelinecheck.out
97	-@${PYTHON} ${PETSC_DIR}/lib/petsc/bin/maint/doublelinecheck.py doublelinecheck.out >> checkbadSource.out
98	-@${RM} -f doublelinecheck.out
99	-@echo "----- Tabs in file -------------------------------------------------" >> checkbadSource.out
100	-@git --no-pager grep -n -P '\t' -- ${GITSRC} >> checkbadSource.out;true
101	-@echo "----- Tabs in makefiles --------------------------------------------" >> checkbadSource.out
102	-@git --no-pager grep -n -P '^[^#][ ]*[#A-Za-z0-9][ :=_A-Za-z0-9]*\t' -- ${GITMAKE} >> checkbadSource.out;true
103	-@echo "----- White space at end of line -----------------------------------" >> checkbadSource.out
104	-@git --no-pager grep -n -P ' $$' -- ${GITSRC} ${GITMAKE} 'config/*' >> checkbadSource.out;true
105	-@echo "----- Two ;; -------------------------------------------------------" >> checkbadSource.out
106	-@git --no-pager grep -n -P -e ';;' -- ${GITSRC} | grep -v ' for (' >> checkbadSource.out;true
107	-@echo "----- PetscCall for an MPI error code ------------------------------" >> checkbadSource.out
108	-@git --no-pager grep -n -P -e 'PetscCall\(MPI[U]*_\w*\(.*\)\);' -- ${GITSRC} | grep -Ev 'MPIU_File' >> checkbadSource.out;true
109	-@echo "----- DOS file (with DOS newlines) ---------------------------------" >> checkbadSource.out
110	-@git --no-pager grep -n -P '\r' -- ${GITSRC} ${GITMAKE} 'config/*' >> checkbadSource.out;true
111	-@echo "----- { before SETERRQ ---------------------------------------------" >> checkbadSource.out
112	-@git --no-pager grep -n -P '{SETERRQ' -- ${GITSRC} >> checkbadSource.out;true
113	-@echo "----- PetscCall following SETERRQ ----------------------------------" >> checkbadSource.out
114	-@git --no-pager grep -n -P 'SETERRQ' -- ${GITSRC} | grep ";PetscCall" >> checkbadSource.out;true
115	-@echo "----- SETERRQ() without defined error code -------------------------" >> checkbadSource.out
116	-@git --no-pager grep -n -P 'SETERRQ\((?!\))' -- ${GITSRC} | grep -v PETSC_ERR | grep " if " | grep -v "__VA_ARGS__" | grep -v "then;" | grep -v flow.c >> checkbadSource.out;true
117	-@echo "----- SETERRQ() with trailing newline ------------------------------" >> checkbadSource.out
118	-@git --no-pager grep -n -P "SETERRQ[1-9]?.*\\\n\"" -- ${GITSRC} >> checkbadSource.out;true
119	-@echo "----- Define keyword used in test definition -----------------------" >> checkbadSource.out
120	-@git --no-pager grep -n -P -e 'requires:.*define\(' -- ${GITSRC} >> checkbadSource.out;true
121	-@echo "----- Using if (condition) SETERRQ(...) instead of PetscCheck() ----" >> checkbadSource.out
122	-@git --no-pager grep -n -P ' if +(.*) *SETERRQ' -- ${GITSRC} | grep -v 'PetscUnlikelyDebug' | grep -v 'petscerror.h' | grep -v "then;" | grep -v "__VA_ARGS__" >> checkbadSource.out;true
123	-@echo "----- Using if (PetscUnlikelyDebug(condition)) SETERRQ(...) instead of PetscAssert()" >> checkbadSource.out
124	-@git --no-pager grep -n -P -E ' if +\(PetscUnlikelyDebug.*\) *SETERRQ' -- ${GITSRC} | grep -v petscerror.h >> checkbadSource.out;true
125	-@echo "----- Using PetscFunctionReturn(ierr) ------------------------------" >> checkbadSource.out
126	-@git --no-pager grep -n -P 'PetscFunctionReturn(ierr)' -- ${GITSRC} >> checkbadSource.out;true
127	-@echo "----- .seealso with leading white spaces ---------------------------" >> checkbadSource.out
128	-@git --no-pager grep -n -P -E '^[ ]+.seealso:' -- ${GITSRC} ':!src/sys/tests/linter/*' >> checkbadSource.out;true
129	-@echo "----- .seealso with double backticks -------------------------------" >> checkbadSource.out
130	-@git --no-pager grep -n -P -E '^.seealso:.*``' -- ${GITSRC} >> checkbadSource.out;true
131	-@echo "----- Defining a returning macro without PetscMacroReturns() -------" >> checkbadSource.out
132	-@git --no-pager grep -n -P 'define .*\w+;\s+do' -- ${GITSRC} | grep -E -v '(PetscMacroReturns|PetscDrawCollectiveBegin|MatPreallocateBegin|PetscOptionsBegin|PetscObjectOptionsBegin|PetscOptionsHeadEnd)' >> checkbadSource.out;true
133	-@echo "----- Defining an error checking macro using CHKERR style ----------" >> checkbadSource.out
134	-@git --no-pager grep -n -P 'define\s+CHKERR\w*\(.*\)\s*do\s+{' -- ${GITSRC} >> checkbadSource.out;true
135	-@echo "----- Using Petsc[Array|Mem]cpy() for ops instead of assignment ----" >> checkbadSource.out
136	-@git --no-pager grep -n -P 'cpy\(.*(.|->)ops, .*\)' -- ${GITSRC} >> checkbadSource.out;true
137	-@echo "----- Extra spaces in test harness rules ---------------------------" >> checkbadSource.out
138	-@git --no-pager grep -n -P -E '^(\!){0,1}[ ]*(suffix|output_file|nsize|requires|args):.*  .*' -- ${GITSRC} >> checkbadSource.out;true
139	-@echo "----- Extra comma in test harness rules ----------------------------" >> checkbadSource.out
140	-@git --no-pager grep -n -P -E '^(\!){0,1}[ ]*requires:.*,' -- ${GITSRC} >> checkbadSource.out;true
141	-@echo "----- Using PetscInfo() without carriage return --------------------" >> checkbadSource.out
142	-@git --no-pager grep -n -P 'PetscCall\(PetscInfo\(' -- ${GITSRC} | grep -v '\\n' >> checkbadSource.out;true
143	-@echo "----- Using Petsc(Assert|Check)() with carriage return -------------" >> checkbadSource.out
144	-@git --no-pager grep -n -P -E 'Petsc(Assert|Check)\(.*[^\]\\\n' -- ${GITSRC} >> checkbadSource.out;true
145	-@echo "----- Extra \"\" after format specifier ending a string --------------" >> checkbadSource.out
146	-@git --no-pager grep -n -P -E '_FMT \"\",' -- ${GITSRC} >> checkbadSource.out;true
147	-@echo "----- First blank line ---------------------------------------------" >> checkbadSource.out
148	-@git --no-pager grep -n -P \^\$$ -- ${GITSRC} ${GITMAKE} 'config/*' | grep ':1:' >> checkbadSource.out;true
149	-@echo "----- Last blank line ----------------------------------------------" >> checkbadSource.out
150	-@git ls-files ${GITFSRC} ${GITMAKE} 'config/*' ':!*petscdm.h90' ':!*petscts.h90' ':!*/__init__.py' | xargs -I{} sh -c 'tail -n1 "{}" | grep -q . || echo "{}"' >> checkbadSource.out;true
151	-@echo "----- Blank line after PetscFunctionBegin and derivatives ----------" >> checkbadSource.out
152	-@git --no-pager grep -n -E -A 1 '  PetscFunctionBegin(User|Hot){0,1};' -- ${GITSRC} | grep -E '\-[0-9]+-$$' | grep -v '^--$$' >> checkbadSource.out;true
153	-@echo "----- Blank line before PetscFunctionReturn ------------------------" >> checkbadSource.out
154	-@git --no-pager grep -n -E -B 1 '  PetscFunctionReturn' -- ${GITSRC} | grep -E '\-[0-9]+-$$' | grep -v '^--$$' >> checkbadSource.out;true
155	-@echo "----- No blank line before PetscFunctionBegin and derivatives ------" >> checkbadSource.out
156	-@git --no-pager grep -n -E -B 1 '  PetscFunctionBegin(User|Hot){0,1};' -- ${GITSRC} ':!src/sys/tests/*' ':!src/sys/tutorials/*' | grep -E '\-[0-9]+-.*;' | grep -v '^--$$' | grep -v '\\' >> checkbadSource.out;true
157	-@echo "----- Unneeded parentheses [!&~*](foo[->|.]bar) --------------------" >> checkbadSource.out
158	-@git --no-pager grep -n -P -E '([\!\&\~\*\(]|\-\-|\+\+|\)\)|\([^,\*\(]+\**\))\(([a-zA-Z0-9_]+((\.|->)[a-zA-Z0-9_]+|\[[a-zA-Z0-9_ \%\+\*\-]+\])+)\)' -- ${GITSRC} >> checkbadSource.out;true
159	-@echo "----- Use PetscSafePointerPlusOffset(ptr, n) instead of ptr ? ptr + n : NULL" >> checkbadSource.out
160	-@git --no-pager grep -n -Po ' ([^()\ ]+) \? (?1) \+ (.)* : NULL' -- ${GITSRC} >> checkbadSource.out;true
161	-@echo "----- Wrong PETSc capitalization -----------------------------------" >> checkbadSource.out
162	-@git --no-pager grep -n -P -E '[^a-zA-Z_*>{.]petsc [^+=]' -- ${GITSRC} | grep -v 'mat_solver_type petsc' | grep -v ' PETSc ' >> checkbadSource.out;true
163	-@echo "----- Fortran: Semi-colon at end of line ---------------------------" >> checkbadSource.out
164	-@git --no-pager grep -n -P -E ";$$" -- ${GITFSRC} >> checkbadSource.out;true
165	-@echo "----- Empty test harness output_file not named output/empty.out ----" >> checkbadSource.out
166	-@git --no-pager grep -L . -- '*.out' | grep -Ev '(/empty|/[a-zA-Z0-9_-]+_alt).out' >> checkbadSource.out;true
167	-@echo "----- Unnecessary braces around one-liners -------------------------" >> checkbadSource.out
168	-@git --no-pager grep -n -P -E '[ ]*(if|for|while|do|else) \(.*\) \{[^;]*;[^;]*\}( \\)?$$' -- ${GITSRC} >> checkbadSource.out;true
169	-@echo "----- MPI_(Allreduce|Irecv|Isend) instead of MPIU_(Allreduce|Irecv|Isend)" >> checkbadSource.out
170	-@git --no-pager grep -n -P -E '\(MPI_(Allreduce|Irecv|Isend)\([^\)]' -- ${GITSRC} ':!*/tests/*' ':!*/tutorials/*' ':!src/sys/objects/pinit.c' >> checkbadSource.out;true
171	-@echo "----- Fortran: #include <petsc/finclude/...> not 1st line (examples/tutorials)" >> checkbadSource.out
172	-@git --no-pager grep -H -B9999 '#include <petsc/finclude/.*>' -- '*/tutorials/*.F90' '*/tests/*.F90' | grep -v -e '!' -e 'F90-$$' -e '#include <petsc/finclude/.*>' -e '--' >> checkbadSource.out;true
173	-@echo "----- Fortran: labeled do loop -------------------------------------" >> checkbadSource.out
174	-@git --no-pager grep -n "[[:space:]]*do[[:space:]]*[0-9]" -- ${GITFSRC} >> checkbadSource.out;true
175	-@echo "----- Duplicate CUDA/Kokkos file names -----------------------------" >> checkbadSource.out
176	-@git ls-files *.cu *.kokkos.cxx | xargs -I{} sh -c 'basename "{}"' | sort | uniq -d  >> checkbadSource.out;true
177	-@echo "----- #if [!]defined with a trailing white space -------------------" >> checkbadSource.out
178	-@git --no-pager grep -n "#if [!]defined " -- ${GITFSRC} ${GITSRC} >> checkbadSource.out;true
179	-@echo "----- #if[n]def should use #if [!]defined()-------------------------" >> checkbadSource.out
180	-@git --no-pager grep -n "#if[n]def " -- ${GITFSRC} ${GITSRC} >> checkbadSource.out;true
181	-@echo "----- Use of the .cpp file extension instead of .cxx ---------------" >> checkbadSource.out
182	-@git ls-files *.cpp >> checkbadSource.out;true
183	@a=`cat checkbadSource.out | wc -l`; l=`expr $$a - 43` ;\
184         if [ $$l -gt 0 ] ; then \
185           echo $$l " files with errors detected in source code formatting" ;\
186           cat checkbadSource.out ;\
187         else \
188	   ${RM} -f checkbadSource.out;\
189         fi;\
190         test ! $$l -gt 0
191	-@git --no-pager grep -P -n "[\x00-\x08\x0E-\x1F\x80-\xFF]" -- ${GITSRC} > badSourceChar.out;true
192	-@w=`cat badSourceChar.out | wc -l`;\
193         if [ $$w -gt 0 ] ; then \
194           echo "Source files with non-ASCII characters ----------------" ;\
195           cat badSourceChar.out ;\
196         else \
197	   ${RM} -f badSourceChar.out;\
198         fi
199	@test ! -s badSourceChar.out
200
201#  Run a linter in a Python virtual environment to check (and fix) the formatting of PETSc manual pages
202#     V=1        verbose
203#     REPLACE=1  replace ill-formatted docs with correctly formatted docs
204env-lint:
205	@if [[ `which llvm-config` == "" ]]; then echo "llvm-config for version 14 must be in your path"; exit 1; fi
206	@if [ `llvm-config --version | cut -f1 -d"."` != 14 ]; then echo "llvm-config for version 14 must be in your path"; exit 1; fi
207	@python3 -m venv petsc-lint-env
208	@source petsc-lint-env/bin/activate && python3 -m pip install --quiet -r lib/petsc/bin/maint/petsclinter/requirements.txt && \
209          python3 ${PETSC_DIR}/lib/petsc/bin/maint/petsclinter --verbose=${V} --apply-patches=${REPLACE} --clang_lib=`llvm-config --libdir`/libclang.dylib --werror 1 ./src ; deactivate
210
211#  Run a linter to check (and fix) the formatting of PETSc manual pages
212lint:
213	${PYTHON} ${PETSC_DIR}/lib/petsc/bin/maint/petsclinter --verbose=${V} --apply-patches=${REPLACE} $(LINTER_OPTIONS) ${DIRECTORY}
214
215help-lint:
216	@${PYTHON} ${PETSC_DIR}/lib/petsc/bin/maint/petsclinter --help
217	-@echo "Basic usage:"
218	-@echo "   make lint      <options> [DIRECTORY=directory]"
219	-@echo "   make test-lint <options> <test only options>"
220	-@echo
221	-@echo "Options:"
222	-@echo "  V=[0, 1, 2, 3]                        Enable increasingly verbose output"
223	-@echo "  LINTER_OPTIONS=\"--opt1 --opt2 ...\"  See above for available options"
224	-@echo
225	-@echo "Test Only Options:"
226	-@echo "  LINTER_SKIP_MYPY=[1, 0]               Enable or disable mypy checks"
227	-@echo "  REPLACE=[1, 0]                        Enable or disable replacing test output"
228	-@echo
229
230checkvermin_exist:
231	@ret=`which vermin > /dev/null`; \
232	if [ $$? -ne 0 ]; then \
233          echo "vermin is required, please install: python3 -m pip install vermin"; \
234          false; \
235        fi
236
237checkmypy_exist:
238	@ret=`which mypy > /dev/null`; \
239	if [ $$? -ne 0 ]; then \
240          echo "MyPy is required, please install: python3 -m pip install mypy"; \
241          false; \
242        fi
243
244LINTER_SKIP_MYPY ?= 0
245
246test-lint: checkvermin_exist checkmypy_exist
247	vermin --config-file ${PETSC_DIR}/lib/petsc/bin/maint/petsclinter/pyproject.toml -- ${PETSC_DIR}/lib/petsc/bin/maint/petsclinter
248	if [[ "$(LINTER_SKIP_MYPY)" == "0" ]]; then \
249          cd ${PETSC_DIR}/lib/petsc/bin/maint/petsclinter && mypy . ; \
250        fi
251	${PYTHON} ${PETSC_DIR}/lib/petsc/bin/maint/petsclinter/petsclinter/pkg_consistency_checks.py
252	${PYTHON} ${PETSC_DIR}/lib/petsc/bin/maint/petsclinter ${PETSC_DIR}/src/sys/tests/linter --test -j0 --werror --replace=${REPLACE} --verbose=${V} $(LINTER_OPTIONS)
253
254#  Lists all the URLs in the PETSc repository that are unaccessible, nonexistent, or permanently moved (301)
255#  REPLACE=1 locations marked as permanently moved (301) are replaced in the repository
256#  This code is fragile; always check the changes after a use of REPLACE=1 before committing the changes
257#
258#  Notes:
259#    The first tr in the line is split lines for the cases where multiple URLs are in the same line
260#    The first sed handles the case (http[s]*://xxx)
261#    The list is sorted by length so that if REPLACE is used the longer apply before the shorter
262#    The code recursively follows the permanently moved (301) redirections until it reaches the final URL
263#    For DropBox we need to check the validity of the new URL but do not want to return to user the internal "raw" URL
264checkbadURLS:
265	-@x=`git grep "http[s]*://" -- '*.[chF]' '*.html' '*.cxx' '*.cu' '*.F90' '*.py' '*.tex' | grep -E -v "(config/packages|HandsOnExercises)" | tr '[[:blank:]]' '\n' | grep 'http[s]*://' | sed 's!.*(\(http[s]*://[-a-zA-Z0-9_./()?=&+%~]*\))!\1!g' | sed 's!.*\(http[s]*://[-a-zA-Z0-9_./()?=&+%~]*\).*!\1!g' | sed 's/\.$$//g' | sort | uniq| awk '{ print length, $$0 }' | sort -r -n -s | cut -d" " -f2` ; \
266        for i in $$x; do \
267          url=$$i; \
268          msg=''; \
269          while [[ "$${msg}D" == "D" ]] ; do \
270            y1=`curl --connect-timeout 5 --head --silent $${url} | head -n 1`; \
271            y2=`echo $${y1} | grep ' 4[0-9]* '`; \
272            y3=`echo $${y1} | grep ' 301 '`; \
273            if [[ "$${y1}D" == "D" ]] ; then \
274              msg="Unable to reach site" ; \
275            elif [[ "$${y2}D" != "D" ]] ; then \
276              msg=$${y1} ; \
277            elif [[ "$${y3}D" != "D" ]] ; then \
278              l=`curl --connect-timeout 5 --head --silent $${url} | grep ocation | sed 's/.*ocation:[[:blank:]]\(.*\)/\1/g' | tr -d '\r'` ; \
279              w=`echo $$l | grep 'http'` ; \
280              if [[ "$${w}D" != "D" ]] ; then \
281                url=$$l ; \
282              else \
283                ws=`echo $${url} | sed 's!\(http[s]*://[-a-zA-Z0-9_.]*\)/.*!\1!g'` ; \
284                dp=`echo $${ws}$${l} | grep "dropbox.com/s/raw"` ; \
285                if [[ "$${dp}D" != "D" ]] ; then \
286                  b=`curl --connect-timeout 5 --head --silent $${ws}$$l | head -n 1` ; \
287                  c=`echo $${b} | grep -E "( 2[0-9]* | 302 )"` ; \
288                  if [[ "$${c}D" == "D" ]] ; then \
289                    msg=`echo "dropbox file doesn't exist" $${c}` ; \
290                  else \
291                    break ; \
292                  fi; \
293                else \
294                  url="$${ws}$$l" ; \
295                fi; \
296              fi; \
297            else \
298              break; \
299            fi; \
300          done;\
301          if [[ "$${msg}D" == "D" && "$${url}" != "$$i" ]] ; then \
302            echo "URL" $$i "has moved to valid final location:" $${url} ; \
303            if [[ "$${REPLACE}D" != "D" ]] ; then \
304              git psed $$i $$l ;\
305            fi; \
306          elif [[ "$${msg}D" != "D" && "$${url}" != "$$i" ]] ; then \
307            echo "ERROR: URL" $$i "has moved to invalid final location:" $${url} $${msg} ; \
308          elif [[ "$${msg}D" != "D" ]] ; then \
309            echo "ERROR: URL" $$i "invalid:" $${msg} ; \
310          fi; \
311        done
312