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