Skip to main content

risedev_config/
main.rs

1// Copyright 2023 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::io::{BufRead, BufReader, BufWriter, Write};
16
17use anyhow::{Context, Result};
18use clap::{Parser, Subcommand, ValueEnum};
19use console::style;
20use dialoguer::MultiSelect;
21use enum_iterator::{Sequence, all};
22use fs_err::OpenOptions;
23use itertools::Itertools;
24
25#[derive(Parser)]
26#[clap(author, version, about, long_about = None)]
27#[clap(propagate_version = true)]
28#[clap(infer_subcommands = true)]
29pub struct RiseDevConfigOpts {
30    #[clap(subcommand)]
31    command: Option<Commands>,
32    #[clap(short, long)]
33    file: String,
34}
35
36#[derive(Subcommand)]
37#[clap(infer_subcommands = true)]
38enum Commands {
39    /// Enable one component
40    Enable {
41        /// Component to enable
42        #[clap(value_enum)]
43        component: Components,
44    },
45    /// Disable one component
46    Disable {
47        /// Component to disable
48        #[clap(value_enum)]
49        component: Components,
50    },
51    /// Use default configuration
52    Default,
53}
54
55#[expect(clippy::enum_variant_names)]
56#[derive(Clone, Copy, Debug, Sequence, PartialEq, Eq, ValueEnum)]
57pub enum Components {
58    #[clap(name = "minio")]
59    Minio,
60    Lakekeeper,
61    Hdfs,
62    PrometheusAndGrafana,
63    Pubsub,
64    Redis,
65    Tracing,
66    RustComponents,
67    UseSystem,
68    BuildConnectorNode,
69    Dashboard,
70    Release,
71    Sanitizer,
72    DynamicLinking,
73    HummockTrace,
74    Coredump,
75    NoBacktrace,
76    Udf,
77    NoDefaultFeatures,
78    Moat,
79    DataFusion,
80    Adbc,
81}
82
83impl Components {
84    pub fn title(&self) -> String {
85        match self {
86            Self::Minio => "[Component] Hummock: MinIO + MinIO-CLI",
87            Self::Lakekeeper => "[Component] Apache Iceberg: Lakekeeper REST Catalog",
88            Self::Hdfs => "[Component] Hummock: Hdfs Backend",
89            Self::PrometheusAndGrafana => "[Component] Metrics: Prometheus + Grafana",
90            Self::Pubsub => "[Component] Google Pubsub",
91            Self::Redis => "[Component] Redis",
92            Self::BuildConnectorNode => "[Build] Build RisingWave Connector (Java)",
93            Self::RustComponents => "[Build] Rust components",
94            Self::UseSystem => "[Build] Use system RisingWave",
95            Self::Dashboard => "[Build] Dashboard",
96            Self::Tracing => "[Component] Tracing: Grafana Tempo",
97            Self::Release => "[Build] Enable release mode",
98            Self::Sanitizer => "[Build] Enable sanitizer",
99            Self::DynamicLinking => "[Build] Enable dynamic linking",
100            Self::HummockTrace => "[Build] Hummock Trace",
101            Self::Coredump => "[Runtime] Enable coredump",
102            Self::NoBacktrace => "[Runtime] Disable backtrace",
103            Self::Udf => "[Build] Enable UDF",
104            Self::NoDefaultFeatures => "[Build] Disable default features",
105            Self::Moat => "[Component] Enable Moat",
106            Self::DataFusion => "[Build] Enable DataFusion",
107            Self::Adbc => "[Component] ADBC Snowflake Driver",
108        }
109        .into()
110    }
111
112    pub fn description(&self) -> String {
113        match self {
114            Self::Minio => {
115                "
116Required by Hummock state store."
117            }
118            Self::Lakekeeper => {
119                "
120Required if you want to use Apache Iceberg REST Catalog.
121Provides catalog and metadata management for Apache Iceberg tables."
122            }
123            Self::Hdfs => {
124                "
125Required by Hummock state store."
126            }
127            Self::PrometheusAndGrafana => {
128                "
129Required if you want to view metrics."
130            }
131            Self::Pubsub => {
132                "
133Required if you want to create source from Emulated Google Pub/sub.
134                "
135            }
136            Self::RustComponents => {
137                "
138Required if you want to build compute-node and meta-node.
139Otherwise you will need to enable `USE_SYSTEM_RISINGWAVE`, or
140manually download a binary and copy it to RiseDev directory."
141            }
142            Self::UseSystem => {
143                "
144Use the RisingWave installed in the PATH, instead of building it
145from source. This implies `ENABLE_BUILD_RUST` to be false.
146                "
147            }
148            Self::Dashboard => {
149                "
150Required if you want to build dashboard from source.
151This is generally not the option you want to use to develop the
152dashboard. Instead, directly run `npm run dev` in the dashboard
153directory to start the development server, set the API endpoint
154to a running RisingWave cluster in the settings page.
155"
156            }
157            Self::Tracing => {
158                "
159Required if you want to use tracing. This option will help
160you download Grafana Tempo."
161            }
162            Self::Release => {
163                "
164Build RisingWave in release mode"
165            }
166            Self::Sanitizer => {
167                "
168With this option enabled, RiseDev will build Rust components
169with thread sanitizer. The built binaries will be at
170`target/<arch-triple>/(debug|release)` instead of simply at
171`target/debug`. RiseDev will help link binaries when starting
172a dev cluster.
173"
174            }
175            Self::Redis => {
176                "
177Required if you want to sink data to redis.
178                "
179            }
180            Self::BuildConnectorNode => {
181                "
182Required if you want to build Connector Node from source locally.
183                "
184            }
185            Self::DynamicLinking => {
186                "
187With this option enabled, RiseDev will use dynamic linking when
188building Rust components. This can speed up the build process,
189but you might need the expertise to install dependencies correctly.
190                "
191            }
192            Self::HummockTrace => {
193                "
194With this option enabled, RiseDev will enable tracing for Hummock.
195See storage/hummock_trace for details.
196                "
197            }
198            Self::Coredump => {
199                "
200With this option enabled, RiseDev will unlimit the size of core
201files before launching RisingWave. On Apple Silicon platforms,
202the binaries will also be codesigned with `get-task-allow` enabled.
203As a result, RisingWave will dump the core on panics.
204                "
205            }
206            Self::NoBacktrace => {
207                "
208With this option enabled, RiseDev will not set `RUST_BACKTRACE` when launching nodes.
209                "
210            }
211            Self::Udf => {
212                "
213Add --features udf to build command (by default disabled).
214Required if you want to support UDF."
215            }
216            Self::NoDefaultFeatures => {
217                "
218Add --no-default-features to build command.
219Currently, default features are: rw-static-link, all-connectors
220"
221            }
222            Self::Moat => {
223                "
224Enable Moat as distributed hybrid cache service."
225            }
226            Self::DataFusion => {
227                "
228Enable DataFusion as the optional query engine for Iceberg tables."
229            }
230            Self::Adbc => {
231                "
232Enable ADBC (Arrow Database Connectivity) Snowflake driver support.
233Required if you want to use ADBC Snowflake source.
234This will download the ADBC Snowflake driver shared library (.so/.dylib)."
235            }
236        }
237        .into()
238    }
239
240    pub fn from_env(env: impl AsRef<str>) -> Option<Self> {
241        match env.as_ref() {
242            "ENABLE_MINIO" => Some(Self::Minio),
243            "ENABLE_LAKEKEEPER" => Some(Self::Lakekeeper),
244            "ENABLE_HDFS" => Some(Self::Hdfs),
245            "ENABLE_PROMETHEUS_GRAFANA" => Some(Self::PrometheusAndGrafana),
246            "ENABLE_PUBSUB" => Some(Self::Pubsub),
247            "ENABLE_BUILD_RUST" => Some(Self::RustComponents),
248            "USE_SYSTEM_RISINGWAVE" => Some(Self::UseSystem),
249            "ENABLE_BUILD_DASHBOARD" => Some(Self::Dashboard),
250            "ENABLE_COMPUTE_TRACING" => Some(Self::Tracing),
251            "ENABLE_RELEASE_PROFILE" => Some(Self::Release),
252            "ENABLE_DYNAMIC_LINKING" => Some(Self::DynamicLinking),
253            "ENABLE_SANITIZER" => Some(Self::Sanitizer),
254            "ENABLE_REDIS" => Some(Self::Redis),
255            "ENABLE_BUILD_RW_CONNECTOR" => Some(Self::BuildConnectorNode),
256            "ENABLE_HUMMOCK_TRACE" => Some(Self::HummockTrace),
257            "ENABLE_COREDUMP" => Some(Self::Coredump),
258            "DISABLE_BACKTRACE" => Some(Self::NoBacktrace),
259            "ENABLE_UDF" => Some(Self::Udf),
260            "DISABLE_DEFAULT_FEATURES" => Some(Self::NoDefaultFeatures),
261            "ENABLE_MOAT" => Some(Self::Moat),
262            "ENABLE_DATAFUSION" => Some(Self::DataFusion),
263            "ENABLE_ADBC" => Some(Self::Adbc),
264            _ => None,
265        }
266    }
267
268    pub fn env(&self) -> String {
269        match self {
270            Self::Minio => "ENABLE_MINIO",
271            Self::Lakekeeper => "ENABLE_LAKEKEEPER",
272            Self::Hdfs => "ENABLE_HDFS",
273            Self::PrometheusAndGrafana => "ENABLE_PROMETHEUS_GRAFANA",
274            Self::Pubsub => "ENABLE_PUBSUB",
275            Self::Redis => "ENABLE_REDIS",
276            Self::RustComponents => "ENABLE_BUILD_RUST",
277            Self::UseSystem => "USE_SYSTEM_RISINGWAVE",
278            Self::Dashboard => "ENABLE_BUILD_DASHBOARD",
279            Self::Tracing => "ENABLE_COMPUTE_TRACING",
280            Self::Release => "ENABLE_RELEASE_PROFILE",
281            Self::Sanitizer => "ENABLE_SANITIZER",
282            Self::BuildConnectorNode => "ENABLE_BUILD_RW_CONNECTOR",
283            Self::DynamicLinking => "ENABLE_DYNAMIC_LINKING",
284            Self::HummockTrace => "ENABLE_HUMMOCK_TRACE",
285            Self::Coredump => "ENABLE_COREDUMP",
286            Self::NoBacktrace => "DISABLE_BACKTRACE",
287            Self::Udf => "ENABLE_UDF",
288            Self::NoDefaultFeatures => "DISABLE_DEFAULT_FEATURES",
289            Self::Moat => "ENABLE_MOAT",
290            Self::DataFusion => "ENABLE_DATAFUSION",
291            Self::Adbc => "ENABLE_ADBC",
292        }
293        .into()
294    }
295
296    pub fn default_enabled() -> &'static [Self] {
297        &[Self::RustComponents]
298    }
299}
300
301fn configure(chosen: &[Components]) -> Result<Option<Vec<Components>>> {
302    println!("=== Configure RiseDev ===");
303
304    let all_components = all::<Components>().collect_vec();
305
306    const ITEMS_PER_PAGE: usize = 6;
307
308    let items = all_components
309        .iter()
310        .map(|c| {
311            let title = c.title();
312            let desc = style(
313                ("\n".to_owned() + c.description().trim())
314                    .split('\n')
315                    .join("\n      "),
316            )
317            .dim();
318
319            (format!("{title}{desc}",), chosen.contains(c))
320        })
321        .collect_vec();
322
323    let Some(chosen_indices) = MultiSelect::new()
324        .with_prompt(
325            format!(
326                "RiseDev includes several components. You can select the ones you need, so as to reduce build time\n\n{}: navigate\n{}: confirm and save   {}: quit without saving\n\nPick items with {}",
327                style("↑ / ↓ / ← / → ").reverse(),
328                style("Enter").reverse(),
329                style("Esc / q").reverse(),
330                style("Space").reverse(),
331            )
332        )
333        .items_checked(items)
334        .max_length(ITEMS_PER_PAGE)
335        .interact_opt()? else {
336        return Ok(None);
337    };
338
339    let chosen = chosen_indices
340        .into_iter()
341        .map(|i| all_components[i])
342        .collect_vec();
343
344    Ok(Some(chosen))
345}
346
347fn main() -> Result<()> {
348    let opts = RiseDevConfigOpts::parse();
349    let file_path = opts.file;
350
351    let chosen = {
352        match OpenOptions::new().read(true).open(&file_path) {
353            Ok(file) => {
354                let reader = BufReader::new(file);
355                let mut enabled = vec![];
356                for line in reader.lines() {
357                    let line = line?;
358                    if line.trim().is_empty() || line.trim().starts_with('#') {
359                        continue;
360                    }
361                    let Some((component, val)) = line.split_once('=') else {
362                        println!("invalid config line {}, discarded", line);
363                        continue;
364                    };
365                    if component == "RISEDEV_CONFIGURED" {
366                        continue;
367                    }
368                    match Components::from_env(component) {
369                        Some(component) => {
370                            if val == "true" {
371                                enabled.push(component);
372                            }
373                        }
374                        None => {
375                            println!("unknown configure {}, discarded", component);
376                            continue;
377                        }
378                    }
379                }
380                enabled
381            }
382            _ => {
383                println!(
384                    "RiseDev component config not found, generating {}",
385                    file_path
386                );
387                Components::default_enabled().to_vec()
388            }
389        }
390    };
391
392    let chosen = match &opts.command {
393        Some(Commands::Default) => {
394            println!("Using default config");
395            Components::default_enabled().to_vec()
396        }
397        Some(Commands::Enable { component }) => {
398            let mut chosen = chosen;
399            chosen.push(*component);
400            chosen
401        }
402        Some(Commands::Disable { component }) => {
403            chosen.into_iter().filter(|x| x != component).collect()
404        }
405        None => match configure(&chosen)? {
406            Some(chosen) => chosen,
407            None => {
408                println!("Quit without saving");
409                println!("=========================");
410                return Ok(());
411            }
412        },
413    };
414
415    println!("=== Enabled Components ===");
416    for component in all::<Components>() {
417        println!(
418            "{}: {}",
419            component.title(),
420            if chosen.contains(&component) {
421                style("enabled").green()
422            } else {
423                style("disabled").dim()
424            }
425        );
426    }
427
428    println!("Configuration saved at {}", file_path);
429    println!("=========================");
430
431    let mut file = BufWriter::new(
432        OpenOptions::new()
433            .write(true)
434            .truncate(true)
435            .create(true)
436            .open(&file_path)
437            .context(format!("failed to open component config at {}", file_path))?,
438    );
439
440    writeln!(file, "RISEDEV_CONFIGURED=true")?;
441    writeln!(file)?;
442
443    for component in all::<Components>() {
444        writeln!(file, "# {}", component.title())?;
445        writeln!(
446            file,
447            "# {}",
448            component.description().trim().split('\n').join("\n# ")
449        )?;
450        if chosen.contains(&component) {
451            writeln!(file, "{}=true", component.env())?;
452        } else {
453            writeln!(file, "# {}=true", component.env())?;
454        }
455        writeln!(file)?;
456    }
457
458    file.flush()?;
459
460    println!(
461        "RiseDev will {} the components you've enabled.",
462        style("only download").bold()
463    );
464    println!(
465        "If you want to use these components, please {} in {} to start that component.",
466        style("modify the cluster config").yellow().bold(),
467        style("risedev.yml").bold(),
468    );
469    println!("See CONTRIBUTING.md or RiseDev's readme for more information.");
470
471    Ok(())
472}