Skip to content

Commit cda16da

Browse files
YO4nobu
authored andcommitted
mswin: improve configure.bat syntax
Introduce shellsplit.cmd command line parsing subroutine. Dealing with the limitations of cmd.exe's command-line parsing forces users to use workarounds, and corner cases still remain. By implementing our own command line splitting, we conceal the complexity and ensure maintainability. Makefile macro definition and opt-dir list now free from quoting. now can use like ``` configure.bat --with-opt-dir=c:/src/zlib;c:/src/libffi CC="cl -std:c11" DEFS=-DOPT_THREADED_CODE=2 ```
1 parent d858bed commit cda16da

3 files changed

Lines changed: 193 additions & 23 deletions

File tree

win32/configure.bat

100755100644
Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ goto :main
77
set %*
88
exit /b
99

10+
:shift
11+
call %~dp0shellsplit.cmd
12+
set "argv1=%argv2%"
13+
set "argv2=%argv%"
14+
if not defined argv1 if defined argv2 goto :shift
15+
exit /b
16+
17+
:take_arg
18+
if defined arg exit /b
19+
if not defined argv2 exit /b
20+
if not "%argv2:~0,1%"=="-" (set "arg=%argv2%" & call :shift)
21+
exit /b
22+
1023
:main
1124
if "%~dp0" == "%CD%\" (
1225
echo don't run in win32 directory.
@@ -25,6 +38,7 @@ call :set "WIN32DIR=%%WIN32DIR:/%~n0:/:=:/:%%"
2538
set "WIN32DIR=%WIN32DIR:~0,-3%"
2639

