Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-cli / src / lib.rs
21 kB 788 lines
1#![warn( 2 clippy::all, 3 clippy::dbg_macro, 4 clippy::todo, 5 clippy::mem_forget, 6 clippy::use_self, 7 clippy::filter_map_next, 8 clippy::needless_continue, 9 clippy::needless_borrow, 10 clippy::match_wildcard_for_single_variants, 11 clippy::imprecise_flops, 12 clippy::suboptimal_flops, 13 clippy::lossy_float_literal, 14 clippy::rest_pat_in_fully_bound_structs, 15 clippy::fn_params_excessive_bools, 16 clippy::inefficient_to_string, 17 clippy::linkedlist, 18 clippy::macro_use_imports, 19 clippy::option_option, 20 clippy::verbose_file_reads, 21 clippy::unnested_or_patterns, 22 rust_2018_idioms, 23 missing_debug_implementations, 24 missing_copy_implementations, 25 trivial_casts, 26 trivial_numeric_casts, 27 nonstandard_style, 28 unexpected_cfgs, 29 unused_import_braces, 30 unused_qualifications 31)] 32#![deny( 33 clippy::await_holding_lock, 34 clippy::if_let_mutex, 35 clippy::indexing_slicing, 36 clippy::mem_forget, 37 clippy::ok_expect, 38 clippy::unimplemented, 39 clippy::unwrap_used, 40 unsafe_code, 41 unstable_features, 42 unused_results 43)] 44#![allow( 45 clippy::match_single_binding, 46 clippy::inconsistent_struct_constructor, 47 clippy::assign_op_pattern 48)] 49 50#[cfg(test)] 51#[macro_use] 52extern crate pretty_assertions; 53 54mod add; 55mod beam_compiler; 56mod build; 57mod build_lock; 58mod cli; 59mod compile_package; 60mod config; 61mod dependencies; 62mod docs; 63mod export; 64mod fix; 65mod format; 66pub mod fs; 67mod hex; 68mod http; 69mod lsp; 70mod new; 71mod panic; 72mod publish; 73mod remove; 74pub mod run; 75mod shell; 76 77use config::root_config; 78use fs::{get_current_directory, get_project_root}; 79pub use gleam_core::error::{Error, Result}; 80 81use gleam_core::{ 82 analyse::TargetSupport, 83 build::{Codegen, Compile, Mode, NullTelemetry, Options, Runtime, Target}, 84 hex::RetirementReason, 85 paths::ProjectPaths, 86 version::COMPILER_VERSION, 87}; 88use std::str::FromStr; 89 90use camino::Utf8PathBuf; 91 92use clap::{ 93 Args, Parser, Subcommand, 94 builder::{PossibleValuesParser, Styles, TypedValueParser, styling}, 95}; 96use strum::VariantNames; 97 98#[derive(Args, Debug, Clone)] 99struct UpdateOptions { 100 /// (optional) Names of the packages to update 101 /// If omitted, all dependencies will be updated 102 #[arg(verbatim_doc_comment)] 103 packages: Vec<String>, 104} 105 106#[derive(Args, Debug, Clone)] 107struct TreeOptions { 108 /// Name of the package to get the dependency tree for 109 #[arg( 110 short, 111 long, 112 ignore_case = true, 113 help = "Package to be used as the root of the tree" 114 )] 115 package: Option<String>, 116 /// Name of the package to get the inverted dependency tree for 117 #[arg( 118 short, 119 long, 120 ignore_case = true, 121 help = "Invert the tree direction and focus on the given package", 122 value_name = "PACKAGE" 123 )] 124 invert: Option<String>, 125} 126 127#[derive(Parser, Debug)] 128#[command( 129 version, 130 name = "gleam", 131 next_display_order = None, 132 help_template = "\ 133{before-help}{name} {version} 134 135{usage-heading} {usage} 136 137{all-args}{after-help}", 138 styles = Styles::styled() 139 .header(styling::AnsiColor::Yellow.on_default()) 140 .usage(styling::AnsiColor::Yellow.on_default()) 141 .literal(styling::AnsiColor::Green.on_default()) 142)] 143enum Command { 144 /// Build the project 145 Build { 146 /// Emit compile time warnings as errors 147 #[arg(long)] 148 warnings_as_errors: bool, 149 150 #[arg(short, long, ignore_case = true, help = target_doc())] 151 target: Option<Target>, 152 153 /// Don't print progress information 154 #[clap(long)] 155 no_print_progress: bool, 156 }, 157 158 /// Type check the project 159 Check { 160 #[arg(short, long, ignore_case = true, help = target_doc())] 161 target: Option<Target>, 162 }, 163 164 /// Publish the project to the Hex package manager 165 /// 166 /// This command uses the environment variable: 167 /// 168 /// - HEXPM_API_KEY: (optional) A Hex API key to use instead of authenticating. 169 /// 170 #[command(verbatim_doc_comment)] 171 Publish { 172 #[arg(long)] 173 replace: bool, 174 #[arg(short, long)] 175 yes: bool, 176 }, 177 178 /// Render HTML documentation 179 #[command(subcommand)] 180 Docs(Docs), 181 182 /// Work with dependency packages 183 #[command(subcommand)] 184 Deps(Dependencies), 185 186 /// Update dependency packages to their latest versions 187 Update(UpdateOptions), 188 189 /// Work with the Hex package manager 190 #[command(subcommand)] 191 Hex(Hex), 192 193 /// Create a new project 194 New(NewOptions), 195 196 /// Format source code 197 Format { 198 /// Files to format 199 #[arg(default_value = ".")] 200 files: Vec<String>, 201 202 /// Read source from STDIN 203 #[arg(long)] 204 stdin: bool, 205 206 /// Check if inputs are formatted without changing them 207 #[arg(long)] 208 check: bool, 209 }, 210 /// Rewrite deprecated Gleam code 211 Fix, 212 213 /// Start an Erlang shell 214 Shell, 215 216 /// Run the project 217 #[command(trailing_var_arg = true)] 218 Run { 219 #[arg(short, long, ignore_case = true, help = target_doc())] 220 target: Option<Target>, 221 222 #[arg(long, ignore_case = true, help = runtime_doc())] 223 runtime: Option<Runtime>, 224 225 /// The module to run 226 #[arg(short, long)] 227 module: Option<String>, 228 229 /// Don't print progress information 230 #[clap(long)] 231 no_print_progress: bool, 232 233 arguments: Vec<String>, 234 }, 235 236 /// Run the project tests 237 #[command(trailing_var_arg = true)] 238 Test { 239 #[arg(short, long, ignore_case = true, help = target_doc())] 240 target: Option<Target>, 241 242 #[arg(long, ignore_case = true, help = runtime_doc())] 243 runtime: Option<Runtime>, 244 245 arguments: Vec<String>, 246 }, 247 248 /// Run the project development entrypoint 249 #[command(trailing_var_arg = true)] 250 Dev { 251 #[arg(short, long, ignore_case = true, help = target_doc())] 252 target: Option<Target>, 253 254 #[arg(long, ignore_case = true, help = runtime_doc())] 255 runtime: Option<Runtime>, 256 257 arguments: Vec<String>, 258 }, 259 260 /// Compile a single Gleam package 261 #[command(hide = true)] 262 CompilePackage(CompilePackage), 263 264 /// Read and print gleam.toml for debugging 265 #[command(hide = true)] 266 PrintConfig, 267 268 /// Add new project dependencies 269 Add { 270 /// The names of Hex packages to add 271 #[arg(required = true)] 272 packages: Vec<String>, 273 274 /// Add the packages as dev-only dependencies 275 #[arg(long)] 276 dev: bool, 277 }, 278 279 /// Remove project dependencies 280 Remove { 281 /// The names of packages to remove 282 #[arg(required = true)] 283 packages: Vec<String>, 284 }, 285 286 /// Clean build artifacts 287 Clean, 288 289 /// Run the language server, to be used by editors 290 #[command(name = "lsp")] 291 LanguageServer, 292 293 /// Export something useful from the Gleam project 294 #[command(subcommand)] 295 Export(ExportTarget), 296} 297 298fn template_doc() -> &'static str { 299 "The template to use" 300} 301 302fn target_doc() -> String { 303 format!("The platform to target ({})", Target::VARIANTS.join("|")) 304} 305 306fn runtime_doc() -> String { 307 format!("The runtime to target ({})", Runtime::VARIANTS.join("|")) 308} 309 310#[derive(Subcommand, Debug, Clone)] 311pub enum ExportTarget { 312 /// Precompiled Erlang, suitable for deployment 313 ErlangShipment, 314 /// The package bundled into a tarball, suitable for publishing to Hex 315 HexTarball, 316 /// The JavaScript prelude module 317 JavascriptPrelude, 318 /// The TypeScript prelude module 319 TypescriptPrelude, 320 /// Information on the modules, functions, and types in the project in JSON format 321 PackageInterface { 322 #[arg(long = "out", required = true)] 323 /// The path to write the JSON file to 324 output: Utf8PathBuf, 325 }, 326 /// Package information (gleam.toml) in JSON format 327 PackageInformation { 328 #[arg(long = "out", required = true)] 329 /// The path to write the JSON file to 330 output: Utf8PathBuf, 331 }, 332} 333 334#[derive(Args, Debug, Clone)] 335pub struct NewOptions { 336 /// Location of the project root 337 pub project_root: String, 338 339 /// Name of the project 340 #[arg(long)] 341 pub name: Option<String>, 342 343 #[arg(long, ignore_case = true, default_value = "erlang", help = template_doc())] 344 pub template: new::Template, 345 346 /// Skip git initialization and creation of .gitignore, .git/* and .github/* files 347 #[arg(long)] 348 pub skip_git: bool, 349 350 /// Skip creation of .github/* files 351 #[arg(long)] 352 pub skip_github: bool, 353} 354 355#[derive(Args, Debug)] 356pub struct CompilePackage { 357 /// The compilation target for the generated project 358 #[arg(long, ignore_case = true)] 359 target: Target, 360 361 /// The directory of the Gleam package 362 #[arg(long = "package")] 363 package_directory: Utf8PathBuf, 364 365 /// A directory to write compiled package to 366 #[arg(long = "out")] 367 output_directory: Utf8PathBuf, 368 369 /// A directories of precompiled Gleam projects 370 #[arg(long = "lib")] 371 libraries_directory: Utf8PathBuf, 372 373 /// The location of the JavaScript prelude module, relative to the `out` 374 /// directory. 375 /// 376 /// Required when compiling to JavaScript. 377 /// 378 /// This likely wants to be a `.mjs` file as NodeJS does not permit 379 /// importing of other JavaScript file extensions. 380 /// 381 #[arg(verbatim_doc_comment, long = "javascript-prelude")] 382 javascript_prelude: Option<Utf8PathBuf>, 383 384 /// Skip Erlang to BEAM bytecode compilation if given 385 #[arg(long = "no-beam")] 386 skip_beam_compilation: bool, 387} 388 389#[derive(Subcommand, Debug)] 390enum Dependencies { 391 /// List all dependency packages 392 List, 393 394 /// Download all dependency packages 395 Download, 396 397 /// Update dependency packages to their latest versions 398 Update(UpdateOptions), 399 400 /// Tree of all the dependency packages 401 Tree(TreeOptions), 402} 403 404#[derive(Subcommand, Debug)] 405enum Hex { 406 /// Retire a release from Hex 407 /// 408 /// This command uses the environment variable: 409 /// 410 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate against the Hex package manager. 411 /// 412 #[command(verbatim_doc_comment)] 413 Retire { 414 package: String, 415 416 version: String, 417 418 #[arg(value_parser = PossibleValuesParser::new(RetirementReason::VARIANTS).map(|s| RetirementReason::from_str(&s).unwrap()))] 419 reason: RetirementReason, 420 421 message: Option<String>, 422 }, 423 424 /// Un-retire a release from Hex 425 /// 426 /// This command uses this environment variable: 427 /// 428 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate against the Hex package manager. 429 /// 430 #[command(verbatim_doc_comment)] 431 Unretire { package: String, version: String }, 432 433 /// Revert a release from Hex 434 /// 435 /// This command uses this environment variable: 436 /// 437 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate against the Hex package manager. 438 /// 439 #[command(verbatim_doc_comment)] 440 Revert { 441 #[arg(long)] 442 package: Option<String>, 443 444 #[arg(long)] 445 version: Option<String>, 446 }, 447 448 /// Authenticate with Hex 449 Authenticate, 450} 451 452#[derive(Subcommand, Debug)] 453enum Docs { 454 /// Render HTML docs locally 455 Build { 456 /// Opens the docs in a browser after rendering 457 #[arg(long)] 458 open: bool, 459 460 #[arg(short, long, ignore_case = true, help = target_doc())] 461 target: Option<Target>, 462 }, 463 464 /// Publish HTML docs to HexDocs 465 /// 466 /// This command uses this environment variable: 467 /// 468 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate against the Hex package manager. 469 /// 470 #[command(verbatim_doc_comment)] 471 Publish, 472 473 /// Remove HTML docs from HexDocs 474 /// 475 /// This command uses this environment variable: 476 /// 477 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate against the Hex package manager. 478 /// 479 #[command(verbatim_doc_comment)] 480 Remove { 481 /// The name of the package 482 #[arg(long)] 483 package: String, 484 485 /// The version of the docs to remove 486 #[arg(long)] 487 version: String, 488 }, 489} 490 491pub fn main() { 492 initialise_logger(); 493 panic::add_handler(); 494 let stderr = cli::stderr_buffer_writer(); 495 let result = parse_and_run_command(); 496 match result { 497 Ok(_) => { 498 tracing::info!("Successfully completed"); 499 } 500 Err(error) => { 501 tracing::error!(error = ?error, "Failed"); 502 let mut buffer = stderr.buffer(); 503 error.pretty(&mut buffer); 504 stderr.print(&buffer).expect("Final result error writing"); 505 std::process::exit(1); 506 } 507 } 508} 509 510fn parse_and_run_command() -> Result<(), Error> { 511 match Command::parse() { 512 Command::Build { 513 target, 514 warnings_as_errors, 515 no_print_progress, 516 } => { 517 let paths = find_project_paths()?; 518 command_build(&paths, target, warnings_as_errors, no_print_progress) 519 } 520 521 Command::Check { target } => { 522 let paths = find_project_paths()?; 523 command_check(&paths, target) 524 } 525 526 Command::Docs(Docs::Build { open, target }) => { 527 let paths = find_project_paths()?; 528 docs::build(&paths, docs::BuildOptions { open, target }) 529 } 530 531 Command::Docs(Docs::Publish) => { 532 let paths = find_project_paths()?; 533 docs::publish(&paths) 534 } 535 536 Command::Docs(Docs::Remove { package, version }) => docs::remove(package, version), 537 538 Command::Format { 539 stdin, 540 files, 541 check, 542 } => format::run(stdin, check, files), 543 544 Command::Fix => { 545 let paths = find_project_paths()?; 546 fix::run(&paths) 547 } 548 549 Command::Deps(Dependencies::List) => { 550 let paths = find_project_paths()?; 551 dependencies::list(&paths) 552 } 553 554 Command::Deps(Dependencies::Download) => { 555 let paths = find_project_paths()?; 556 download_dependencies(&paths) 557 } 558 559 Command::Deps(Dependencies::Update(options)) => { 560 let paths = find_project_paths()?; 561 dependencies::update(&paths, options.packages) 562 } 563 564 Command::Deps(Dependencies::Tree(options)) => { 565 let paths = find_project_paths()?; 566 dependencies::tree(&paths, options) 567 } 568 569 Command::Hex(Hex::Authenticate) => hex::authenticate(), 570 571 Command::New(options) => new::create(options, COMPILER_VERSION), 572 573 Command::Shell => { 574 let paths = find_project_paths()?; 575 shell::command(&paths) 576 } 577 578 Command::Run { 579 target, 580 arguments, 581 runtime, 582 module, 583 no_print_progress, 584 } => { 585 let paths = find_project_paths()?; 586 run::command( 587 &paths, 588 arguments, 589 target, 590 runtime, 591 module, 592 run::Which::Src, 593 no_print_progress, 594 ) 595 } 596 597 Command::Test { 598 target, 599 arguments, 600 runtime, 601 } => { 602 let paths = find_project_paths()?; 603 run::command( 604 &paths, 605 arguments, 606 target, 607 runtime, 608 None, 609 run::Which::Test, 610 false, 611 ) 612 } 613 614 Command::Dev { 615 target, 616 arguments, 617 runtime, 618 } => { 619 let paths = find_project_paths()?; 620 run::command( 621 &paths, 622 arguments, 623 target, 624 runtime, 625 None, 626 run::Which::Dev, 627 false, 628 ) 629 } 630 631 Command::CompilePackage(opts) => compile_package::command(opts), 632 633 Command::Publish { replace, yes } => { 634 let paths = find_project_paths()?; 635 publish::command(&paths, replace, yes) 636 } 637 638 Command::PrintConfig => { 639 let paths = find_project_paths()?; 640 print_config(&paths) 641 } 642 643 Command::Hex(Hex::Retire { 644 package, 645 version, 646 reason, 647 message, 648 }) => hex::retire(package, version, reason, message), 649 650 Command::Hex(Hex::Unretire { package, version }) => hex::unretire(package, version), 651 652 Command::Hex(Hex::Revert { package, version }) => { 653 let paths = find_project_paths()?; 654 hex::revert(&paths, package, version) 655 } 656 657 Command::Add { packages, dev } => { 658 let paths = find_project_paths()?; 659 add::command(&paths, packages, dev) 660 } 661 662 Command::Remove { packages } => { 663 let paths = find_project_paths()?; 664 remove::command(&paths, packages) 665 } 666 667 Command::Update(options) => { 668 let paths = find_project_paths()?; 669 dependencies::update(&paths, options.packages) 670 } 671 672 Command::Clean => { 673 let paths = find_project_paths()?; 674 clean(&paths) 675 } 676 677 Command::LanguageServer => lsp::main(), 678 679 Command::Export(ExportTarget::ErlangShipment) => { 680 let paths = find_project_paths()?; 681 export::erlang_shipment(&paths) 682 } 683 Command::Export(ExportTarget::HexTarball) => { 684 let paths = find_project_paths()?; 685 export::hex_tarball(&paths) 686 } 687 Command::Export(ExportTarget::JavascriptPrelude) => export::javascript_prelude(), 688 Command::Export(ExportTarget::TypescriptPrelude) => export::typescript_prelude(), 689 Command::Export(ExportTarget::PackageInterface { output }) => { 690 let paths = find_project_paths()?; 691 export::package_interface(&paths, output) 692 } 693 Command::Export(ExportTarget::PackageInformation { output }) => { 694 let paths = find_project_paths()?; 695 export::package_information(&paths, output) 696 } 697 } 698} 699 700fn command_check(paths: &ProjectPaths, target: Option<Target>) -> Result<()> { 701 let _ = build::main( 702 paths, 703 Options { 704 root_target_support: TargetSupport::Enforced, 705 warnings_as_errors: false, 706 codegen: Codegen::DepsOnly, 707 compile: Compile::All, 708 mode: Mode::Dev, 709 target, 710 no_print_progress: false, 711 }, 712 build::download_dependencies(paths, cli::Reporter::new())?, 713 )?; 714 Ok(()) 715} 716 717fn command_build( 718 paths: &ProjectPaths, 719 target: Option<Target>, 720 warnings_as_errors: bool, 721 no_print_progress: bool, 722) -> Result<()> { 723 let manifest = if no_print_progress { 724 build::download_dependencies(paths, NullTelemetry)? 725 } else { 726 build::download_dependencies(paths, cli::Reporter::new())? 727 }; 728 let _ = build::main( 729 paths, 730 Options { 731 root_target_support: TargetSupport::Enforced, 732 warnings_as_errors, 733 codegen: Codegen::All, 734 compile: Compile::All, 735 mode: Mode::Dev, 736 target, 737 no_print_progress, 738 }, 739 manifest, 740 )?; 741 Ok(()) 742} 743 744fn print_config(paths: &ProjectPaths) -> Result<()> { 745 let config = root_config(paths)?; 746 println!("{config:#?}"); 747 Ok(()) 748} 749 750fn clean(paths: &ProjectPaths) -> Result<()> { 751 fs::delete_directory(&paths.build_directory()) 752} 753 754fn initialise_logger() { 755 let enable_colours = std::env::var("GLEAM_LOG_NOCOLOUR").is_err(); 756 tracing_subscriber::fmt() 757 .with_writer(std::io::stderr) 758 .with_env_filter(std::env::var("GLEAM_LOG").unwrap_or_else(|_| "off".into())) 759 .with_target(false) 760 .with_ansi(enable_colours) 761 .without_time() 762 .init(); 763} 764 765fn find_project_paths() -> Result<ProjectPaths> { 766 let current_dir = get_current_directory()?; 767 get_project_root(current_dir).map(ProjectPaths::new) 768} 769 770#[cfg(test)] 771fn project_paths_at_current_directory_without_toml() -> ProjectPaths { 772 let current_dir = get_current_directory().expect("Failed to get current directory"); 773 ProjectPaths::new(current_dir) 774} 775 776fn download_dependencies(paths: &ProjectPaths) -> Result<()> { 777 _ = dependencies::download( 778 paths, 779 cli::Reporter::new(), 780 None, 781 Vec::new(), 782 dependencies::DependencyManagerConfig { 783 use_manifest: dependencies::UseManifest::Yes, 784 check_major_versions: dependencies::CheckMajorVersions::No, 785 }, 786 )?; 787 Ok(()) 788}