Skip to content

Commit d11094e

Browse files
RedZapdos123RedZapdos123andrei-ng
authored
fix: allow plotly_static to compile without webdriver feature selected(#402)
When checking out the repository fresh and running `cargo check` at the workspace root, compilation fails because `plotly_static` has no driver features enabled by default. This PR makes `plotly_static` and `potly` crates compile without a webdriver requirement. However a webdriver needs to be selected to export static plots at runtime. Signed-off-by: Mridankan Mandal <xerontitan90@gmail.com> Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --------- Signed-off-by: Mridankan Mandal <xerontitan90@gmail.com> Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Co-authored-by: RedZapdos123 <redzapdos123@example.com> Co-authored-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com>
1 parent b514df8 commit d11094e

7 files changed

Lines changed: 494 additions & 441 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ jobs:
4444
components: clippy
4545
targets: wasm32-unknown-unknown
4646
# lint plotly_static for all features
47+
- run: cargo check -p plotly_static
48+
- run: cargo test -p plotly_static build_without_driver_feature_returns_error
4749
- run: cargo clippy -p plotly_static --features geckodriver,webdriver_download -- -D warnings -A deprecated
4850
- run: cargo clippy -p plotly_static --features chromedriver,webdriver_download -- -D warnings -A deprecated
4951
# lint the main library workspace for non-wasm target

plotly/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,5 @@ image = "0.25"
8888
itertools = ">=0.10, <0.16"
8989
itertools-num = "0.1"
9090
ndarray = "0.17"
91-
plotly_static = { path = "../plotly_static" }
9291
rand_distr = "0.6"
9392
base64 = "0.22"

plotly_static/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ serde_json = "1.0"
6363

6464
### Feature Flags
6565

66+
- To use static export at runtime, enable exactly one of the driver features below.
6667
- `chromedriver`: Use Chromedriver and Chrome/Chromium browser for rendering and export
6768
- `geckodriver`: Use Geckodriver Firefox browser for rendering for rendering and export
6869
- `webdriver_download`: Auto-download the chosen WebDriver binary

plotly_static/build.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
#![cfg_attr(
2+
not(any(feature = "geckodriver", feature = "chromedriver")),
3+
allow(unused)
4+
)]
5+
16
use std::env;
27
use std::fs;
38
use std::path::{Path, PathBuf};
@@ -12,10 +17,6 @@ use webdriver_downloader::prelude::*;
1217
#[cfg(all(feature = "geckodriver", feature = "chromedriver"))]
1318
compile_error!("Only one of 'geckodriver' or 'chromedriver' features can be enabled at a time.");
1419

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.");
18-
1920
#[cfg(target_os = "windows")]
2021
const DRIVER_EXT: &str = ".exe";
2122
#[cfg(not(target_os = "windows"))]

plotly_static/src/lib.rs

Lines changed: 79 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@
5959
//!
6060
//! ## Features and Dependencies
6161
//!
62-
//! ### Required Features
62+
//! ### Driver Features
6363
//!
64-
//! You must enable one of the following features:
64+
//! For compile time only, no specific feature must be provided. However, to use
65+
//! static export at runtime, one of the below features must be enabled.
6566
//!
6667
//! - `chromedriver`: Use Chrome/Chromium for rendering
6768
//! - `geckodriver`: Use Firefox for rendering
@@ -294,6 +295,7 @@ use fantoccini::{wd::Capabilities, Client, ClientBuilder};
294295
#[cfg(not(any(test, feature = "debug")))]
295296
use log::{debug, error, warn};
296297
use serde::Serialize;
298+
#[cfg(any(feature = "chromedriver", feature = "geckodriver"))]
297299
use serde_json::map::Map as JsonMap;
298300
use urlencoding::encode;
299301
use webdriver::WebDriver;
@@ -303,6 +305,9 @@ use crate::template::{image_export_js_script, pdf_export_js_script};
303305
mod template;
304306
mod webdriver;
305307

