cargo_web/
lib.rs

1//! A `cargo` subcommand for the client-side Web.
2
3#![deny(
4    missing_debug_implementations,
5    trivial_numeric_casts,
6    unstable_features,
7    unused_import_braces,
8    unused_qualifications
9)]
10
11#[macro_use]
12extern crate structopt;
13extern crate clap;
14extern crate digest;
15extern crate futures;
16extern crate http;
17extern crate hyper;
18extern crate libflate;
19extern crate notify;
20extern crate pbr;
21extern crate reqwest;
22extern crate serde;
23extern crate sha1;
24extern crate sha2;
25extern crate tar;
26extern crate tempfile;
27extern crate toml;
28#[macro_use]
29extern crate serde_derive;
30#[macro_use]
31extern crate serde_json;
32extern crate base_x;
33extern crate handlebars;
34extern crate indexmap;
35extern crate regex;
36extern crate unicode_categories;
37extern crate walkdir;
38extern crate websocket;
39#[macro_use]
40extern crate lazy_static;
41extern crate directories;
42extern crate percent_encoding;
43
44extern crate parity_wasm;
45#[macro_use]
46extern crate log;
47extern crate env_logger;
48extern crate rustc_demangle;
49
50extern crate ansi_term;
51extern crate cargo_metadata;
52
53extern crate memmap;
54extern crate semver;
55
56extern crate atty;
57extern crate open;
58#[macro_use]
59extern crate failure;
60extern crate mime_guess;
61
62mod cargo_shim;
63
64#[macro_use]
65mod utils;
66mod build;
67mod chrome_devtools;
68mod cmd_build;
69mod cmd_deploy;
70mod cmd_prepare_emscripten;
71mod cmd_start;
72mod cmd_test;
73mod config;
74mod deployment;
75mod emscripten;
76mod error;
77mod http_utils;
78mod package;
79mod project_dirs;
80mod test_chromium;
81mod wasm;
82mod wasm_context;
83mod wasm_export_main;
84mod wasm_export_table;
85mod wasm_gc;
86mod wasm_hook_grow;
87mod wasm_inline_js;
88mod wasm_intrinsics;
89mod wasm_js_export;
90mod wasm_js_snippet;
91mod wasm_runtime;
92
93use std::ffi::OsStr;
94use std::net::{IpAddr, ToSocketAddrs};
95use std::path::PathBuf;
96
97use build::{Backend, BuildArgs};
98use cargo_shim::MessageFormat;
99use error::Error;
100use wasm_runtime::RuntimeKind;
101
102/// CLI for `cargo-web`
103#[derive(Debug, StructOpt)]
104#[structopt(name = "cargo-web")]
105#[structopt(about = "A `cargo` subcommand for the client-side web.")]
106#[structopt(raw(global_setting = "structopt::clap::AppSettings::ColoredHelp"))]
107#[structopt(raw(setting = "structopt::clap::AppSettings::VersionlessSubcommands"))]
108#[structopt(rename_all = "kebab-case")]
109pub enum CargoWebOpts {
110    /// Compile a local package and all of its dependencies
111    Build(BuildOpts),
112    /// Typecheck a local package and all of its dependencies
113    Check(CheckOpts),
114    /// Deploys your project so that it's ready to be served statically
115    Deploy(DeployOpts),
116    /// Fetches and installs prebuilt Emscripten packages
117    PrepareEmscripten(PrepareEmscriptenOpts),
118    /// Runs an embedded web server, which serves the built project
119    Start(StartOpts),
120    /// Compiles and runs tests
121    Test(TestOpts),
122    #[doc(hidden)]
123    #[structopt(raw(setting = "structopt::clap::AppSettings::Hidden"))]
124    __Nonexhaustive,
125}
126
127/// Run a subcommand based on a configuration
128pub fn run(cfg: CargoWebOpts) -> Result<(), Error> {
129    match cfg {
130        CargoWebOpts::Build(BuildOpts {
131            build_args,
132            build_target,
133            ext,
134        }) => cmd_build::command_build(BuildArgs::new(build_args, ext, build_target)?),
135        CargoWebOpts::Check(CheckOpts {
136            build_args,
137            build_target,
138            ext,
139        }) => cmd_build::command_check(BuildArgs::new(build_args, ext, build_target)?),
140        CargoWebOpts::Deploy(DeployOpts { build_args, output }) => {
141            cmd_deploy::command_deploy(build_args.into(), output)
142        }
143        CargoWebOpts::PrepareEmscripten(_) => cmd_prepare_emscripten::command_prepare_emscripten(),
144        CargoWebOpts::Start(StartOpts {
145            build_args,
146            build_target,
147            auto_reload,
148            open,
149            port,
150            host,
151        }) => cmd_start::command_start(
152            BuildArgs::from(build_args).with_target(build_target),
153            host,
154            port,
155            open,
156            auto_reload,
157        ),
158        CargoWebOpts::Test(TestOpts {
159            build_args,
160            nodejs,
161            no_run,
162            passthrough,
163        }) => {
164            let pass_os = passthrough.iter().map(OsStr::new).collect::<Vec<_>>();
165            cmd_test::command_test(build_args.into(), nodejs, no_run, &pass_os)
166        }
167        CargoWebOpts::__Nonexhaustive => unreachable!(),
168    }
169}
170
171/// Options for `cargo web build`
172#[derive(Debug, StructOpt)]
173#[structopt(rename_all = "kebab-case")]
174pub struct BuildOpts {
175    #[structopt(flatten)]
176    build_args: Build,
177    #[structopt(flatten)]
178    ext: BuildExt,
179    #[structopt(flatten)]
180    build_target: Target,
181}
182
183/// Options for `cargo web check`
184#[derive(Debug, StructOpt)]
185#[structopt(rename_all = "kebab-case")]
186pub struct CheckOpts {
187    #[structopt(flatten)]
188    build_args: Build,
189    #[structopt(flatten)]
190    ext: BuildExt,
191    #[structopt(flatten)]
192    build_target: Target,
193}
194
195/// Options for `cargo web deploy`
196#[derive(Debug, StructOpt)]
197#[structopt(rename_all = "kebab-case")]
198pub struct DeployOpts {
199    /// Output directory; the default is `$CARGO_TARGET_DIR/deploy`
200    #[structopt(short = "o", long, parse(from_os_str))]
201    output: Option<PathBuf>,
202    #[structopt(flatten)]
203    build_args: Build,
204}
205
206/// Options for `cargo web prepare-emscripten`
207#[derive(Debug, StructOpt)]
208#[structopt(rename_all = "kebab-case")]
209pub struct PrepareEmscriptenOpts {
210    #[doc(hidden)]
211    #[structopt(raw(set = "structopt::clap::ArgSettings::Hidden"))]
212    __reserved: bool,
213}
214
215/// Options for `cargo web start`
216#[derive(Debug, StructOpt)]
217#[structopt(rename_all = "kebab-case")]
218pub struct StartOpts {
219    /// Bind the server to this address
220    #[structopt(
221        long,
222        parse(try_from_str = "resolve_host"),
223        default_value = "localhost"
224    )]
225    host: IpAddr,
226    /// Bind the server to this port
227    #[structopt(long, default_value = "8000")]
228    port: u16,
229    /// Open browser after server starts
230    #[structopt(long)]
231    open: bool,
232    /// Will try to automatically reload the page on rebuild
233    #[structopt(long)]
234    auto_reload: bool,
235    #[structopt(flatten)]
236    build_target: Target,
237    #[structopt(flatten)]
238    build_args: Build,
239}
240
241/// Options for `cargo web test`
242#[derive(Debug, StructOpt)]
243#[structopt(rename_all = "kebab-case")]
244pub struct TestOpts {
245    /// Compile, but don't run tests
246    #[structopt(long)]
247    no_run: bool,
248    /// Uses Node.js to run the tests
249    #[structopt(long)]
250    nodejs: bool,
251    #[structopt(flatten)]
252    build_args: Build,
253    /// all additional arguments will be passed through to the test runner
254    passthrough: Vec<String>,
255}
256
257/// Select a target to build
258#[derive(Debug, StructOpt)]
259#[structopt(rename_all = "kebab-case")]
260struct Target {
261    /// Build only this package's library
262    #[structopt(long, group = "target_type")]
263    lib: bool,
264    /// Build only the specified binary
265    #[structopt(long, group = "target_type")]
266    bin: Option<String>,
267    /// Build only the specified example
268    #[structopt(long, group = "target_type")]
269    example: Option<String>,
270    /// Build only the specified test target
271    #[structopt(long, group = "target_type")]
272    test: Option<String>,
273    /// Build only the specified benchmark target
274    #[structopt(long, group = "target_type")]
275    bench: Option<String>,
276}
277
278impl Default for Target {
279    fn default() -> Self {
280        Self {
281            lib: false,
282            bin: None,
283            example: None,
284            test: None,
285            bench: None,
286        }
287    }
288}
289
290/// Specify additional build options
291#[derive(Debug, StructOpt)]
292#[structopt(rename_all = "kebab-case")]
293struct BuildExt {
294    /// Selects the stdout output format
295    #[structopt(
296        long,
297        default_value = "human",
298        parse(try_from_str),
299        raw(possible_values = "&[\"human\", \"json\"]"),
300        raw(set = "structopt::clap::ArgSettings::NextLineHelp")
301    )]
302    message_format: MessageFormat,
303    /// Selects the type of JavaScript runtime which will be generated
304    /// (Only valid when targeting `wasm32-unknown-unknown`). [possible values:
305    /// standalone, library-es6, web-extension]
306    #[structopt(
307        long,
308        parse(try_from_str),
309        raw(set = "structopt::clap::ArgSettings::NextLineHelp")
310    )]
311    runtime: Option<RuntimeKind>,
312}
313
314impl Default for BuildExt {
315    fn default() -> Self {
316        Self {
317            message_format: MessageFormat::Json,
318            runtime: None,
319        }
320    }
321}
322
323/// Build configuration for one or more targets
324#[derive(Debug, StructOpt)]
325#[structopt(rename_all = "kebab-case")]
326struct Build {
327    /// Package to build
328    #[structopt(short = "p", long)]
329    pub package: Option<String>,
330    /// Additional features to build (space-delimited list)
331    #[structopt(long, group = "build_features")]
332    pub features: Option<String>,
333    /// Build all available features
334    #[structopt(long, group = "build_features")]
335    pub all_features: bool,
336    /// Do not build the `default` feature
337    #[structopt(long)]
338    pub no_default_features: bool,
339    /// Won't try to download Emscripten; will always use the system one
340    #[structopt(long)]
341    pub use_system_emscripten: bool,
342    /// Build artifacts in release mode, with optimizations
343    #[structopt(long)]
344    pub release: bool,
345    /// Target triple to build for, overriding setting in `Web.toml`. If not
346    /// specified in `Web.toml`, default target is `wasm32-unknown-unknown`.
347    #[structopt(
348        long,
349        parse(try_from_str),
350        raw(
351            possible_values = "&[\"wasm32-unknown-unknown\", \"wasm32-unknown-emscripten\", \"asmjs-unknown-emscripten\"]"
352        ),
353        raw(set = "structopt::clap::ArgSettings::NextLineHelp")
354    )]
355    pub target: Option<Backend>,
356    /// Use verbose output
357    #[structopt(short = "v", long)]
358    pub verbose: bool,
359}
360
361impl Default for Build {
362    /// Returns a sensible default config.
363    ///
364    /// # Note
365    /// If you want to change the target triple, use `Into`, e.g.
366    /// `target: "asmjs-unknown-emscripten".into()`
367    fn default() -> Self {
368        Self {
369            package: None,
370            features: None,
371            all_features: false,
372            no_default_features: false,
373            use_system_emscripten: false,
374            release: false,
375            target: None,
376            verbose: false,
377        }
378    }
379}
380
381/// Resolve hostname to IP address
382fn resolve_host(host: &str) -> std::io::Result<IpAddr> {
383    (host, 0)
384        .to_socket_addrs()
385        .map(|itr| itr.map(|a| a.ip()).collect::<Vec<_>>()[0])
386}