I using a Jenkins (on a windows machine) job to compile some code for different targets via Ant.
For doing so, I wrap the call to the ant target within a (windows) batch loop like this:
#echo off
for %%t in (target1 target2 target3) do (
ant -f build.xml build -DPARAM_TARGET=%%t
)
That was my first idea ... but this codes leads to an successful build even if (e.g.) target1 failed. So I put some more lines to the windows batch build step to get more overview. Also I have checekdout the code to get the same workingspace than Jenkins has to my local machine and add an test.bat to check the windows batch code can work at all.
#echo off
for %%t in (target1 target2 target3) do (
ant -f build.xml build -DPARAM_TARGET=%%t
echo ELVL: %ERRORLEVEL%
IF NOT %ERRORLEVEL% == 0 (
echo ABORT: %ERRORLEVEL%
exit /b %ERRORLEVEL%
) ELSE (
echo PROCEED: %ERRORLEVEL%
)
)
Testing this on my local windows machine shows the expected behaviour - here on success:
BUILD SUCCESSFUL
Total time: 3 seconds
ELVL: 0
PROCEED: 0
And on failure:
BUILD FAILED
C:\Work\...
C:\Work\...
Total time: 0 seconds
ELVL: 9009
ABORT: 9009
The same code on Jenkins do this:
BUILD FAILED
C:\Work\...
C:\Work\...
Total time: 4 seconds
ELVL: 0
PROCEED: 0
After using google for a while it reveals, that the return code from calling the Ant target is not properly passed to the java enviornment wherefrom Jenkins do the calls. I have tested around using "call" or "set ERRORLEVEL=1" something like this, but haven't found a soloution yet.
Anyone has an idea?
Put the loop (target1-3) into a system groovy script and hande the callc manually - does that work?
Regards
I think your problem is because you read %ERROR_LEVEL% in a for loop.
I think you must use setlocal EnableDelayedExpansion
EnableDelayedExpansion : Expand variables at execution time rather than at parse time.
(ref is here)
Try to do something like this :
setlocal EnableDelayedExpansion
for %%t in (target1 target2 target3) do (
ant -f build.xml build -DPARAM_TARGET=%%t
echo ELVL: !ERRORLEVEL!
IF NOT !ERRORLEVEL! == 0 (
echo ABORT: !ERRORLEVEL!
exit /b !ERRORLEVEL!
) ELSE (
echo PROCEED: !ERRORLEVEL!
)
)
It don't explain why it runs on your computer... maybe because the EnableDelayedExpansion is already set in your dos windows.
EDIT
In batch-file :
%var% will be expanded when the code is parsed (i.e. before execution !)
!var! will be expanded when the code is executed
Since you are in a loop : %ERROR_LEVEL% is expanded once (i.e. just before first execution). But what you need is to re-expand ERROR_LEVEL for each iteration and that's the purpose of !ERROR_LEVEL! syntax.
#echo off
for %%t in (target1 target2 target3) do (
ant -f build.xml build -DPARAM_TARGET=%%t
echo ELVL: %ERRORLEVEL%
IF NOT %ERRORLEVEL% == 0 (
echo ABORT: %ERRORLEVEL%
exit /b %ERRORLEVEL%
) ELSE (
echo PROCEED: %ERRORLEVEL%
)
)
IIRC The issue is that ant.bat is a batch file, so you need to call it, e.g.
#ECHO OFF
FOR %%t IN (target1 target2 target3) DO (
CALL ant.bat -f build.xml build -DPARAM_TARGET=%%t
ECHO ELVL: %ERRORLEVEL%
IF NOT %ERRORLEVEL% == 0 (
ECHO ABORT: %ERRORLEVEL%
EXIT /b %ERRORLEVEL%
) ELSE (
ECHO PROCEED: %ERRORLEVEL%
)
)
Update
Actually there is a nicer way:
#ECHO OFF
FOR %%t IN (target1 target2 target3) DO (
CALL ant.bat -f build.xml build -DPARAM_TARGET=%%t || EXIT /b 1
)
Related
I have a Windows bat file with the following content:
setlocal EnableDelayedExpansion
rem Stop batch script loop if a command failed.
#echo OFF
FOR %%a in (x86 x86_64 armeabi-v7a arm64-v8a) do (
set MY_ANDROID_BUILD_ABI=%%a
FOR %%m in (debug release) DO (
set MY_MODE=%%m
echo !MY_MODE! -^> !MY_ANDROID_BUILD_ABI!
cd D:\dev\libs\QT6.4\android\!MY_MODE!\!MY_ANDROID_BUILD_ABI!
rem some further build commands go here
cmake --build . --parallel && cmake --install .
)
)
it iterates over build configurations and executes multiple commands inside nested loop to build some library.
How to make the script exit the loop and stop execution when an error occurres?
For example, the script should stop if a directory does not exist or cmake command failed.
Should I add
IF %ERRORLEVEL% NEQ 0 (
echo "Previous command execution failed."
exit %ERRORLEVEL%
)
after each command?
Yes, you need to add some kind of error checking, since cmd doesn't have built-in exceptions. The full solution is to add after each command:
(command)
if ERRORLEVEL 1 (
echo failure message, maybe to some log
exit !ERRORLEVEL!
)
You can use the shorthand suggested in #Gerhard's comment:
(command) || (echo failure message& exit /b !ERRORLEVEL! )
In either case, beware of using %ERRORLEVEL%, as it's expanded too early. As your code correctly shows, use !ERRORLEVEL! (which requires setlocal EnabledDelayedExpansion), or use the special if ERRORLEVEL 1 format.
Consider a command script named t.cmd that consists solely of these 2 lines:
#exit /b 123
#echo If you see this, THEN EXIT FAILED..
So, the script merely sets the exit code of the script's execution process to 123, but does not kill cmd.exe. The final echo confirms that exit actually causes an immediate return (its output should not appear).
Now execute this script, and then print out the %errorlevel%:
>t.cmd
>echo %errorlevel%
123
So far so good: everything behaves exactly as expected.
But now execute the above all on one line, using && for conditional execution:
>t.cmd && echo %errorlevel%
123
I do NOT expect this: if t.cmd really returns with a non-0 exit code, then it should stop everything after that && (i.e. the echo) from executing. The fact that we see it print means that it DID execute. What the heck is going on?
And if execute the above all on one line, using || for conditional execution:
>t.cmd || echo %errorlevel%
>
This behavior is also the opposite of what I expect (altho it is consistent with the && behavior above).
Note that this weird behavior is ONLY true for bat files, but not for "raw commands".
Proof: consider the following command line interactions where instead of calling t.cmd, I try to execute the bogus command abcdef:
>abcdef
'abcdef' is not recognized as an internal or external command,
operable program or batch file.
>echo %errorlevel%
9009
>abcdef && echo %errorlevel%
'abcdef' is not recognized as an internal or external command,
operable program or batch file.
>abcdef || echo %errorlevel%
'abcdef' is not recognized as an internal or external command,
operable program or batch file.
9009
Here, && and || immediately see the exit code of the failed bogus command.
So why do cmd files behave differently?
A possibly related bug in cmd.exe has been observed in File redirection in Windows and %errorlevel%
Also, I am aware that ERRORLEVEL is not %ERRORLEVEL%
By the way, the code above was all executed on a Win 7 Pro 64 bit box. I have no idea how other versions of Windows behave.
With t.bat slightly modified as follows:
#exit /b 123%~1
#echo If you see this, THEN EXIT FAILED..
Think out next output:
==>t.bat 1
==>echo %errorlevel%
1231
==>t.bat 2&echo %errorlevel%
1231
==>echo %errorlevel%
1232
==>cmd /V /C t.bat 3^&echo !errorlevel!
1233
==>echo %errorlevel%
0
==>cmd /V /C t.bat 4^&echo !errorlevel!^&exit /B !errorlevel!
1234
==>echo %errorlevel%
1234
==>
Resources
(%~1 etc. special page) Command Line arguments (Parameters)
(special page) EnableDelayedExpansion
Edit to enlighten EnableDelayedExpansion:
==>cmd /v
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.
==>t.bat 5&echo !errorlevel!
1235
==>echo %errorlevel%
1235
==>
Edit 2 to enlighten (or confuse?) && and ||. Save next code snippet as errlevels.cmd:
#ECHO ON >NUL
#SETLOCAL enableextensions enabledelayedexpansion
(call )
#echo ^(call ^) command clears errorlevel %errorlevel%
abcd /G>NUL 2>&1
#echo abcd /G: "'abcd' not recognized" errorlevel %errorlevel%
abcd /G>NUL 2>&1 && echo YES !errorlevel! || echo NO !errorlevel!
#echo abcd /G: ^|^| changed errorlevel %errorlevel%
find /G >NUL 2>&1 && echo YES !errorlevel! || echo NO !errorlevel!
#echo find /G: ^|^| unchanged errorlevel %errorlevel%
call t.cmd 333 && echo YES !errorlevel! || echo NO !errorlevel!
type t.cmd
t.cmd 222 && echo YES !errorlevel! || echo NO !errorlevel!
Output (from errlevels.cmd):
==>errlevels.cmd
==>(call )
(call ) command clears errorlevel 0
==>abcd /G 1>NUL 2>&1
abcd /G: "'abcd' not recognized" errorlevel 9009
==>abcd /G 1>NUL 2>&1 && echo YES !errorlevel! || echo NO !errorlevel!
NO 1
abcd /G: || changed errorlevel 1
==>find /G 1>NUL 2>&1 && echo YES !errorlevel! || echo NO !errorlevel!
NO 2
find /G: || unchanged errorlevel 2
==>call t.cmd 333 && echo YES !errorlevel! || echo NO !errorlevel!
NO 333
==>type t.cmd
#exit /B %~1
==>t.cmd 222 && echo YES !errorlevel! || echo NO !errorlevel!
YES 222
==>
Note that
|| shows errorlevel 1 although 'abcd' not recognized error should be 9009 whilst
|| keeps errorlevel 2 unchanged for FIND: Invalid switch error.
|| branch evaluates in call t.cmd 333 whilst
&& branch evaluates in t.cmd 222.
On (call ) see DBenham's answer:
If you want to force the errorlevel to 0, then you can use this
totally non-intuitive, but very effective syntax: (call ). The space
after call is critical.
If you want to set the errorlevel to 1, you can use (call). It
is critical that there not be any space after call.
%errorlevel% is expanded when line is read. So it is 123 at the time the line was read so comes from the previous command not t.exe. && is executed only if the current errorlevel (from t command) is 0.
See setlocal /? and set /? for more info.
#JosefZ's response provides excellent coverage of the disparity between ERRORLEVEL and exit code with respect to command scripts.
Unfortunately, as he pointed out, the && and || operators will only work if you invoke your command script with the call command. In most cases, you'd prefer that users can just run your command script, without having to remember to prefix it with a call each time.
Typically, I want my scripts to set both the ERRORLEVEL and the exit code to indicate failure (in order to have my scripts behave the same as regular executables). I used to use exit /b <nonzero> to try to do this, but had the problem indicated above.
It turns out that the Windows command interpreter exits with the exit code of the last command executed. The actual exit code of the exit /b <nonzero> command is, ironically, 0 (since it successfully exited). It does set the ERRORLEVEL, but not the exit code. Thus, that command won't work. The solution to all this is to (1) use the cmd /c exit <nonzero> command, and (2) to have it execute as the last command in the script. Since the cmd command returns back to the script, where the next command is then executed, the only way to get it to be the last line executed is to have it be the last line of your script.
Thus, here's a solution to get everything to behave as the OP requested:
#echo off & setlocal
if "%1" equ "fail" (
echo -- Failure
goto fail
) else (
echo -- Success
exit /b 0
)
:fail
#REM // Exit with script a failure exit code.
cmd /c exit 37
I have a build step in my build configuration thats runner type "Command Line", running a custom script.
The script is executing Robocopy:
robocopy "%teamcity.build.workingDir%\Code" "\\target\d$\Web\Target Sites" /E /NP /LOG:robocopy.log
if ERRORLEVEL GEQ 4 (
"D:\blat.exe" "robocopy.log" -to me#me.com -f me#me.com -subject "Error during robocopy on TEAMCITY" -server mail.me.com
)
exit /B 0
The Robocopy command is working fine but I keep getting an email and in the build log I keep seeing:
GEQ was unexpected at this time.
The ERRORLEVEL check isn't working for some reason?
I tried IF %ERRORLEVEL% GEQ but this breaks my build has TeamCity expects me to pass a build parameter.
Does this only work as an "Executable with parameters"?
Neil, you might try escaping the percent sign.
Try IF %%ERRORLEVEL%% GEQ ...
I've just run into this problem, and appreciate #John's answer.
Here is what I came up with:
robocopy [from] [to] /MIR
REM http://ss64.com/nt/robocopy-exit.html
IF %%ERRORLEVEL%% EQU 0 (
ECHO No errors occurred, and no copying was done; The source and destination directory trees are completely synchronized.
EXIT 0
)
IF %%ERRORLEVEL%% EQU 1 (
ECHO One or more files were copied successfully, new files have arrived.
EXIT 0
)
IF %%ERRORLEVEL%% EQU 2 (
ECHO Some Extra files or directories were detected. No files were copied.
EXIT 0
)
IF %%ERRORLEVEL%% GEQ 3 (
ECHO Robocopy Exit Codes: http://ss64.com/nt/robocopy-exit.html
EXIT %%ERRORLEVEL%%
)
I made a piece of batch-code, and I thought this will work. What I'm thinking that this code is doing? I have some plugins and I want to test if the deploy correct. So I get the pluginlink from the plugins.txt. Then I get the plugin from SVN with the java sentence. I deploy the plugin and get the feedback in test1.txt. Then I do a findStr in that file and searchs for "BUILD SUCCESSFUL" if it is there I want to add the sentence Build Gelukt and if it fails I want to add Build Fout. But I get always the answer Build Gelukt, while as you can see in the image he sends back that the build is Failed.
Whats wrong with this piece of code?
for /f "tokens=* delims= " %%a in (plugins.txt) do (
echo %%a
cd "C:\dotCMS Automatic Install"
java -cp .;"C:\dotCMS Automatic Install\svnkit.jar" Test %%a
cd %dotcms_home%
call ant deploy-plugins > test1.txt
FindStr "SUCCESSFUL" test1.txt
if %ERRORLEVEL% ==1 (echo ^<tr BGCOLOR=\"#FFFFFF\"^>^<td^>%%a^</td^>^<td^>Build Fout^</td^>^</tr^> >> C:\dotCMSResults\goedje.html ) else (echo ^<tr BGCOLOR=\"#00FF00\"^>^<td^>%%a^</td^>^<td^>Build Gelukt^</td^>^</tr^> >> C:\dotCMSResults\goedje.html)
del test1.txt
rem call ant undeploy-plugins >> test.txt
)
Classic batch problem - you are setting your ERRORLEVEL and attempting to access it using %ERRORLEVEL% within the same DO() clause. %VAR% expansion happens at parse time, and the entire FOR ... DO() statement is parsed once, so you are seeing the value of ERRORLEVEL before the statement was executed. Obviously that won't work.
jeb alluded to the answer in his comment regarding disappearing quotes. Your problem will be fixed if you setlocal enableDelayedExpansion at the top, and then use !ERRORLEVEL! instead of %ERRORLEVEL%. Also, GregHNZ is correct in that the ERRORLEVEL test should occur immediately after your FINDSTR statement.
There are other ways to handle ERRORLEVEL within parentheses that don't require delayed expansion:
The following tests if ERRORLEVEL is greater than or equal 1
IF ERRORLEVEL 1 (...) ELSE (...)
And below conditionally executes commands based on the outcome of the prior command
FindStr "SUCCESSFUL" test1.txt && (
commands to execute if FindStr succeeded
) || (
commands to execute if prior command failed.
)
The %ErrorLevel% variable applies to the immediately previous command only.
So when you do this:
echo Errorlevel: %ERRORLEVEL%
With your current code, you are getting the error level of the CD command above
Try putting your if %ERRORLEVEL% ==1 line immediately after the FindStr command, and then do the del and the cd afterward. Obviously you'll need to put the full path to the html file in your echo statement.
So I recently stumbled on the (potentially) useful %~$PATH:1 expansion, however I seem to be unable to make it work correctly. I tried to use it to make a cheap Windows version of the which command, however the syntax seems to be defeating me. My batch file looks like this:
#echo off
echo %~$PATH:1
However when I run this with for example
which cmd
all I get as output of "ECHO is off.", which means according to the docs that the %~$PATH:1 didn't find "cmd". What am I doing wrong?
Checking for files with the extensions .exe, .cmd or .bat is not enough. The set of applicable extensions is defined in the environment variable PATHEXT.
Here is my version of a which command that honors the PATHEXT variable upon search:
#echo off
rem Windows equivalent of Unix which command
setlocal enabledelayedexpansion
if "%~1"=="" (
echo Usage: which cmdname
exit /b 1
)
call :findOnPath "%~1"
if not errorlevel 1 exit /b 0
for %%E in (%PATHEXT:;= %) do (
call :findOnPath "%~1%%E"
if not errorlevel 1 exit /b 0
)
echo "%~1" not found on PATH.
exit /b 1
:findOnPath
if not "%~$PATH:1" == "" (
echo "%~$PATH:1"
exit /b 0
)
exit /b 1
Shoot! I just figured it out! I need to use the full "cmd.exe" as a parameter instead of just "cmd". D'oh! ;] So, the complete which.cmd script looks like this:
#echo off
call :checkpath %1
call :checkpath %1.exe
call :checkpath %1.cmd
call :checkpath %1.bat
:checkpath
if "%~$PATH:1" NEQ "" echo %~$PATH:1
Yeah! Finally a which command on Windows! ;]
I have been using this one for a while, it also checks built-in commands