2740
set configure=%~0
41+
set args=%*
2842
set target=
2943
set optdirs=
3044
set pathlist=
@@ -34,12 +48,21 @@ set debug_configure=
3448
echo>%config_make% # CONFIGURE
3549
type nul > %confargs%
3650
:loop
37-
if [%1] == [] goto :end ;
38-
if "%~1" == "" (shift & goto :loop)
39-
for /f "delims== tokens=1,*" %%I in ("%~1") do ((set "opt=%%I") && (set "arg=%%J"))
40-
set "eq=="
41-
if "%arg%" == "" if not "%~1" == "%opt%=%arg%" (set "eq=")
42-
shift
51+
call :shift
52+
if not defined argv1 goto :end
53+
for /f "delims== tokens=1,*" %%I in (" %argv1% ") do ((set "opt=%%I") && (set "arg=%%J"))
54+
set "opt=%opt:~1%"
55+
if defined arg (
56+
set "eq=="
57+
set "arg=%arg:~0,-1%"
58+
) else (
59+
set "eq="
60+
set "opt=%opt:~0,-1%"
61+
)
62+
if "%opt%"=="" (
63+
echo 1>&2 %configure%: assignment for empty variable name %argv1%
64+
exit /b 1
65+
)
4366
if "%opt%" == "--debug-configure" (
4467
echo on
4568
set "debug_configure=yes"
@@ -85,7 +108,7 @@ for /f "delims== tokens=1,*" %%I in ("%~1") do ((set "opt=%%I") && (set "arg=%%J
85108
)
86109
goto :loop ;
87110
:target
88-
if "%eq%" == "" (set "arg=%~1" & shift)
111+
if "%eq%" == "" call :take_arg
89112
if "%arg%" == "" (
90113
echo 1>&2 %configure%: missing argument for %opt%
91114
exit /b 1
@@ -105,32 +128,37 @@ goto :loop ;
105128
)
106129
goto :unknown_opt
107130
:name
108-
if "%eq%" == "" (set "arg=%~1" & shift)
131+
if "%eq%" == "" call :take_arg
109132
echo>> %config_make% %var% = %arg%
110133
goto :loopend ;
111134
:dir
112-
if "%eq%" == "" (set "arg=%~1" & shift)
135+
if "%eq%" == "" call :take_arg
113136
echo>> %config_make% %opt:~2% = %arg:\=/%
114137
goto :loopend ;
115138
:enable
116-
echo>>%confargs% "%opt%" \
117-
if %enable% == yes (set "opt=%opt:~9%") else (set "opt=%opt:~10%")
118-
if "%opt%" == "install-doc" (
139+
if %enable% == yes (
140+
if "%eq%" == "" call :take_arg
141+
set "feature=%opt:~9%"
142+
) else (
143+
set "feature=%opt:~10%"
144+
)
145+
if %enable% == yes if defined arg (set "enable=%arg%")
146+
if "%feature%" == "install-doc" (
119147
echo>> %config_make% RDOCTARGET = %enable:yes=r%doc
120148
)
121-
if "%opt%" == "install-static-library" (
149+
if "%feature%" == "install-static-library" (
122150
echo>> %config_make% INSTALL_STATIC_LIBRARY = %enable%
123151
)
124-
if "%opt%" == "debug-env" (
152+
if "%feature%" == "debug-env" (
125153
echo>> %config_make% ENABLE_DEBUG_ENV = %enable%
126154
)
127-
if "%opt%" == "devel" (
155+
if "%feature%" == "devel" (
128156
echo>> %config_make% RUBY_DEVEL = %enable%
129157
)
130-
if "%opt%" == "rubygems" (
131-
echo>> %config_make% USE_RUBYGEMS = %enable%
158+
if "%feature%" == "rubygems" (
159+
echo>> %config_make% USE_RUBYGEMS = %enable%
132160
)
133-
goto :loop ;
161+
goto :loopend ;
134162
:withoutarg
135163
echo>>%confargs% "%opt%" \
136164
if "%opt%" == "--without-baseruby" goto :nobaseruby
@@ -140,7 +168,7 @@ goto :loop ;
140168
goto :loop ;
141169
:witharg
142170
if "%opt%" == "--with-static-linked-ext" goto :extstatic
143-
if "%eq%" == "" (set "arg=%~1" & shift)
171+
if "%eq%" == "" call :take_arg
144172
if not "%arg%" == "" (
145173
echo>>%confargs% "%opt%=%arg:$=$$%" \
146174
) else (
@@ -158,7 +186,7 @@ goto :loop ;
158186
:ntver
159187
::- For version constants, see
160188
::- https://learn.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt#remarks
161-
if "%eq%" == "" (set "NTVER=%~1" & shift) else (set "NTVER=%arg%")
189+
if "%eq%" == "" (set "NTVER=%~1" & call :shift) else (set "NTVER=%arg%")
162190
if /i not "%NTVER:~0,2%" == "0x" if /i not "%NTVER:~0,13%" == "_WIN32_WINNT_" (
163191
for %%i in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) do (
164192
call :set NTVER=%%NTVER:%%i=%%i%%
@@ -168,11 +196,11 @@ goto :loop ;
168196
echo>> %config_make% NTVER = %NTVER%
169197
goto :loopend ;
170198
:extout
171-
if "%eq%" == "" (set "arg=%~1" & shift)
199+
if "%eq%" == "" call :take_arg
172200
if not "%arg%" == ".ext" (echo>> %config_make% EXTOUT = %arg%)
173201
goto :loopend ;
174202
:path
175-
if "%eq%" == "" (set "arg=%~1" & shift)
203+
if "%eq%" == "" call :take_arg
176204
set "pathlist=%pathlist%%arg:\=/%;"
177205
goto :loopend ;
178206
:extstatic
@@ -236,7 +264,7 @@ goto :loop ;
236264
echo --with-ntver=0xXXXX target NT version (shouldn't use with old SDK)
237265
echo --with-ntver=_WIN32_WINNT_XXXX
238266
echo --with-ntver=XXXX same as --with-ntver=_WIN32_WINNT_XXXX
239-
echo Note that '[1m=,;[m' need to be enclosed within double quotes in batch file command line.
267+
echo Note that parameters containing spaces must be enclosed within double quotes.
240268
del %confargs% %config_make%
241269
goto :EOF
242270
:unknown_opt

win32/shellsplit.cmd

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
setlocal EnableExtensions DisableDelayedExpansion
2+
if not defined V set V=0
3+
if not defined args (
4+
goto :return_arg
5+
)
6+
set INPUT=%args%
7+
set OUTBUF=
8+
set UNQ=
9+
set QTD=
10+
11+
set INPUT=%INPUT:#=#35%
12+
set INPUT=%INPUT:@=#64%
13+
set "PENDING=%INPUT:"=@sep%" &:: escape double quotes and split consecutive marks
14+
15+
::#### split into unquoted part, quoted part, remains
16+
:loop
17+
18+
for /F "tokens=1,2* delims=@" %%I in (" %PENDING%") do (
19+
set "UNQ=%%I"
20+
set "QTD=%%J"
21+
set "PENDING=%%K"
22+
)
23+
set "UNQ=%UNQ:~1%"
24+
25+
if %V%==1 (
26+
echo unquoted:
27+
(echo UNQ :"%UNQ%")&(echo QTD :"%QTD%")&(echo REST:"%PENDING%")&(if defined OUTBUF echo OUTBUF:"%OUTBUF%")
28+
)
29+
30+
if defined QTD (set "QTD=%QTD:~3%")
31+
if defined PENDING (set "PENDING=%PENDING:~3%")
32+
33+
if %V%==1 (
34+
(echo QTD :"%QTD%")&(echo REST:"%PENDING%")
35+
)
36+
37+
if not defined UNQ if defined OUTBUF (
38+
set concat_next=true
39+
goto :process_unquote
40+
)
41+
42+
set concat_prev=
43+
set concat_check=
44+
if defined UNQ (set "concat_check=%UNQ:~0,1%")
45+
if not "%concat_check%"==" " (set concat_prev=true)
46+
47+
set concat_next=
48+
set concat_check=
49+
if defined UNQ (set "concat_check=%UNQ:~-1%")
50+
if not "%concat_check%"==" " (set concat_next=true)
51+
52+
if not defined concat_prev if defined OUTBUF (
53+
goto :return_arg
54+
)
55+
56+
::#### process unquoted part
57+
:process_unquote
58+
59+
if defined UNQ if "%UNQ: =%"=="" (set UNQ=)
60+
if not defined UNQ goto :process_quoted
61+
62+
for /F "tokens=1* eol=" %%I in ("%UNQ%") do (
63+
set "token=%%I"
64+
set "UNQ=%%J"
65+
)
66+
67+
if %V%==1 (
68+
(echo unq :"%token%")&(echo UNQ :"%UNQ%")
69+
)
70+
71+
set "OUTBUF=%OUTBUF%%token%"
72+
if defined UNQ (
73+
goto :return_arg
74+
) else (
75+
if not defined concat_next (
76+
goto :return_arg
77+
)
78+
)
79+
80+
::#### process quoted part
81+
:process_quoted
82+
83+
if %V%==1 (
84+
echo quoted:
85+
(echo UNQ :"%UNQ%")&(echo QTD :"%QTD%")&(echo REST:"%PENDING%")&(if defined OUTBUF echo OUTBUF:"%OUTBUF%")
86+
)
87+
88+
set "OUTBUF=%OUTBUF%%QTD%"
89+
set QTD=
90+
91+
if not defined PENDING (
92+
goto :return_arg
93+
)
94+
goto :loop
95+
96+
::#### return splitted argv
97+
:return_arg
98+
99+
set "argv=%OUTBUF%"
100+
if defined argv (set "argv=%argv:#64=@%")
101+
if defined argv (set "argv=%argv:#35=#%")
102+
103+
if defined QTD (set QTD="%QTD%")
104+
105+
:: special handling is required because they may contain double quotes
106+
107+
if defined PENDING set PENDING=%PENDING:@sep="%
108+
set args=%UNQ%%QTD%%PENDING%
109+
if defined args set args=%args:#64=@%
110+
if defined args set args=%args:#35=#%
111+
112+
endlocal & set "argv=%argv%" & set args=%args%
113+
114+
exit /b

win32/test_shellsplit.cmd

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@echo off & if not [%1]==[] goto :process
2+
3+
echo.
4+
echo This script demonstrates how shellsplit.cmd works.
5+
echo usage: %0 arg1 arg2...
6+
echo.
7+
echo Prints separated arguments as (arg1)(arg2)...
8+
echo - splits commandline with spaces/tabs. cmd.exe standard rule is ignored.
9+
echo - you can use double quotes to contain spaces/tabs into an argument.
10+
echo - you can not escape double quote.
11+
echo - solitary "" is ignored since cmd.exe variables cannot represent empty value.
12+
exit /b 0
13+
14+
:process
15+
setlocal
16+
set V=0
17+
18+
:: %* can contain meta character inside quote. do not use set "args=%*" here.
19+
set args=%*
20+
21+
:loop
22+
call %~dp0\shellsplit.cmd
23+
if not defined argv goto :end
24+
set /p "tmp=(%argv%)"<NUL
25+
goto :loop
26+
27+
:end
28+
endlocal & exit /b 0

0 commit comments

Comments
 (0)