Skip to content

Commit 2be6d04

Browse files
Merge branch 'master' into feature-Web-GUI
Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
2 parents e3efb15 + 302bea7 commit 2be6d04

20 files changed

Lines changed: 871 additions & 203 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: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
#include <spdlog/common.h>
77

8+
#include <condition_variable>
9+
#include <cstdint>
810
#include <functional>
911
#include <memory>
12+
#include <mutex>
1013
#include <string>
1114
#include <thread>
1215
#include <vector>
@@ -24,6 +27,10 @@ namespace sta {
2427
class dbSta;
2528
}
2629

30+
namespace spdlog::sinks {
31+
class sink;
32+
}
33+
2734
namespace web {
2835

2936
struct Color;
@@ -35,23 +42,29 @@ struct TclEvaluator;
3542
class TimingReport;
3643
class WebViewerHook;
3744

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

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

5669
class WebServer
5770
{
@@ -66,11 +79,19 @@ class WebServer
6679
// Start the web server on the given port. Launches background
6780
// I/O threads and returns immediately. A second call is a no-op if
6881
// the server is already running.
69-
void serve(int port, const std::string& doc_root);
82+
void serve(int port);
7083

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

87+
// Block the calling thread until requestStop() is called, then
88+
// tear down the server. Typically called on the main/Tcl thread.
89+
void waitForStop();
90+
91+
// Signal waitForStop() to return. Safe to call from any thread
92+
// (e.g. an ASIO worker thread executing a Tcl command).
93+
void requestStop();
94+
7495
void saveReport(const std::string& filename,
7596
int max_setup_paths,
7697
int max_hold_paths);
@@ -102,6 +123,7 @@ class WebServer
102123
int num_threads_ = 0;
103124
std::shared_ptr<TileGenerator> generator_;
104125
std::unique_ptr<WebViewerHook> viewer_hook_;
126+
std::shared_ptr<spdlog::sinks::sink> log_sink_;
105127

106128
// Background I/O context and worker threads (non-null while running).
107129
std::unique_ptr<boost::asio::io_context> ioc_;
@@ -115,6 +137,11 @@ class WebServer
115137
// Held so stop() can remove it from the Logger before viewer_hook_ is
116138
// destroyed — the sink stores a raw pointer into the hook.
117139
spdlog::sink_ptr log_sink_;
140+
// Blocking support: waitForStop() sleeps on stop_cv_ until
141+
// requestStop() sets stop_requested_.
142+
std::mutex stop_mutex_;
143+
std::condition_variable stop_cv_;
144+
bool stop_requested_ = false;
118145
};
119146

120147
} // namespace web

0 commit comments

Comments
 (0)