Fork of daniellemaywood.uk/gleam — Wasm codegen work
32 kB
1045 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2018 The Gleam contributors
3
4#![warn(
5 clippy::all,
6 clippy::redundant_clone,
7 clippy::dbg_macro,
8 clippy::todo,
9 clippy::mem_forget,
10 clippy::use_self,
11 clippy::filter_map_next,
12 clippy::needless_continue,
13 clippy::needless_borrow,
14 clippy::match_wildcard_for_single_variants,
15 clippy::imprecise_flops,
16 clippy::suboptimal_flops,
17 clippy::lossy_float_literal,
18 clippy::rest_pat_in_fully_bound_structs,
19 clippy::fn_params_excessive_bools,
20 clippy::inefficient_to_string,
21 clippy::linkedlist,
22 clippy::macro_use_imports,
23 clippy::option_option,
24 clippy::verbose_file_reads,
25 clippy::unnested_or_patterns,
26 clippy::default_trait_access,
27 clippy::format_push_string,
28 rust_2018_idioms,
29 missing_debug_implementations,
30 missing_copy_implementations,
31 trivial_casts,
32 trivial_numeric_casts,
33 nonstandard_style,
34 unexpected_cfgs,
35 unused_import_braces,
36 unused_qualifications
37)]
38#![deny(
39 clippy::await_holding_lock,
40 clippy::if_let_mutex,
41 clippy::indexing_slicing,
42 clippy::mem_forget,
43 clippy::ok_expect,
44 clippy::unimplemented,
45 clippy::unwrap_used,
46 unsafe_code,
47 unstable_features,
48 unused_results
49)]
50#![allow(
51 clippy::match_single_binding,
52 clippy::inconsistent_struct_constructor,
53 clippy::assign_op_pattern,
54 clippy::len_without_is_empty,
55 clippy::let_unit_value
56)]
57
58#[cfg(test)]
59#[macro_use]
60extern crate pretty_assertions;
61
62mod add;
63mod beam_compiler;
64mod build;
65mod build_lock;
66mod cli;
67mod compile_package;
68mod config;
69mod dependencies;
70mod docs;
71mod export;
72mod fix;
73mod format;
74pub mod fs;
75mod hex;
76mod http;
77mod lsp;
78mod new;
79mod owner;
80mod panic;
81mod publish;
82mod remove;
83pub mod run;
84mod shell;
85mod text_layout;
86
87use config::root_config;
88use fs::{get_current_directory, get_project_root};
89pub use gleam_core::error::{Error, Result};
90
91use camino::Utf8PathBuf;
92use clap::{
93 Args, Parser, Subcommand,
94 builder::{Styles, styling},
95};
96use gleam_core::{
97 analyse::TargetSupport,
98 build::{Codegen, Compile, Mode, NullTelemetry, Options, Runtime, Target},
99 hex::RetirementReason,
100 paths::ProjectPaths,
101 version::COMPILER_VERSION,
102};
103
104#[derive(Args, Debug, Clone)]
105pub struct UpdateOptions {
106 /// (optional) Names of the packages to update
107 /// If omitted, all dependencies will be updated
108 #[arg(verbatim_doc_comment)]
109 packages: Vec<String>,
110}
111
112#[derive(Args, Debug, Clone)]
113pub struct TreeOptions {
114 /// Name of the package to get the dependency tree for
115 #[arg(
116 short,
117 long,
118 ignore_case = true,
119 conflicts_with = "invert",
120 help = "Package to be used as the root of the tree"
121 )]
122 package: Option<String>,
123 /// Name of the package to get the inverted dependency tree for
124 #[arg(
125 short,
126 long,
127 ignore_case = true,
128 conflicts_with = "package",
129 help = "Invert the tree direction and focus on the given package",
130 value_name = "PACKAGE"
131 )]
132 invert: Option<String>,
133}
134
135#[derive(Parser, Debug)]
136#[command(
137 version,
138 name = "gleam",
139 next_display_order = None,
140 help_template = "\
141{before-help}{name} {version}
142
143{usage-heading} {usage}
144
145{all-args}{after-help}",
146 styles = Styles::styled()
147 .header(styling::AnsiColor::Yellow.on_default())
148 .usage(styling::AnsiColor::Yellow.on_default())
149 .literal(styling::AnsiColor::Green.on_default())
150)]
151pub enum Command {
152 /// Build the project
153 ///
154 /// This command optionally accepts the environment variable
155 /// `HEXPM_READ_API_KEY`, which can hold a Hex API key to authenticate
156 /// with Hex with a higher rate limit.
157 ///
158 #[command(verbatim_doc_comment)]
159 Build {
160 /// Consider the build failed if the package contains any warnings
161 #[arg(long)]
162 warnings_as_errors: bool,
163
164 /// Which compilation target to use
165 #[arg(short, long, ignore_case = true, help = target_doc())]
166 target: Option<Target>,
167
168 #[arg(long, help = no_print_progress_doc())]
169 no_print_progress: bool,
170 },
171
172 /// Type check the project
173 Check {
174 /// Which compilation target to use
175 #[arg(short, long, ignore_case = true, help = target_doc())]
176 target: Option<Target>,
177 },
178
179 /// Publish the project to the Hex package repository
180 ///
181 /// Please ensure your package is suitable for production use before
182 /// publishing. If you have a prototype package that you wish to use
183 /// in another project then use git dependencies instead of publishing
184 /// to the package repository.
185 ///
186 /// This command optionally accepts the environment variable
187 /// `HEXPM_API_KEY`, which can hold a Hex API key to authenticate
188 /// with Hex.
189 ///
190 #[command(verbatim_doc_comment)]
191 Publish {
192 /// Replace the latest release with this one
193 #[arg(long)]
194 replace: bool,
195 /// Automatically accept confirmation prompts
196 #[arg(short, long)]
197 yes: bool,
198 },
199
200 /// Render HTML documentation for the package
201 ///
202 /// There are several options in `gleam.toml` that can be used to
203 /// configure the output.
204 ///
205 /// repository = { type = "github", user = "lpil", repo = "wibble" }
206 ///
207 /// Specify the location of the source code repository for the package.
208 /// This will add a link to the side bar, and add "view code" links for
209 /// the documented types and values.
210 ///
211 /// links = [
212 /// { title = "Home page", href = "https://example.com" },
213 /// { title = "Other site", href = "https://another.example.com" },
214 /// ]
215 ///
216 /// Specify some additional links to include in the sidebar.
217 ///
218 /// [documentation]
219 /// pages = [
220 /// { title = "My Page", path = "my-page.html", source = "./path/to/my-page.md" },
221 /// ]
222 ///
223 /// Specify additional markdown pages to include in the documentation,
224 /// with links for each added to the sidebar.
225 ///
226 #[command(subcommand, verbatim_doc_comment)]
227 Docs(Docs),
228
229 /// Work with dependency packages
230 ///
231 /// The packages and the acceptable version ranges are specified in
232 /// `gleam.toml`. You can edit this file manually, or use the `gleam add`
233 /// and `gleam remove` commands.
234 ///
235 /// Package versions follow semantic versioning: MAJOR.MINOR.PATCH.
236 /// - Major updates include breaking changes, either type changes or
237 /// semantic changes.
238 /// - Minor updates include new functionality, but no breaking changes.
239 /// - Patch updates include only bug fixes.
240 ///
241 /// Dependency resolution will be performed automatically by any command
242 /// that builds your project. Once versions have been selected they are
243 /// written to `manifest.toml`, which locks the package to those versions,
244 /// making your build deterministic. You should not edit this file manually.
245 ///
246 /// To upgrade the dependencies you can use the `gleam update` command,
247 /// which will select the newest versions compatible with the requirements
248 /// in `gleam.toml`.
249 ///
250 /// The `[dependencies]` section of `gleam.toml` holds the production
251 /// dependencies. These are included in your production application, or
252 /// are used as the dependencies when published as a library. The
253 /// `[dev_dependencies]` section holds dependencies that are only used
254 /// during development, e.g. code used for testing the package.
255 ///
256 /// ## Syntax examples:
257 ///
258 /// [dependencies]
259 /// wibble = ">= 1.2.0 and < 2.0.0"
260 ///
261 /// Require the package `wibble`, permitting versions greater than or
262 /// equal to 1.2.0, but lower than 2.0.0.
263 ///
264 /// [dependencies]
265 /// wibble = ">= 1.2.0 and < 2.0.0 and != 1.4.1"
266 ///
267 /// Permit a range, but deny a specific version within that range. This
268 /// could be useful if there is a version known to have a bug.
269 ///
270 /// [dependencies]
271 /// wibble = { git = "https://example.com/wibble.git", ref = "a8b3c5d82" }
272 ///
273 /// A dependency fetched from Git instead of from Hex. This is useful
274 /// for using packages of yours that are not-yet production-ready, or
275 /// for bug fixes that have not yet been published to Hex.
276 ///
277 /// [dependencies]
278 /// wibble = { path = "../wibble" }
279 ///
280 /// A local dependency, on your computer. This is useful for testing and
281 /// and for applications made of multiple packages in a single version
282 /// control repository.
283 ///
284 /// This command optionally accepts the environment variable
285 /// `HEXPM_READ_API_KEY`, which can hold a Hex API key to authenticate
286 /// with Hex with a higher rate limit.
287 ///
288 #[command(subcommand, verbatim_doc_comment)]
289 Deps(Dependencies),
290
291 /// Update dependency packages to their latest versions
292 ///
293 /// This command optionally accepts the environment variable
294 /// `HEXPM_READ_API_KEY`, which can hold a Hex API key to authenticate
295 /// with Hex with a higher rate limit.
296 ///
297 #[command(verbatim_doc_comment)]
298 Update(UpdateOptions),
299
300 /// Work with the Hex package manager
301 #[command(subcommand)]
302 Hex(Hex),
303
304 /// Create a new project
305 New(NewOptions),
306
307 /// Format source code
308 Format {
309 /// The files or directories to format
310 #[arg(default_value = ".")]
311 files: Vec<String>,
312
313 /// Read source from standard-input
314 #[arg(long)]
315 stdin: bool,
316
317 /// Only check if inputs are formatted correctly, erroring if they are not
318 #[arg(long)]
319 check: bool,
320 },
321
322 /// Rewrite deprecated Gleam code
323 Fix,
324
325 /// Start an Erlang REPL with the Gleam code loaded
326 Shell,
327
328 /// Run the project
329 ///
330 /// This command runs the `main` function from the `<PROJECT_NAME>` module.
331 #[command(trailing_var_arg = true)]
332 Run {
333 /// Which compilation target to use
334 #[arg(short, long, ignore_case = true, help = target_doc())]
335 target: Option<Target>,
336
337 /// Which runtime to use
338 #[arg(long, ignore_case = true, help = runtime_doc())]
339 runtime: Option<Runtime>,
340
341 /// The module to run
342 #[arg(short, long)]
343 module: Option<String>,
344
345 #[arg(long, help = no_print_progress_doc())]
346 no_print_progress: bool,
347
348 arguments: Vec<String>,
349 },
350
351 /// Run the project tests
352 ///
353 /// This command runs the `main` function from the `<PROJECT_NAME>_test` module.
354 #[command(trailing_var_arg = true)]
355 Test {
356 /// Which compilation target to use
357 #[arg(short, long, ignore_case = true, help = target_doc())]
358 target: Option<Target>,
359
360 /// Which runtime to use
361 #[arg(long, ignore_case = true, help = runtime_doc())]
362 runtime: Option<Runtime>,
363
364 arguments: Vec<String>,
365 },
366
367 /// Run the project development entrypoint
368 ///
369 /// This command runs the `main` function from the `<PROJECT_NAME>_dev` module.
370 #[command(trailing_var_arg = true)]
371 Dev {
372 /// Which compilation target to use
373 #[arg(short, long, ignore_case = true, help = target_doc())]
374 target: Option<Target>,
375
376 /// Which runtime to use
377 #[arg(long, ignore_case = true, help = runtime_doc())]
378 runtime: Option<Runtime>,
379
380 #[arg(long, help = no_print_progress_doc())]
381 no_print_progress: bool,
382
383 arguments: Vec<String>,
384 },
385
386 /// A low-level API for compiling a single Gleam package
387 ///
388 /// This is to be used by other build tools to implement support for Gleam
389 /// code. It is not used directly by humans.
390 ///
391 #[command(verbatim_doc_comment)]
392 CompilePackage(CompilePackage),
393
394 /// Read and print gleam.toml for debugging
395 #[command(hide = true)]
396 PrintConfig,
397
398 /// Add new dependencies
399 ///
400 /// The newest compatible version of the package is determined, and then
401 /// `gleam.toml` is updated to require at least that version, with a range
402 /// permitting future patch and minor updates.
403 ///
404 /// Add the package "wibble":
405 /// gleam add wibble
406 ///
407 /// Add the package "wibble", requiring version >= v2.0.0 and < v3.0.0:
408 /// gleam add wibble@2
409 ///
410 /// Add the package "wibble", requiring version >= v2.5.1 and < v3.0.0:
411 /// gleam add wibble@2.5.1
412 ///
413 /// Add multiple packages:
414 /// gleam add wibble@2 warble@1
415 ///
416 /// Add a package as a non-production dependency:
417 /// gleam add --dev wibble
418 ///
419 /// You can also edit `gleam.toml` directly, for further control over your
420 /// package dependencies. Run `gleam help deps` for documentation on the
421 /// format.
422 ///
423 #[command(verbatim_doc_comment)]
424 Add {
425 /// The names of Hex packages to add
426 #[arg(required = true)]
427 packages: Vec<String>,
428
429 /// Add the packages as dev-only dependencies
430 #[arg(long)]
431 dev: bool,
432 },
433
434 /// Remove project dependencies
435 Remove {
436 /// The names of packages to remove
437 #[arg(required = true)]
438 packages: Vec<String>,
439 },
440
441 /// Delete any build artefacts for this project
442 Clean,
443
444 /// Run the language server, to be used by editors
445 #[command(name = "lsp")]
446 LanguageServer,
447
448 /// Export something useful from the Gleam project
449 #[command(subcommand)]
450 Export(ExportTarget),
451}
452
453impl Command {
454 pub fn run(self, directory: Utf8PathBuf) -> Result<(), Error> {
455 match self {
456 Self::Build {
457 target,
458 warnings_as_errors,
459 no_print_progress,
460 } => {
461 let paths = find_project_paths(directory)?;
462 command_build(&paths, target, warnings_as_errors, no_print_progress)
463 }
464
465 Self::Check { target } => {
466 let paths = find_project_paths(directory)?;
467 command_check(&paths, target)
468 }
469
470 Self::Docs(Docs::Build { open, target }) => {
471 let paths = find_project_paths(directory)?;
472 docs::build(&paths, docs::BuildOptions { open, target })
473 }
474
475 Self::Docs(Docs::Publish) => {
476 let paths = find_project_paths(directory)?;
477 docs::publish(&paths)
478 }
479
480 Self::Docs(Docs::Remove { package, version }) => docs::remove(package, version),
481
482 Self::Format {
483 stdin,
484 files,
485 check,
486 } => format::run(stdin, check, files),
487
488 Self::Fix => {
489 let paths = find_project_paths(directory)?;
490 fix::run(&paths)
491 }
492
493 Self::Deps(Dependencies::List) => {
494 let paths = find_project_paths(directory)?;
495 dependencies::list(&paths)
496 }
497
498 Self::Deps(Dependencies::Download) => {
499 let paths = find_project_paths(directory)?;
500 download_dependencies(&paths)
501 }
502
503 Self::Deps(Dependencies::Outdated) => {
504 let paths = find_project_paths(directory)?;
505 dependencies::outdated(&paths)
506 }
507
508 Self::Deps(Dependencies::Update(options)) => {
509 let paths = find_project_paths(directory)?;
510 dependencies::update(&paths, options.packages)
511 }
512
513 Self::Deps(Dependencies::Tree(options)) => {
514 let paths = find_project_paths(directory)?;
515 dependencies::tree(&paths, options)
516 }
517
518 Self::Hex(Hex::Authenticate) => hex::authenticate(),
519
520 Self::New(options) => new::create(options, COMPILER_VERSION),
521
522 Self::Shell => {
523 let paths = find_project_paths(directory)?;
524 shell::command(&paths)
525 }
526
527 Self::Run {
528 target,
529 arguments,
530 runtime,
531 module,
532 no_print_progress,
533 } => {
534 let paths = find_project_paths(directory)?;
535 run::command(
536 &paths,
537 arguments,
538 target,
539 runtime,
540 module,
541 run::Which::Src,
542 no_print_progress,
543 )
544 }
545
546 Self::Test {
547 target,
548 arguments,
549 runtime,
550 } => {
551 let paths = find_project_paths(directory)?;
552 run::command(
553 &paths,
554 arguments,
555 target,
556 runtime,
557 None,
558 run::Which::Test,
559 false,
560 )
561 }
562
563 Self::Dev {
564 target,
565 arguments,
566 runtime,
567 no_print_progress,
568 } => {
569 let paths = find_project_paths(directory)?;
570 run::command(
571 &paths,
572 arguments,
573 target,
574 runtime,
575 None,
576 run::Which::Dev,
577 no_print_progress,
578 )
579 }
580
581 Self::CompilePackage(opts) => compile_package::command(opts),
582
583 Self::Publish { replace, yes } => {
584 let paths = find_project_paths(directory)?;
585 publish::command(&paths, replace, yes)
586 }
587
588 Self::PrintConfig => {
589 let paths = find_project_paths(directory)?;
590 print_config(&paths)
591 }
592
593 Self::Hex(Hex::Retire {
594 package,
595 version,
596 reason,
597 message,
598 }) => hex::retire(package, version, reason, message),
599
600 Self::Hex(Hex::Unretire { package, version }) => hex::unretire(package, version),
601
602 Self::Hex(Hex::Revert { package, version }) => {
603 let paths = find_project_paths(directory)?;
604 hex::revert(&paths, package, version)
605 }
606
607 Self::Hex(Hex::Owner(Owner::Add {
608 package,
609 username_or_email,
610 level,
611 })) => owner::add(package, username_or_email, level),
612
613 Self::Hex(Hex::Owner(Owner::Transfer {
614 package,
615 username_or_email,
616 })) => owner::transfer(package, username_or_email),
617
618 Self::Add { packages, dev } => {
619 let paths = find_project_paths(directory)?;
620 add::command(&paths, packages, dev)
621 }
622
623 Self::Remove { packages } => {
624 let paths = find_project_paths(directory)?;
625 remove::command(&paths, packages)
626 }
627
628 Self::Update(options) => {
629 let paths = find_project_paths(directory)?;
630 dependencies::update(&paths, options.packages)
631 }
632
633 Self::Clean => {
634 let paths = find_project_paths(directory)?;
635 clean(&paths)
636 }
637
638 Self::LanguageServer => lsp::main(),
639
640 Self::Export(ExportTarget::ErlangShipment) => {
641 let paths = find_project_paths(directory)?;
642 export::erlang_shipment(&paths)
643 }
644 Self::Export(ExportTarget::Escript) => {
645 let paths = find_project_paths(directory)?;
646 export::escript(&paths)
647 }
648 Self::Export(ExportTarget::HexTarball) => {
649 let paths = find_project_paths(directory)?;
650 export::hex_tarball(&paths)
651 }
652 Self::Export(ExportTarget::JavascriptPrelude) => export::javascript_prelude(),
653 Self::Export(ExportTarget::TypescriptPrelude) => export::typescript_prelude(),
654 Self::Export(ExportTarget::PackageInterface { output }) => {
655 let paths = find_project_paths(directory)?;
656 export::package_interface(&paths, output)
657 }
658 Self::Export(ExportTarget::PackageInformation { output }) => {
659 let paths = find_project_paths(directory)?;
660 export::package_information(&paths, output)
661 }
662 Self::Export(ExportTarget::ZedExtension) => {
663 let paths = find_project_paths(directory)?;
664 export::zed_extension(&paths)
665 }
666 }
667 }
668}
669
670fn template_doc() -> &'static str {
671 "The template to use"
672}
673
674fn target_doc() -> &'static str {
675 "The platform to target"
676}
677
678fn no_print_progress_doc() -> &'static str {
679 "Don't print progress information"
680}
681
682fn runtime_doc() -> &'static str {
683 "The JavaScript runtime to target. This is only available on the \
684 JavaScript target"
685}
686
687#[derive(Subcommand, Debug, Clone)]
688pub enum ExportTarget {
689 /// Precompiled Erlang in a single file, suitable for CLIs
690 Escript,
691 /// Precompiled Erlang, suitable for deployment
692 ErlangShipment,
693 /// The package bundled into a tarball, suitable for publishing to Hex
694 HexTarball,
695 /// The JavaScript prelude module
696 JavascriptPrelude,
697 /// The TypeScript prelude module
698 TypescriptPrelude,
699 /// Information on the modules, functions, and types in the project in JSON format
700 PackageInterface {
701 /// The path to write the JSON file to
702 #[arg(long = "out", required = true)]
703 output: Utf8PathBuf,
704 },
705 /// Package information (gleam.toml) in JSON format
706 PackageInformation {
707 /// The path to write the JSON file to
708 #[arg(long = "out", required = true)]
709 output: Utf8PathBuf,
710 },
711 /// Build a Zed editor extension component (`extension.wasm`)
712 ///
713 /// Compiles the package for the Wasm target and packages a full
714 /// `zed:extension` world guest (API 0.7.0) with `zed:api-version`.
715 /// Writes `extension.wasm` at the package root.
716 #[command(name = "zed-extension")]
717 ZedExtension,
718}
719
720#[derive(Args, Debug, Clone)]
721pub struct NewOptions {
722 /// Location of the project root
723 pub project_root: String,
724
725 /// Name of the project
726 #[arg(long)]
727 pub name: Option<String>,
728
729 #[arg(long, ignore_case = true, default_value = "erlang", help = template_doc())]
730 pub template: new::Template,
731
732 /// Skip git initialization and creation of .gitignore, .git/* and .github/* files
733 #[arg(long)]
734 pub skip_git: bool,
735
736 /// Skip creation of .github/* files
737 #[arg(long)]
738 pub skip_github: bool,
739}
740
741#[derive(Args, Debug)]
742pub struct CompilePackage {
743 /// The compilation target for the generated project
744 #[arg(long, ignore_case = true, help = target_doc())]
745 target: Target,
746
747 /// The directory of the Gleam package
748 #[arg(long = "package")]
749 package_directory: Utf8PathBuf,
750
751 /// A directory to write compiled package to
752 #[arg(long = "out")]
753 output_directory: Utf8PathBuf,
754
755 /// A directories of precompiled Gleam projects
756 #[arg(long = "lib")]
757 libraries_directory: Utf8PathBuf,
758
759 /// The location of the JavaScript prelude module, relative to the `out`
760 /// directory.
761 ///
762 /// Required when compiling to JavaScript.
763 ///
764 /// This likely wants to be a `.mjs` file as NodeJS does not permit
765 /// importing of other JavaScript file extensions.
766 ///
767 #[arg(verbatim_doc_comment, long = "javascript-prelude")]
768 javascript_prelude: Option<Utf8PathBuf>,
769
770 /// Skip Erlang to BEAM bytecode compilation
771 #[arg(long = "no-beam")]
772 skip_beam_compilation: bool,
773}
774
775#[derive(Subcommand, Debug)]
776pub enum Dependencies {
777 /// List all dependency packages
778 List,
779
780 /// Download all dependency packages
781 Download,
782
783 /// List all outdated dependencies
784 Outdated,
785
786 /// Update dependency packages to their latest versions
787 Update(UpdateOptions),
788
789 /// Tree of all the dependency packages
790 Tree(TreeOptions),
791}
792
793#[derive(Subcommand, Debug)]
794pub enum Hex {
795 /// Retire a release from Hex
796 ///
797 /// This command uses the environment variable:
798 ///
799 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager.
800 ///
801 #[command(verbatim_doc_comment)]
802 Retire {
803 /// The name of the package to retire
804 #[arg(long)]
805 package: String,
806 /// The version to retire
807 #[arg(long)]
808 version: String,
809 /// The reason for the retirement
810 #[arg(long)]
811 reason: RetirementReason,
812 message: Option<String>,
813 },
814
815 /// Un-retire a release from Hex
816 ///
817 /// This command uses this environment variable:
818 ///
819 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager.
820 ///
821 #[command(verbatim_doc_comment)]
822 Unretire {
823 /// The name of the package to unretire
824 #[arg(long)]
825 package: String,
826 /// The version to unretire
827 #[arg(long)]
828 version: String,
829 },
830
831 /// Revert a release, removing it from Hex.
832 ///
833 /// Releases can only be reverted within 24 hours since they were published.
834 ///
835 /// This command uses this environment variable:
836 ///
837 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager.
838 ///
839 #[command(verbatim_doc_comment)]
840 Revert {
841 /// The name of the package to revert
842 #[arg(long)]
843 package: Option<String>,
844
845 /// The version to revert
846 #[arg(long)]
847 version: Option<String>,
848 },
849
850 /// Deal with package ownership
851 #[command(subcommand)]
852 Owner(Owner),
853
854 /// Log in to Hex. Replaces the credentials with new ones if already logged in.
855 Authenticate,
856}
857
858#[derive(Subcommand, Debug)]
859pub enum Owner {
860 /// Adds a new owner to the given package on Hex
861 ///
862 /// This command uses this environment variable:
863 ///
864 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate against the Hex package manager.
865 ///
866 #[command(verbatim_doc_comment)]
867 Add {
868 #[arg(long)]
869 package: String,
870
871 /// The username or email of the additional owner
872 #[arg(long = "user")]
873 username_or_email: String,
874
875 /// The ownership level
876 #[arg(long, default_value = "maintainer")]
877 level: hexpm::OwnerLevel,
878 },
879
880 /// Transfers ownership of the given package to a new Hex user
881 ///
882 /// This command uses this environment variable:
883 ///
884 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate against the Hex package manager.
885 ///
886 #[command(verbatim_doc_comment)]
887 Transfer {
888 /// The name of the package
889 #[arg(long)]
890 package: String,
891
892 /// The username or email of the new owner
893 #[arg(long = "user")]
894 username_or_email: String,
895 },
896}
897
898#[derive(Subcommand, Debug)]
899pub enum Docs {
900 /// Render HTML docs locally
901 Build {
902 /// Opens the docs in a browser after rendering
903 #[arg(long)]
904 open: bool,
905
906 /// Which compilation target to use
907 #[arg(short, long, ignore_case = true, help = target_doc())]
908 target: Option<Target>,
909 },
910
911 /// Publish HTML docs to HexDocs
912 ///
913 /// This command uses this environment variable:
914 ///
915 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager.
916 ///
917 #[command(verbatim_doc_comment)]
918 Publish,
919
920 /// Remove HTML docs from HexDocs
921 ///
922 /// This command uses this environment variable:
923 ///
924 /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager.
925 ///
926 #[command(verbatim_doc_comment)]
927 Remove {
928 /// The name of the package
929 #[arg(long)]
930 package: String,
931
932 /// The version of the docs to remove
933 #[arg(long)]
934 version: String,
935 },
936}
937
938pub fn main() {
939 initialise_logger();
940 panic::add_handler();
941 let stderr = cli::stderr_buffer_writer();
942 let result = get_current_directory()
943 .and_then(|working_directory| Command::parse().run(working_directory));
944 match result {
945 Ok(_) => {
946 tracing::info!("Successfully completed");
947 }
948 Err(error) => {
949 tracing::error!(error = ?error, "Failed");
950 let mut buffer = stderr.buffer();
951 error.pretty(&mut buffer);
952 stderr.print(&buffer).expect("Final result error writing");
953 std::process::exit(1);
954 }
955 }
956}
957
958fn command_check(paths: &ProjectPaths, target: Option<Target>) -> Result<()> {
959 let _ = build::main(
960 paths,
961 Options {
962 root_target_support: TargetSupport::Enforced,
963 warnings_as_errors: false,
964 codegen: Codegen::DepsOnly,
965 compile: Compile::All,
966 mode: Mode::Dev,
967 target,
968 no_print_progress: false,
969 },
970 build::download_dependencies(paths, cli::Reporter::new())?,
971 )?;
972 Ok(())
973}
974
975fn command_build(
976 paths: &ProjectPaths,
977 target: Option<Target>,
978 warnings_as_errors: bool,
979 no_print_progress: bool,
980) -> Result<()> {
981 let manifest = if no_print_progress {
982 build::download_dependencies(paths, NullTelemetry)?
983 } else {
984 build::download_dependencies(paths, cli::Reporter::new())?
985 };
986 let _ = build::main(
987 paths,
988 Options {
989 root_target_support: TargetSupport::Enforced,
990 warnings_as_errors,
991 codegen: Codegen::All,
992 compile: Compile::All,
993 mode: Mode::Dev,
994 target,
995 no_print_progress,
996 },
997 manifest,
998 )?;
999 Ok(())
1000}
1001
1002fn print_config(paths: &ProjectPaths) -> Result<()> {
1003 let config = root_config(paths)?;
1004 println!("{config:#?}");
1005 Ok(())
1006}
1007
1008fn clean(paths: &ProjectPaths) -> Result<()> {
1009 fs::delete_directory(&paths.build_directory())
1010}
1011
1012fn initialise_logger() {
1013 let enable_colours = std::env::var("GLEAM_LOG_NOCOLOUR").is_err();
1014 tracing_subscriber::fmt()
1015 .with_writer(std::io::stderr)
1016 .with_env_filter(std::env::var("GLEAM_LOG").unwrap_or_else(|_| "off".into()))
1017 .with_target(false)
1018 .with_ansi(enable_colours)
1019 .without_time()
1020 .init();
1021}
1022
1023fn find_project_paths(current_dir: Utf8PathBuf) -> Result<ProjectPaths> {
1024 get_project_root(current_dir).map(ProjectPaths::new)
1025}
1026
1027#[cfg(test)]
1028fn project_paths_at_current_directory_without_toml() -> ProjectPaths {
1029 let current_dir = get_current_directory().expect("Failed to get current directory");
1030 ProjectPaths::new(current_dir)
1031}
1032
1033fn download_dependencies(paths: &ProjectPaths) -> Result<()> {
1034 _ = dependencies::resolve_and_download(
1035 paths,
1036 cli::Reporter::new(),
1037 None,
1038 Vec::new(),
1039 dependencies::DependencyManagerConfig {
1040 use_manifest: dependencies::UseManifest::Yes,
1041 check_major_versions: dependencies::CheckMajorVersions::No,
1042 },
1043 )?;
1044 Ok(())
1045}