Skip to content

Commit c322bd4

Browse files
committed
test windows ci
Signed-off-by: Andrei Gherghescu <[email protected]>
1 parent d6f822f commit c322bd4

File tree

7 files changed

+659
-99
lines changed

7 files changed

+659
-99
lines changed

.github/workflows/ci.yml

Lines changed: 385 additions & 1 deletion
Large diffs are not rendered by default.

docs/book/src/fundamentals/static_image_export.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ plotly = { version = "0.13", features = ["static_export_default"] }
4343

4444
2. **Browser Installation**: You need Chrome/Chromium or Firefox installed
4545

46-
3. **Environment Variable** (optional): Set `WEBDRIVER_PATH` to specify custom WebDriver location
46+
3. **Environment Variable** (optional): Set `WEBDRIVER_PATH` to specify custom WebDriver binary location (should point to the full executable path)
4747
```bash
4848
export WEBDRIVER_PATH=/path/to/chromedriver
4949
```

plotly_static/build.rs

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@ use webdriver_downloader::prelude::*;
1212
#[cfg(all(feature = "geckodriver", feature = "chromedriver"))]
1313
compile_error!("Only one of 'geckodriver' or 'chromedriver' features can be enabled at a time.");
1414

15-
// Define the WEBDRIVER_BIN constant based on the enabled feature
16-
#[cfg(feature = "geckodriver")]
17-
const WEBDRIVER_BIN: &str = "geckodriver";
18-
19-
#[cfg(feature = "chromedriver")]
20-
const WEBDRIVER_BIN: &str = "chromedriver";
15+
// Enforce that at least one driver feature is enabled
16+
#[cfg(not(any(feature = "geckodriver", feature = "chromedriver")))]
17+
compile_error!("At least one of 'geckodriver' or 'chromedriver' features must be enabled.");
2118