308+
#[cfg(not(any(feature = "chromedriver", feature = "geckodriver")))]
309+
const DRIVER_FEATURE_REQUIRED: &str = "Static image export at runtime requires enabling either the 'chromedriver' or 'geckodriver' feature.";
310+
306311
/// Supported image formats for static image export.
307312
///
308313
/// This enum defines all the image formats that can be exported from Plotly
@@ -648,6 +653,7 @@ impl StaticExporterBuilder {
648653
}
649654

650655
/// Create a new WebDriver instance based on the spawn_webdriver flag
656+
#[cfg(any(feature = "chromedriver", feature = "geckodriver"))]
651657
fn create_webdriver(&self) -> Result<WebDriver> {
652658
let port = self.webdriver_port;
653659
let in_async = tokio::runtime::Handle::try_current().is_ok();
@@ -688,16 +694,22 @@ impl StaticExporterBuilder {
688694
/// .expect("Failed to build AsyncStaticExporter");
689695
/// ```
690696
pub fn build_async(&self) -> Result<AsyncStaticExporter> {
691-
let wd = self.create_webdriver()?;
692-
Ok(AsyncStaticExporter {
693-
webdriver_port: self.webdriver_port,
694-
webdriver_url: self.webdriver_url.clone(),
695-
webdriver: wd,
696-
offline_mode: self.offline_mode,
697-
pdf_export_timeout: self.pdf_export_timeout,
698-
webdriver_browser_caps: self.webdriver_browser_caps.clone(),
699-
webdriver_client: None,
700-
})
697+
#[cfg(not(any(feature = "chromedriver", feature = "geckodriver")))]
698+
return Err(anyhow!(DRIVER_FEATURE_REQUIRED));
699+
700+
#[cfg(any(feature = "chromedriver", feature = "geckodriver"))]
701+
{
702+
let wd = self.create_webdriver()?;
703+
Ok(AsyncStaticExporter {
704+
webdriver_port: self.webdriver_port,
705+
webdriver_url: self.webdriver_url.clone(),
706+
webdriver: wd,
707+
offline_mode: self.offline_mode,
708+
pdf_export_timeout: self.pdf_export_timeout,
709+
webdriver_browser_caps: self.webdriver_browser_caps.clone(),
710+
webdriver_client: None,
711+
})
712+
}
701713
}
702714
}
703715

@@ -927,6 +939,10 @@ pub struct AsyncStaticExporter {
927939
pdf_export_timeout: u32,
928940

929941
/// Browser command-line flags (e.g., "--headless", "--no-sandbox")
942+
#[cfg_attr(
943+
not(any(feature = "chromedriver", feature = "geckodriver")),
944+
allow(dead_code)
945+
)]
930946
webdriver_browser_caps: Vec<String>,
931947

932948
/// Cached WebDriver client for session reuse
@@ -1121,46 +1137,53 @@ impl AsyncStaticExporter {
11211137
}
11221138

