2222from sphinx_autobuild .filter import IgnoreFilter
2323from sphinx_autobuild .middleware import JavascriptInjectorMiddleware
2424from sphinx_autobuild .server import RebuildServer
25- from sphinx_autobuild .utils import find_free_port , open_browser , show_message
25+ from sphinx_autobuild .utils import find_free_port , is_port_available , open_browser , show_message
2626
2727
2828def main (argv = ()):
@@ -45,17 +45,26 @@ def main(argv=()):
4545 serve_dir .mkdir (parents = True , exist_ok = True )
4646
4747 host_name = args .host
48- port_num = args .port or find_free_port ()
49- url_host = f"{ host_name } :{ port_num } "
48+
49+ # Resolve port:
50+ # - If user specified --port 0, always find a free port
51+ # - If user specified --port N (N > 0), use that port (may fail if unavailable)
52+ # - If user didn't specify --port, use 8000 (or free port if 8000 is taken)
53+ if args .port == 0 :
54+ # Auto-find mode
55+ port_num = find_free_port ()
56+ port_explicitly_set = False
57+ elif args .port is not None :
58+ # User specified a specific port
59+ port_num = args .port
60+ port_explicitly_set = True
61+ else :
62+ # Default: try 8000, but allow fallback to free port
63+ port_num = 8000
64+ port_explicitly_set = False
5065
5166 pre_build_commands = list (map (shlex .split , args .pre_build ))
5267 post_build_commands = list (map (shlex .split , args .post_build ))
53- builder = Builder (
54- build_args ,
55- url_host = url_host ,
56- pre_build_commands = pre_build_commands ,
57- post_build_commands = post_build_commands ,
58- )
5968
6069 watch_dirs = [src_dir ] + args .additional_watched_dirs
6170 ignore_dirs = [
@@ -80,7 +89,57 @@ def main(argv=()):
8089 ]
8190 ignore_dirs = list (filter (None , ignore_dirs ))
8291 ignore_handler = IgnoreFilter (ignore_dirs , args .re_ignore )
83- app = _create_app (watch_dirs , ignore_handler , builder , serve_dir , url_host )
92+
93+ _run_with_port_fallback (
94+ host_name ,
95+ port_num ,
96+ port_explicitly_set ,
97+ args ,
98+ watch_dirs ,
99+ ignore_handler ,
100+ build_args ,
101+ pre_build_commands ,
102+ post_build_commands ,
103+ serve_dir ,
104+ )
105+
106+
107+ def _run_with_port_fallback (
108+ host_name ,
109+ port_num ,
110+ port_explicitly_set ,
111+ args ,
112+ watch_dirs ,
113+ ignore_handler ,
114+ build_args ,
115+ pre_build_commands ,
116+ post_build_commands ,
117+ serve_dir ,
118+ ):
119+ """Run the server with automatic port fallback on binding failure."""
120+ # Check if the port is available BEFORE doing anything else
121+ if not is_port_available (host_name , port_num ):
122+ if port_explicitly_set :
123+ show_message (
124+ f"Error: Cannot bind to { host_name } :{ port_num } . "
125+ f"The port is already in use. Use --port 0 to automatically find a free port."
126+ )
127+ sys .exit (1 )
128+ else :
129+ show_message (
130+ f"Port { port_num } already in use. Attempting to find a free port..."
131+ )
132+ port_num = find_free_port ()
133+ show_message (f"Using port { port_num } instead." )
134+
135+ url_host = f"{ host_name } :{ port_num } "
136+
137+ builder = Builder (
138+ build_args ,
139+ url_host = url_host ,
140+ pre_build_commands = pre_build_commands ,
141+ post_build_commands = post_build_commands ,
142+ )
84143
85144 if not args .no_initial_build :
86145 show_message ("Starting initial build" )
@@ -90,6 +149,8 @@ def main(argv=()):
90149 open_browser (url_host , args .delay )
91150
92151 show_message ("Waiting to detect changes..." )
152+ app = _create_app (watch_dirs , ignore_handler , builder , serve_dir , url_host )
153+
93154 try :
94155 uvicorn .run (app , host = host_name , port = port_num , log_level = "warning" )
95156 except KeyboardInterrupt :
@@ -182,8 +243,8 @@ def _add_autobuild_arguments(parser):
182243 group .add_argument (
183244 "--port" ,
184245 type = int ,
185- default = 8000 ,
186- help = "port to serve documentation on. 0 means find and use a free port" ,
246+ default = None ,
247+ help = "port to serve documentation on (defaults to 8000, or a free port if 8000 is in use) " ,
187248 )
188249 group .add_argument (
189250 "--host" ,
0 commit comments