Skip to content

Commit 927ca5d

Browse files
authored
Merge pull request #10264 from The-OpenROAD-Project-staging/web-server-blocking
web: make web_server block like GUI, add -web flag
2 parents 578be38 + e8d84de commit 927ca5d

19 files changed

Lines changed: 769 additions & 161 deletions

BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ cc_binary(
197197
"//src/gui",
198198
"//src/sta:opensta_lib",
199199
"//src/utl",
200+
"//src/web",
200201
"@boost.stacktrace",
201202
"@rules_cc//cc/runfiles", # sets BAZEL_CURRENT_REPOSITORY
202203
"@tcl_lang//:tcl",

src/Main.cc

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
#include <strings.h>
66

77
#include <array>
8+
#include <charconv>
89
#include <climits>
910
#include <clocale>
1011
#include <csignal>
1112
#include <cstdio>
1213
#include <cstdlib>
14+
#include <cstring>
1315
#include <filesystem>
1416
#include <iostream>
1517
#include <memory>
@@ -37,6 +39,7 @@
3739
#include "sta/StringUtil.hh"
3840
#include "utl/Logger.h"
3941
#include "utl/decode.h"
42+
#include "web/web.h"
4043

4144
#ifdef BAZEL_CURRENT_REPOSITORY
4245
#include "bazel/tcl_library_init.h"
@@ -93,6 +96,8 @@ static const char* metrics_filename = nullptr;
9396
static const char* read_odb_filename = nullptr;
9497
static bool no_settings = false;
9598
static bool minimize = false;
99+
static bool web_enabled = false;
100+
static const char* web_port_arg = nullptr;
96101

97102
static const char* init_filename = ".openroad";
98103

@@ -249,6 +254,8 @@ int main(int argc, char* argv[])
249254
read_odb_filename = findCmdLineKey(argc, argv, "-db");
250255
no_settings = findCmdLineFlag(argc, argv, "-no_settings");
251256
minimize = findCmdLineFlag(argc, argv, "-minimize");
257+
web_enabled = findCmdLineFlag(argc, argv, "-web");
258+
web_port_arg = findCmdLineKey(argc, argv, "-web_port");
252259

253260
cmd_argc = argc;
254261
cmd_argv = argv;
@@ -375,6 +382,22 @@ static int tclAppInit(int& argc,
375382
ord::initOpenRoad(
376383
interp, log_filename, metrics_filename, exit_after_cmd_file);
377384

385+
// Start the web server before splash/thread output so the
386+
// WebLogSink captures all startup messages for the browser console.
387+
if (web_enabled) {
388+
int port = 0;
389+
if (web_port_arg) {
390+
const char* end = web_port_arg + std::strlen(web_port_arg);
391+
auto [ptr, ec] = std::from_chars(web_port_arg, end, port);
392+
if (ec != std::errc{} || ptr != end || port < 0 || port > 65535) {
393+
fprintf(
394+
stderr, "Error: invalid -web_port value '%s'\n", web_port_arg);
395+
exit(EXIT_FAILURE);
396+
}
397+
}
398+
ord::OpenRoad::openRoad()->getWebServer()->serve(port);
399+
}
400+
378401
bool no_splash = findCmdLineFlag(argc, argv, "-no_splash");
379402
if (!no_splash) {
380403
showSplash();
@@ -389,7 +412,11 @@ static int tclAppInit(int& argc,
389412
ord::OpenRoad::openRoad()->getThreadCount(), false);
390413
}
391414

392-
const bool gui_enabled = gui::Gui::enabled();
415+
// gui::Gui::enabled() is true when a HeadlessViewer is installed
416+
// (which the web server does). But addRestoreStateCommand() only
417+
// works with the Qt event loop — the web server executes scripts
418+
// directly on the main thread, like the non-GUI path.
419+
const bool gui_enabled = gui::Gui::enabled() && !web_enabled;
393420

394421
if (read_odb_filename) {
395422
std::string cmd = fmt::format("read_db {{{}}}", read_odb_filename);
@@ -448,6 +475,12 @@ static int tclAppInit(int& argc,
448475
}
449476
}
450477
}
478+
479+
// Block until the web server is stopped (like QApplication::exec()
480+
// for the GUI). After this returns, fall through to readline.
481+
if (web_enabled) {
482+
ord::OpenRoad::openRoad()->getWebServer()->waitForStop();
483+
}
451484
}
452485
#ifdef ENABLE_READLINE
453486
// Initialize readline unless the Qt GUI is active (it has its own
@@ -484,15 +517,18 @@ int ord::tclInit(Tcl_Interp* interp)
484517
static void showUsage(const char* prog, const char* init_filename)
485518
{
486519
printf("Usage: %s [-help] [-version] [-no_init] [-no_splash] [-exit] ", prog);
487-
printf("[-gui] [-threads count|max] [-log file_name] [-metrics file_name] ");
488-
printf("[-db file_name] [-no_settings] [-minimize] cmd_file\n");
520+
printf("[-gui] [-web] [-threads count|max] [-log file_name] ");
521+
printf("[-metrics file_name] [-db file_name] [-no_settings] [-minimize] ");
522+
printf("cmd_file\n");
489523
printf(" -help show help and exit\n");
490524
printf(" -version show version and exit\n");
491525
printf(" -no_init do not read %s init file\n", init_filename);
492526
printf(" -threads count|max use count threads\n");
493527
printf(" -no_splash do not show the license splash at startup\n");
494528
printf(" -exit exit after reading cmd_file\n");
495529
printf(" -gui start in gui mode\n");
530+
printf(" -web start in web viewer mode\n");
531+
printf(" -web_port port web server port (default auto-assigned)\n");
496532
printf(" -minimize start the gui minimized\n");
497533
printf(" -no_settings do not load the previous gui settings\n");
498534
#ifdef ENABLE_PYTHON3

src/web/BUILD

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ py_binary(
1717
main = "src/embed_report_assets.py",
1818
)
1919

20+
py_binary(
21+
name = "embed_web_assets",
22+
srcs = ["src/embed_web_assets.py"],
23+
main = "src/embed_web_assets.py",
24+
)
25+
2026
genrule(
2127
name = "report_assets",
2228
srcs = [
@@ -67,6 +73,39 @@ genrule(
6773
tools = [":embed_report_assets"],
6874
)
6975

76+
_WEB_ASSET_FILES = [
77+
"src/index.html",
78+
"src/style.css",
79+
"src/theme.js",
80+
"src/coordinates.js",
81+
"src/ui-utils.js",
82+
"src/checkbox-tree-model.js",
83+
"src/vis-tree.js",
84+
"src/websocket-manager.js",
85+
"src/websocket-tile-layer.js",
86+
"src/display-controls.js",
87+
"src/inspector.js",
88+
"src/ruler.js",
89+
"src/tcl-completer.js",
90+
"src/hierarchy-browser.js",
91+
"src/menu-bar.js",
92+
"src/clock-tree-widget.js",
93+
"src/schematic-widget.js",
94+
"src/charts-widget.js",
95+
"src/timing-widget.js",
96+
"src/drc-widget.js",
97+
"src/main.js",
98+
]
99+
100+
genrule(
101+
name = "web_assets",
102+
srcs = _WEB_ASSET_FILES,
103+
outs = ["src/web_assets.cpp"],
104+
cmd = "$(execpath :embed_web_assets) --output $@ " +
105+
" ".join(["$(location %s)" % f for f in _WEB_ASSET_FILES]),
106+
tools = [":embed_web_assets"],
107+
)
108+
70109
cc_library(
71110
name = "web",
72111
srcs = [
@@ -86,6 +125,7 @@ cc_library(
86125
"src/timing_report.cpp",
87126
"src/timing_report.h",
88127
"src/web.cpp",
128+
"src/web_assets.h",
89129
"src/web_chart.cpp",
90130
"src/web_chart.h",
91131
"src/web_painter.cpp",
@@ -94,6 +134,7 @@ cc_library(
94134
"src/web_viewer_hook.cpp",
95135
"src/web_viewer_hook.h",
96136
":report_assets",
137+
":web_assets",
97138
],
98139
hdrs = [
99140
"include/web/web.h",

src/web/CMakeLists.txt

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,36 @@ swig_lib(NAME web
1212
${ODB_HOME}/include
1313
)
1414

15+
set(WEB_ASSET_FILES
16+
src/index.html
17+
src/style.css
18+
src/theme.js
19+
src/coordinates.js
20+
src/ui-utils.js
21+
src/checkbox-tree-model.js
22+
src/vis-tree.js
23+
src/websocket-manager.js
24+
src/websocket-tile-layer.js
25+
src/display-controls.js
26+
src/inspector.js
27+
src/ruler.js
28+
src/tcl-completer.js
29+
src/hierarchy-browser.js
30+
src/menu-bar.js
31+
src/clock-tree-widget.js
32+
src/schematic-widget.js
33+
src/charts-widget.js
34+
src/timing-widget.js
35+
src/drc-widget.js
36+
src/main.js
37+
)
38+
39+
# Prepend CMAKE_CURRENT_SOURCE_DIR to each asset file.
40+
set(WEB_ASSET_PATHS "")
41+
foreach(f ${WEB_ASSET_FILES})
42+
list(APPEND WEB_ASSET_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/${f})
43+
endforeach()
44+
1545
set(REPORT_ASSETS_CPP ${CMAKE_CURRENT_BINARY_DIR}/report_assets.cpp)
1646
add_custom_command(
1747
OUTPUT ${REPORT_ASSETS_CPP}
@@ -39,29 +69,22 @@ add_custom_command(
3969
${CMAKE_CURRENT_SOURCE_DIR}/src/main.js
4070
DEPENDS
4171
src/embed_report_assets.py
42-
src/style.css
43-
src/theme.js
44-
src/coordinates.js
45-
src/ui-utils.js
46-
src/checkbox-tree-model.js
47-
src/vis-tree.js
48-
src/websocket-manager.js
49-
src/websocket-tile-layer.js
50-
src/display-controls.js
51-
src/inspector.js
52-
src/ruler.js
53-
src/tcl-completer.js
54-
src/hierarchy-browser.js
55-
src/menu-bar.js
56-
src/clock-tree-widget.js
57-
src/schematic-widget.js
58-
src/charts-widget.js
59-
src/timing-widget.js
60-
src/drc-widget.js
61-
src/main.js
72+
${WEB_ASSET_FILES}
6273
COMMENT "Generating report_assets.cpp"
6374
)
6475

76+
set(WEB_ASSETS_CPP ${CMAKE_CURRENT_BINARY_DIR}/web_assets.cpp)
77+
add_custom_command(
78+
OUTPUT ${WEB_ASSETS_CPP}
79+
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/src/embed_web_assets.py
80+
--output ${WEB_ASSETS_CPP}
81+
${WEB_ASSET_PATHS}
82+
DEPENDS
83+
src/embed_web_assets.py
84+
${WEB_ASSET_FILES}
85+
COMMENT "Generating web_assets.cpp"
86+
)
87+
6588
target_sources(web
6689
PRIVATE
6790
src/clock_tree_report.cpp
@@ -78,6 +101,7 @@ target_sources(web
78101
src/web_viewer_hook.cpp
79102
src/MakeWeb.cpp
80103
${REPORT_ASSETS_CPP}
104+
${WEB_ASSETS_CPP}
81105
)
82106

83107
target_link_libraries(web

src/web/include/web/web.h

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
#pragma once
55

6+
#include <condition_variable>
7+
#include <cstdint>
68
#include <functional>
79
#include <memory>
10+
#include <mutex>
811
#include <string>
912
#include <thread>
1013
#include <vector>
@@ -22,6 +25,10 @@ namespace sta {
2225
class dbSta;
2326
}
2427

28+
namespace spdlog::sinks {
29+
class sink;
30+
}
31+
2532
namespace web {
2633

2734
struct Color;
@@ -33,23 +40,29 @@ struct TclEvaluator;
3340
class TimingReport;
3441
class WebViewerHook;
3542

36-
// Factory that creates, starts, and returns a shutdown callback for a
37-
// Listener. Defined in web.cpp (where Listener is local); called from
38-
// web_serve.cpp.
39-
std::function<void()> createAndRunListener(
43+
// Returned by createAndRunListener: a shutdown callback and the actual
44+
// port the listener bound to (useful when the caller passes port 0).
45+
struct ListenerHandle
46+
{
47+
std::function<void()> shutdown;
48+
uint16_t port;
49+
};
50+
51+
// Factory that creates, starts, and returns a handle for a Listener.
52+
// Defined in web.cpp (where Listener is local); called from web_serve.cpp.
53+
ListenerHandle createAndRunListener(
4054
boost::asio::io_context& ioc,
4155
const boost::asio::ip::tcp::endpoint& endpoint,
4256
std::shared_ptr<TileGenerator> generator,
4357
std::shared_ptr<TclEvaluator> tcl_eval,
4458
std::shared_ptr<TimingReport> timing_report,
4559
std::shared_ptr<ClockTreeReport> clock_report,
46-
const std::string& doc_root,
4760
utl::Logger* logger,
4861
WebViewerHook* viewer_hook);
4962

50-
// A layout web server. serve() starts the server in background threads
51-
// and returns immediately so the Tcl interpreter remains interactive
52-
// (analogous to -gui for the Qt frontend).
63+
// A layout web server. serve() starts the server in background I/O
64+
// threads; waitForStop() blocks the calling thread until requestStop()
65+
// is called, mirroring gui::show / gui::hide.
5366

5467
class WebServer
5568
{
@@ -64,11 +77,19 @@ class WebServer
6477
// Start the web server on the given port. Launches background
6578
// I/O threads and returns immediately. A second call is a no-op if
6679
// the server is already running.
67-
void serve(int port, const std::string& doc_root);
80+
void serve(int port);
6881

6982
// True after serve() returns and before stop/destructor.
7083
bool isRunning() const { return ioc_ != nullptr; }
7184

85+
// Block the calling thread until requestStop() is called, then
86+
// tear down the server. Typically called on the main/Tcl thread.
87+
void waitForStop();
88+
89+
// Signal waitForStop() to return. Safe to call from any thread
90+
// (e.g. an ASIO worker thread executing a Tcl command).
91+
void requestStop();
92+
7293
void saveReport(const std::string& filename,
7394
int max_setup_paths,
7495
int max_hold_paths);
@@ -94,6 +115,7 @@ class WebServer
94115
int num_threads_ = 0;
95116
std::shared_ptr<TileGenerator> generator_;
96117
std::unique_ptr<WebViewerHook> viewer_hook_;
118+
std::shared_ptr<spdlog::sinks::sink> log_sink_;
97119

98120
// Background I/O context and worker threads (non-null while running).
99121
std::unique_ptr<boost::asio::io_context> ioc_;
@@ -103,6 +125,12 @@ class WebServer
103125
// io_context is destroyed — prevents a crash where the acceptor's
104126
// destructor references the io_context that's mid-destruction.
105127
std::function<void()> shutdown_listener_;
128+
129+
// Blocking support: waitForStop() sleeps on stop_cv_ until
130+
// requestStop() sets stop_requested_.
131+
std::mutex stop_mutex_;
132+
std::condition_variable stop_cv_;
133+
bool stop_requested_ = false;
106134
};
107135

108136
} // namespace web

0 commit comments

Comments
 (0)