11231139
fn build_webdriver_caps(&self) -> Result<Capabilities> {
1124-
// Define browser capabilities (copied to avoid reordering existing code)
1125-
let mut caps = JsonMap::new();
1126-
let mut browser_opts = JsonMap::new();
1127-
let browser_args = self.webdriver_browser_caps.clone();
1128-
1129-
browser_opts.insert("args".to_string(), serde_json::json!(browser_args));
1130-
1131-
// Add Chrome binary capability if BROWSER_PATH is set
1132-
#[cfg(feature = "chromedriver")]
1133-
if let Ok(chrome_path) = std::env::var("BROWSER_PATH") {
1134-
browser_opts.insert("binary".to_string(), serde_json::json!(chrome_path));
1135-
debug!("Added Chrome binary capability: {chrome_path}");
1136-
}
1137-
// Add Firefox binary capability if BROWSER_PATH is set
1138-
#[cfg(feature = "geckodriver")]
1139-
if let Ok(firefox_path) = std::env::var("BROWSER_PATH") {
1140-
browser_opts.insert("binary".to_string(), serde_json::json!(firefox_path));
1141-
debug!("Added Firefox binary capability: {firefox_path}");
1142-
}
1143-
1144-
// Add Firefox-specific preferences for CI environments
1145-
#[cfg(feature = "geckodriver")]
1140+
#[cfg(not(any(feature = "chromedriver", feature = "geckodriver")))]
11461141
{
1147-
let prefs = common::get_firefox_ci_preferences();
1148-
browser_opts.insert("prefs".to_string(), serde_json::json!(prefs));
1149-
debug!("Added Firefox preferences for CI compatibility");
1142+
Err(anyhow!(DRIVER_FEATURE_REQUIRED))
11501143
}
1144+
#[cfg(any(feature = "chromedriver", feature = "geckodriver"))]
1145+
{
1146+
// Define browser capabilities (copied to avoid reordering existing code)
1147+
let mut caps = JsonMap::new();
1148+
let mut browser_opts = JsonMap::new();
1149+
let browser_args = self.webdriver_browser_caps.clone();
1150+
1151+
browser_opts.insert("args".to_string(), serde_json::json!(browser_args));
1152+
1153+
// Add Chrome binary capability if BROWSER_PATH is set
1154+
#[cfg(feature = "chromedriver")]
1155+
if let Ok(chrome_path) = std::env::var("BROWSER_PATH") {
1156+
browser_opts.insert("binary".to_string(), serde_json::json!(chrome_path));
1157+
debug!("Added Chrome binary capability: {chrome_path}");
1158+
}
1159+
// Add Firefox binary capability if BROWSER_PATH is set
1160+
#[cfg(feature = "geckodriver")]
1161+
if let Ok(firefox_path) = std::env::var("BROWSER_PATH") {
1162+
browser_opts.insert("binary".to_string(), serde_json::json!(firefox_path));
1163+
debug!("Added Firefox binary capability: {firefox_path}");
1164+
}
11511165

1152-
caps.insert(
1153-
"browserName".to_string(),
1154-
serde_json::json!(get_browser_name()),
1155-
);
1156-
caps.insert(
1157-
get_options_key().to_string(),
1158-
serde_json::json!(browser_opts),
1159-
);
1166+
// Add Firefox-specific preferences for CI environments
1167+
#[cfg(feature = "geckodriver")]
1168+
{
1169+
let prefs = common::get_firefox_ci_preferences();
1170+
browser_opts.insert("prefs".to_string(), serde_json::json!(prefs));
1171+
debug!("Added Firefox preferences for CI compatibility");
1172+
}
1173+
1174+
caps.insert(
1175+
"browserName".to_string(),
1176+
serde_json::json!(get_browser_name()),
1177+
);
1178+
caps.insert(
1179+
get_options_key().to_string(),
1180+
serde_json::json!(browser_opts),
1181+
);
11601182

1161-
debug!("WebDriver capabilities: {caps:?}");
1183+
debug!("WebDriver capabilities: {caps:?}");
11621184

1163-
Ok(caps)
1185+
Ok(caps)
1186+
}
11641187
}
11651188

11661189
#[cfg(target_os = "windows")]
@@ -1334,6 +1357,15 @@ mod tests {
13341357
let _ = env_logger::try_init();
13351358
}
13361359

1360+
#[test]
1361+
#[cfg(not(any(feature = "chromedriver", feature = "geckodriver")))]
1362+
fn build_without_driver_feature_returns_error() {
1363+
match StaticExporterBuilder::default().build_async() {
1364+
Err(e) => assert_eq!(e.to_string(), DRIVER_FEATURE_REQUIRED),
1365+
Ok(_) => panic!("expected build to fail without a driver feature"),
1366+
}
1367+
}
1368+
13371369
// Helper to generate unique ports for parallel tests
13381370
#[cfg(not(feature = "debug"))]
13391371
fn get_unique_port() -> u32 {

0 commit comments

Comments
 (0)