2219
#[cfg(target_os = "windows")]
2320
const DRIVER_EXT: &str = ".exe";
@@ -61,33 +58,33 @@ fn user_bin_dir() -> PathBuf {
6158

6259
/// Check if a driver is already installed at the given path from environment
6360
/// variable
64-
fn is_webdriver_available(env_var: &str, executable_name: &str) -> bool {
61+
fn is_webdriver_available(env_var: &str, bin_name: &str) -> bool {
6562
// First check environment variable path
6663
if let Ok(path) = env::var(env_var) {
67-
let exe_path = if cfg!(windows) && !path.to_lowercase().ends_with(".exe") {
64+
let bin_path = if cfg!(target_os = "windows") && !path.to_lowercase().ends_with(".exe") {
6865
format!("{path}{DRIVER_EXT}")
6966
} else {
7067
path
7168
};
72-
let exe = Path::new(&exe_path);
69+
let exe = Path::new(&bin_path);
7370
if exe.exists() && exe.is_file() {
74-
println!("{executable_name} found at path specified in {env_var}: {exe_path}");
71+
println!("{bin_name} found at path specified in {env_var}: {bin_path}");
7572
return true;
7673
}
7774
}
7875

7976
// Check if webdriver exists in user's bin directory
8077
let bin_dir = user_bin_dir();
81-
let bin_path = bin_dir.join(format!("{executable_name}{DRIVER_EXT}"));
78+
let bin_path = bin_dir.join(format!("{bin_name}{DRIVER_EXT}"));
8279
if bin_path.exists() && bin_path.is_file() {
8380
println!(
8481
"{} found in user's bin directory: {}",
85-
executable_name,
82+
bin_name,
8683
bin_path.display()
8784
);
8885
println!(
8986
"cargo:rustc-env=WEBDRIVER_DOWNLOAD_PATH={}",
90-
bin_dir.to_string_lossy()
87+
bin_path.to_string_lossy()
9188
);
9289
return true;
9390
}
@@ -165,7 +162,7 @@ fn setup_driver(config: &WebdriverDownloadConfig) -> Result<()> {
165162
webdriver_bin_dir.display()
166163
)
167164
})?;
168-
let webdriver_bin = webdriver_bin_dir.join(WEBDRIVER_BIN);
165+
let webdriver_bin = webdriver_bin_dir.join(config.driver_name);
169166

170167
println!(
171168
"cargo::rerun-if-changed={}",
@@ -199,12 +196,38 @@ fn setup_driver(config: &WebdriverDownloadConfig) -> Result<()> {
199196

200197
println!(
201198
"cargo:rustc-env=WEBDRIVER_DOWNLOAD_PATH={}",
202-
webdriver_bin_dir.to_string_lossy()
199+
webdriver_bin.to_string_lossy()
203200
);
204201

205202
Ok(())
206203
}
207204

205+
#[cfg(feature = "chromedriver")]
206+
fn get_chrome_path() -> Result<PathBuf> {
207+
if let Ok(chrome_path) = env::var(CHROME_PATH_ENV) {
208+
let path = PathBuf::from(&chrome_path);
209+
if path.exists() {
210+
Ok(path)
211+
} else {
212+
Err(anyhow!("Chrome not found on path: {}", chrome_path)).with_context(|| {
213+
format!("Please set {CHROME_PATH_ENV} to a valid Chrome installation")
214+
})
215+
}
216+
} else {
217+
let new_browser_path = os_specific::chromedriver_for_testing::default_browser_path()?;
218+
let old_browser_path = os_specific::chromedriver_old::default_browser_path()?;
219+
if new_browser_path.exists() {
220+
Ok(new_browser_path)
221+
} else if old_browser_path.exists() {
222+
Ok(old_browser_path)
223+
} else {
224+
Err(anyhow!("Chrome browser not detected")).with_context(|| {
225+
format!("Use {CHROME_PATH_ENV} to point to a valid Chrome installation")
226+
})
227+
}
228+
}
229+
}
230+
208231
#[cfg(feature = "geckodriver")]
209232
fn get_firefox_path() -> Result<PathBuf> {
210233
if let Ok(firefox_path) = env::var(FIREFOX_PATH_ENV) {
@@ -234,32 +257,6 @@ fn get_firefox_path() -> Result<PathBuf> {
234257
}
235258
}
236259

237-
#[cfg(feature = "chromedriver")]
238-
fn get_chrome_path() -> Result<PathBuf> {
239-
if let Ok(chrome_path) = env::var(CHROME_PATH_ENV) {
240-
let path = PathBuf::from(&chrome_path);
241-
if path.exists() {
242-
Ok(path)
243-
} else {
244-
Err(anyhow!("Chrome not found on path: {}", chrome_path)).with_context(|| {
245-
format!("Please set {CHROME_PATH_ENV} to a valid Chrome installation")
246-
})
247-
}
248-
} else {
249-
let new_browser_path = os_specific::chromedriver_for_testing::default_browser_path()?;
250-
let old_browser_path = os_specific::chromedriver_old::default_browser_path()?;
251-
if new_browser_path.exists() {
252-
Ok(new_browser_path)
253-
} else if old_browser_path.exists() {
254-
Ok(old_browser_path)
255-
} else {
256-
Err(anyhow!("Chrome browser not detected")).with_context(|| {
257-
format!("Use {CHROME_PATH_ENV} to point to a valid Chrome installation")
258-
})
259-
}
260-
}
261-
}
262-
263260
fn get_browser_version(path: &PathBuf) -> Result<String> {
264261
let output = Command::new(path)
265262
.arg("--version")
@@ -307,16 +304,15 @@ fn main() -> Result<()> {
307304
if cfg!(feature = "webdriver_download") {
308305
println!("cargo:rerun-if-changed=src/lib.rs");
309306
let webdriver_bin_dir = user_bin_dir();
310-
let webdriver_bin = webdriver_bin_dir.join(WEBDRIVER_BIN);
311307
println!(
312308
"cargo::rerun-if-changed={}",
313-
webdriver_bin.to_string_lossy()
309+
webdriver_bin_dir.to_string_lossy()
314310
);
315311

316312
#[cfg(feature = "chromedriver")]
317313
{
318314
let config = WebdriverDownloadConfig {
319-
driver_name: WEBDRIVER_BIN,
315+
driver_name: "chromedriver",
320316
path_env: CHROMEDRIVER_PATH_ENV,
321317
get_browser_path: get_chrome_path,
322318
};
@@ -326,15 +322,33 @@ fn main() -> Result<()> {
326322
#[cfg(feature = "geckodriver")]
327323
{
328324
let config = WebdriverDownloadConfig {
329-
driver_name: WEBDRIVER_BIN,
325+
driver_name: "geckodriver",
330326
path_env: GECKODRIVER_PATH_ENV,
331327
get_browser_path: get_firefox_path,
332328
};
333329
setup_driver(&config)?;
334330
}
331+
332+
#[cfg(not(any(feature = "chromedriver", feature = "geckodriver")))]
333+
{
334+
println!("cargo::warning=No specific driver feature enabled, skipping driver setup");
335+
}
335336
} else {
336-
let msg = format!("'webdriver_download' feature disabled. Please install a {GECKODRIVER_PATH_ENV} or {CHROMEDRIVER_PATH_ENV} version manually and make the environment variable 'WEBDRIVER_PATH' point to it.");
337-
println!("cargo::warning={msg}");
337+
#[cfg(feature = "chromedriver")]
338+
{
339+
let msg = format!("'webdriver_download' feature disabled. Please install a {CHROMEDRIVER_PATH_ENV} version manually and make the environment variable 'WEBDRIVER_PATH' point to it.");
340+
println!("cargo::warning={msg}");
341+
}
342+
#[cfg(feature = "geckodriver")]
343+
{
344+
let msg = format!("'webdriver_download' feature disabled. Please install a {} version manually and make the environment variable 'WEBDRIVER_PATH' point to it.",
345+
GECKODRIVER_PATH_ENV);
346+
println!("cargo::warning={msg}");
347+
}
348+
#[cfg(not(any(feature = "chromedriver", feature = "geckodriver")))]
349+
{
350+
println!("cargo::warning='webdriver_download' feature disabled and no driver feature enabled");
351+
}
338352
}
339353
Ok(())
340354
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use plotly_static::{ImageFormat, StaticExporterBuilder};
2+
use serde_json::json;
3+
4+
fn main() {
5+
// Set CHROME_PATH to test the binary capability
6+
std::env::set_var("CHROME_PATH", "/usr/bin/google-chrome");
7+
8+
let plot = json!({
9+
"data": [{
10+
"type": "scatter",
11+
"x": [1, 2, 3],
12+
"y": [4, 5, 6]
13+
}],
14+
"layout": {}
15+
});
16+
17+
let mut exporter = StaticExporterBuilder::default()
18+
.build()
19+
.expect("Failed to build StaticExporter");
20+
21+
// Test that we can export with the custom Chrome binary
22+
let svg_data = exporter
23+
.write_to_string(&plot, ImageFormat::SVG, 800, 600, 1.0)
24+
.expect("Failed to export plot");
25+
26+
println!("Successfully exported SVG with custom Chrome binary!");
27+
println!("SVG length: {} characters", svg_data.len());
28+
}

plotly_static/src/lib.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,21 @@
9999
//! .expect("Failed to build StaticExporter");
100100
//! ```
101101
//!
102+
//! ### Browser Binary Configuration
103+
//!
104+
//! You can specify custom browser binaries using environment variables:
105+
//!
106+
//! ```bash
107+
//! # For Chrome/Chromium
108+
//! export CHROME_PATH="/path/to/chrome"
109+
//!
110+
//! # For Firefox
111+
//! export FIREFOX_PATH="/path/to/firefox"
112+
//! ```
113+
//!
114+
//! The library will automatically use these binaries when creating WebDriver
115+
//! sessions.
116+
//!
102117
//! ### String Export
103118
//!
104119
//! ```no_run
@@ -185,7 +200,7 @@
185200
//! ### WebDriver Configuration
186201
//!
187202
//! Set the `WEBDRIVER_PATH` environment variable to specify a custom WebDriver
188-
//! location:
203+
//! binary location (should point to the full executable path):
189204
//!
190205
//! ```bash
191206
//! export WEBDRIVER_PATH=/path/to/chromedriver
@@ -261,7 +276,7 @@ use webdriver::WebDriver;
261276

262277
use crate::template::{IMAGE_EXPORT_JS_SCRIPT, PDF_EXPORT_JS_SCRIPT};
263278

264-
#[cfg(feature = "chromedriver")]
279+
#[cfg(all(feature = "chromedriver", not(target_os = "windows")))]
265280
const CHROME_DEFAULT_CAPS: [&str; 23] = [
266281
"--headless",
267282
"--no-sandbox",
@@ -290,6 +305,46 @@ const CHROME_DEFAULT_CAPS: [&str; 23] = [
290305
"--v=1",
291306
];
292307

308+
#[cfg(all(feature = "chromedriver", target_os = "windows"))]
309+
const CHROME_DEFAULT_CAPS: [&str; 36] = [
310+
"--headless",
311+
"--no-sandbox",
312+
"--disable-gpu-sandbox",
313+
"--disable-dev-shm-usage",
314+
"--disable-extensions",
315+
"--disable-background-networking",
316+
"--disable-sync",
317+
"--disable-translate",
318+
"--disable-background-timer-throttling",
319+
"--disable-renderer-backgrounding",
320+
"--disable-features=VizDisplayCompositor",
321+
"--memory-pressure-off",
322+
"--disable-plugins",
323+
"--disable-features=TranslateUI",
324+
"--remote-debugging-port=0",
325+
"--disable-web-security",
326+
"--disable-breakpad",
327+
"--no-first-run",
328+
"--no-default-browser-check",
329+
// Additional flags for better PDF generation support
330+
"--disable-backgrounding-occluded-windows",
331+
"--disable-ipc-flooding-protection",
332+
"--enable-logging",
333+
"--v=1",
334+
// Additional flags for Windows CI graphics issues
335+
"--disable-gpu",
336+
"--disable-software-rasterizer",
337+
"--disable-accelerated-2d-canvas",
338+
"--disable-accelerated-jpeg-decoding",
339+
"--disable-accelerated-mjpeg-decode",
340+
"--disable-accelerated-video-decode",
341+
"--disable-accelerated-video-encode",
342+
"--disable-gpu-compositing",
343+
"--disable-gpu-rasterization",
344+
"--disable-gpu-sandbox",
345+
"--disable-accelerated-layers",
346+
];
347+
293348
#[cfg(feature = "geckodriver")]
294349
const FIREFOX_DEFAULT_CAPS: [&str; 2] = [
295350
"-headless", // Essential for headless operation (single dash for Firefox)
@@ -992,6 +1047,19 @@ impl StaticExporter {
9921047

9931048
browser_opts.insert("args".to_string(), serde_json::json!(browser_args));
9941049

1050+
// Add Chrome binary capability if CHROME_PATH is set
1051+
#[cfg(feature = "chromedriver")]
1052+
if let Ok(chrome_path) = std::env::var("CHROME_PATH") {
1053+
browser_opts.insert("binary".to_string(), serde_json::json!(chrome_path));
1054+
debug!("Added Chrome binary capability: {chrome_path}");
1055+
}
1056+
// Add Firefox binary capability if FIREFOX_PATH is set
1057+
#[cfg(feature = "geckodriver")]
1058+
if let Ok(firefox_path) = std::env::var("FIREFOX_PATH") {
1059+
browser_opts.insert("binary".to_string(), serde_json::json!(firefox_path));
1060+
debug!("Added Firefox binary capability: {}", firefox_path);
1061+
}
1062+
9951063
caps.insert(
9961064
"browserName".to_string(),
9971065
serde_json::json!(get_browser_name()),

plotly_static/src/template.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ fn offline_js_sources() -> String {
243243
let local_html2pdf_js = include_str!("../resource/html2pdf.bundle.min.js");
244244
format!(
245245
"<script type=\"text/javascript\">{local_plotly_js}</script>\n
246-
<script type=\"text/javascript\">{local_tex_svg_js}</script>\n,
246+
<script type=\"text/javascript\">{local_tex_svg_js}</script>\n
247247
<script type=\"text/javascript\">{local_html2pdf_js}</script>\n"
248248
)
249249
.to_string()

0 commit comments

Comments
 (0)