1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
// Copyright (C) 2018 Christopher R. Field.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! The implementation for the `init` command. The `init` command for the `cargo
//! wix` subcommand is focused on creating a WiX Source file (wxs) based on the
//! contents of the Cargo manifest file (Cargo.toml) for the project and any
//! run-time based settings.
//!
//! The `init` command should generally be called before any other commands and
//! it should only be called once per project. Once a WiX Source file (wxs)
//! exists for the project, the `init` command does not need to be executed
//! again.

use camino::Utf8PathBuf;
use cargo_metadata::Package;

use crate::print;
use crate::stored_path::StoredPathBuf;
use crate::Error;
use crate::Result;
use crate::WIX;
use crate::WIX_SOURCE_FILE_EXTENSION;
use crate::WIX_SOURCE_FILE_NAME;

use log::{debug, info, trace};

use std::fs;
use std::path::{Path, PathBuf};

/// A builder for running the `cargo wix init` subcommand.
#[derive(Debug, Clone)]
pub struct Builder<'a> {
    banner: Option<&'a str>,
    binaries: Option<Vec<&'a str>>,
    copyright_year: Option<&'a str>,
    copyright_holder: Option<&'a str>,
    description: Option<&'a str>,
    dialog: Option<&'a str>,
    eula: Option<&'a str>,
    force: bool,
    help_url: Option<&'a str>,
    input: Option<&'a str>,
    license: Option<&'a str>,
    manufacturer: Option<&'a str>,
    output: Option<&'a str>,
    package: Option<&'a str>,
    path_guid: Option<&'a str>,
    product_icon: Option<&'a str>,
    product_name: Option<&'a str>,
    upgrade_guid: Option<&'a str>,
}

impl<'a> Builder<'a> {
    /// Creates a new `Builder` instance.
    pub fn new() -> Self {
        Builder {
            banner: None,
            binaries: None,
            copyright_year: None,
            copyright_holder: None,
            description: None,
            dialog: None,
            eula: None,
            force: false,
            help_url: None,
            input: None,
            license: None,
            manufacturer: None,
            output: None,
            package: None,
            path_guid: None,
            product_icon: None,
            product_name: None,
            upgrade_guid: None,
        }
    }

    /// Sets the path to a bitmap (BMP) file to be used as a banner image across
    /// the top of each dialog in the installer.
    ///
    /// The banner image must be 493 x 58 pixels. See the [Wix Toolset
    /// documentation] for details about [customization].
    ///
    /// [Wix Toolset documentation]: http://wixtoolset.org/documentation/
    /// [customization]: http://wixtoolset.org/documentation/manual/v3/wixui/wixui_customizations.html
    pub fn banner(&mut self, b: Option<&'a str>) -> &mut Self {
        self.banner = b;
        self
    }

    /// Sets the path to the binaries.
    ///
    /// The default is to, first, collect all of the `bin` sections and use the
    /// `name` field within each `bin` section of the package's manifest for
    /// each binary's name and create the following source with the `.exe` file
    /// extension: `target\release\<binary-name>.exe`, where `<binary-name>` is
    /// replaced with the name obtained from each `bin` section. All binaries
    /// are included in the installer. If no `bin` sections exist, then the
    /// package's `name` field is used and only one binary is included in the
    /// installer.
    ///
    /// This method skips creating the binary names and sources from the
    /// package's manifest (Cargo.toml) and uses the supplied paths, regardless
    /// of the number of `bin` sections in the package's manifest. The binary
    /// name is extracted from each supplied path as the file stem (file name
    /// without extension).
    ///
    /// This method is useful for including binaries, a.k.a. executables, in the
    /// installer that are necessary for the application to run but are not
    /// necessarily Rust/Cargo built binaries. However, this method overrides
    /// _all_ binaries in the Cargo-based project, so if the installer is to
    /// include a mixture of external and internal binaries, the internal
    /// binaries must be explicitly included in this method.
    pub fn binaries(&mut self, b: Option<Vec<&'a str>>) -> &mut Self {
        self.binaries = b;
        self
    }

    /// Sets the copyright holder for the generated license file and EULA.
    ///
    /// The default is to use the `authors` field of the
    /// package's manifest (Cargo.toml). This method can be used to override the
    /// default and set a different copyright holder if and when a Rich Text
    /// Format (RTF) license and EULA are generated based on the value of the
    /// `license` field in the package's manifest (Cargo.toml).
    ///
    /// This value is ignored and not used if an EULA is set with the [`eula`]
    /// method, if a custom EULA is set using the `license-file` field in the
    /// package's manifest (Cargo.toml), or an EULA is _not_ generated from the
    /// `license` field in the package's manifest (Cargo.toml).
    ///
    /// [`eula`]: https://volks73.github.io/cargo-wix/cargo_wix/initialize.html#eula
    pub fn copyright_holder(&mut self, h: Option<&'a str>) -> &mut Self {
        self.copyright_holder = h;
        self
    }

    /// Sets the copyright year for the generated license file and EULA.
    ///
    /// The default is to use the current year. This method can be used to
    /// override the default and set a specific year if and when a Rich Text
    /// Format (RTF) license and EULA are generated based on the value of the
    /// `license` field in the package's manifest (Cargo.toml).
    ///
    /// This value is ignored and not used if an EULA is set with the [`eula`]
    /// method, if a custom EULA is set using the `license-file` field in the
    /// package's manifest (Cargo.toml), or an EULA is _not_ generated from the
    /// `license` field in the package's manifest (Cargo.toml).
    ///
    /// [`eula`]: https://volks73.github.io/cargo-wix/cargo_wix/initialize.html#eula
    pub fn copyright_year(&mut self, y: Option<&'a str>) -> &mut Self {
        self.copyright_year = y;
        self
    }

    /// Sets the description.
    ///
    /// This overrides the description determined from the `description` field
    /// in the package's manifest (Cargo.toml).
    pub fn description(&mut self, d: Option<&'a str>) -> &mut Self {
        self.description = d;
        self
    }

    /// Sets the path to a bitmap (`.bmp`) file that will be displayed on the
    /// first dialog to the left.
    ///
    /// The image must be 493 x 312 pixels. See the [Wix Toolset
    /// documentation] for details about [customization].
    ///
    /// [Wix Toolset documentation]: http://wixtoolset.org/documentation/
    /// [customization]: http://wixtoolset.org/documentation/manual/v3/wixui/wixui_customizations.html
    pub fn dialog(&mut self, d: Option<&'a str>) -> &mut Self {
        self.dialog = d;
        self
    }

    /// Sets the path to a custom End User License Agreement (EULA).
    ///
    /// The EULA is the text that appears in the license agreement dialog of the
    /// installer, where a checkbox is present for the user to agree to the
    /// terms of the license. Typically, this is the same as the license file
    /// that is included as a [sidecar] file in the installation destination of
    /// the executable.
    ///
    /// The default is to generate an EULA from an embedded template as a RTF
    /// file based on the name of the license specified in the `license` field
    /// of the package's manifest (Cargo.toml). This method can be used to
    /// override the default and specify a custom EULA. A custom EULA must be in
    /// the RTF format and have the `.rtf` file extension.
    ///
    /// If the `license` field is not specified or a template for the license
    /// does not exist but the `license-file` field does specify a path to a
    /// file with the RTF extension, then that RTF file is used as the EULA for
    /// the license agreement dialog in the installer. Finally, if the
    /// `license-file` does not exist or it specifies a file that does not have
    /// the `.rtf` extension, then the license agreement dialog is skipped and
    /// there is no EULA for the installer. This would override the default
    /// behavior and ensure the license agreement dialog is used.
    ///
    /// [sidecar]: https://en.wikipedia.org/wiki/Sidecar_file
    pub fn eula(&mut self, e: Option<&'a str>) -> &mut Self {
        self.eula = e;
        self
    }

    /// Forces the generation of new output even if the various outputs already
    /// exists at the destination.
    pub fn force(&mut self, f: bool) -> &mut Self {
        self.force = f;
        self
    }

    /// Sets the help URL.
    ///
    /// The default is to obtain a URL from one of the following fields in the
    /// package's manifest (Cargo.toml): `documentation`, `homepage`, or
    /// `repository`. If none of these are specified, then the default is to
    /// exclude a help URL from the installer. This will override the default
    /// behavior and provide a help URL for the installer if none of the fields
    /// exist.
    ///
    /// The help URL is the URL that appears in the Add/Remove Program control
    /// panel, a.k.a. `ARPHELPLINK`.
    pub fn help_url(&mut self, h: Option<&'a str>) -> &mut Self {
        self.help_url = h;
        self
    }

    /// Sets the path to a package's manifest (Cargo.toml) to be used to
    /// generate a WiX Source (wxs) file from the embedded template.
    ///
    /// A `wix` and `wix\main.wxs` file will be created in the same directory as
    /// the package's manifest. The default is to use the package's manifest in
    /// the current working directory.
    pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
        self.input = i;
        self
    }

    /// Sets the path to a file to be used as the [sidecar] license file.
    ///
    /// This will override the `license-file` field in the package's manifest
    /// (Cargo.toml). If the file has the `.rtf` extension, then it will also be
    /// used for the EULA in the license agreement dialog for the installer.
    /// Otherwise, the [`eula`] method can be used to set an RTF file as the
    /// EULA for the license agreement dialog that is independent of the sidecar
    /// license file.
    ///
    /// The default is to use the value specified in the `license-file` field of
    /// the package's manifest or generate a license file and EULA from an
    /// embedded template based on the license ID used in the `license` field
    /// of the package's manifest. If none of these fields are specified or
    /// overridden, then a license file is _not_ included in the installation
    /// directory and the license agreement dialog is skipped in the installer.
    ///
    /// [sidecar]: https://en.wikipedia.org/wiki/Sidecar_file
    /// [`eula`]: #eula
    pub fn license(&mut self, l: Option<&'a str>) -> &mut Self {
        self.license = l;
        self
    }

    /// Sets the manufacturer.
    ///
    /// Default is to use the `authors` field of the
    /// package's manifest (Cargo.toml). This would override the default value.
    pub fn manufacturer(&mut self, m: Option<&'a str>) -> &mut Self {
        self.manufacturer = m;
        self
    }

    /// Sets the destination for creating all of the output from initialization.
    ///
    /// The default is to create all initialization output in the same folder as
    /// the package's manifest (Cargo.toml). Thus, a `wix` folder will be
    /// created within the same folder as the `Cargo.toml` file and all
    /// initialization created files will be placed in the `wix` folder.
    ///
    /// This method can be used to override the default output destination and
    /// have the files related to creating an installer placed in a different
    /// location inside or outside of the package's project folder.
    pub fn output(&mut self, o: Option<&'a str>) -> &mut Self {
        self.output = o;
        self
    }

    /// Sets the path to an image file to be used for product icon.
    ///
    /// The product icon is the icon that appears for an installed application
    /// in the Add/Remove Programs (ARP) control panel. If a product icon is
    /// _not_ defined for an application within the installer, then the Windows
    /// OS assigns a generic one.
    pub fn product_icon(&mut self, p: Option<&'a str>) -> &mut Self {
        self.product_icon = p;
        self
    }

    /// Sets the package within a workspace to initialize an installer.
    ///
    /// Each package within a workspace has its own package manifest, i.e.
    /// `Cargo.toml`. This indicates within package manifest within a workspace
    /// should be used when initializing an installer.
    pub fn package(&mut self, p: Option<&'a str>) -> &mut Self {
        self.package = p;
        self
    }

    /// Sets the GUID for the path component.
    ///
    /// The default automatically generates the GUID needed for the path
    /// component. A GUID is needed so that the path component can be
    /// successfully removed on uninstall.
    ///
    /// Generally, the path component GUID should be generated only once per
    /// project/product and then the same GUID used every time the installer is
    /// created. The GUID is stored in the WiX Source (WXS) file. However,
    /// this allows using an existing GUID, possibly obtained with another tool.
    pub fn path_guid(&mut self, p: Option<&'a str>) -> &mut Self {
        self.path_guid = p;
        self
    }

    /// Sets the product name.
    ///
    /// The default is to use the `name` field under the `package` section of
    /// the package's manifest (Cargo.toml). This overrides that value. An error
    /// occurs if the `name` field is not found in the manifest.
    ///
    /// The product name is also used for the disk prompt during installation
    /// and the name of the default installation destination. For example, a
    /// product anme of `Example` will have an installation destination of
    /// `C:\Program Files\Example` as the default during installation.
    pub fn product_name(&mut self, p: Option<&'a str>) -> &mut Self {
        self.product_name = p;
        self
    }

    /// Sets the Upgrade Code GUID.
    ///
    /// The default automatically generates the need GUID for the `UpgradeCode`
    /// attribute to the `Product` tag. The Upgrade Code uniquely identifies the
    /// installer. It is used to determine if the new installer is the same
    /// product and the current installation should be removed and upgraded to
    /// this version. If the GUIDs of the current product and new product do
    /// _not_ match, then Windows will treat the two installers as separate
    /// products.
    ///
    /// Generally, the upgrade code should be generated only one per
    /// project/product and then the same code used every time the installer is
    /// created and the GUID is stored in the WiX Source (WXS) file. However,
    /// this allows the user to provide an existing GUID for the upgrade code.
    pub fn upgrade_guid(&mut self, u: Option<&'a str>) -> &mut Self {
        self.upgrade_guid = u;
        self
    }

    /// Builds a read-only initialization execution.
    pub fn build(&mut self) -> Execution {
        Execution {
            banner: self.banner.map(StoredPathBuf::from),
            binaries: self
                .binaries
                .as_ref()
                .map(|b| b.iter().copied().map(StoredPathBuf::from).collect()),
            copyright_year: self.copyright_year.map(String::from),
            copyright_holder: self.copyright_holder.map(String::from),
            description: self.description.map(String::from),
            dialog: self.dialog.map(StoredPathBuf::from),
            eula: self.eula.map(StoredPathBuf::from),
            force: self.force,
            help_url: self.help_url.map(String::from),
            input: self.input.map(PathBuf::from),
            license: self.license.map(StoredPathBuf::from),
            manufacturer: self.manufacturer.map(String::from),
            output: self.output.map(PathBuf::from),
            package: self.package.map(String::from),
            path_guid: self.path_guid.map(String::from),
            product_icon: self.product_icon.map(StoredPathBuf::from),
            product_name: self.product_name.map(String::from),
            upgrade_guid: self.upgrade_guid.map(String::from),
        }
    }
}

impl<'a> Default for Builder<'a> {
    fn default() -> Self {
        Builder::new()
    }
}

/// A context for creating the necessary files to eventually build an installer.
#[derive(Debug)]
pub struct Execution {
    banner: Option<StoredPathBuf>,
    binaries: Option<Vec<StoredPathBuf>>,
    copyright_holder: Option<String>,
    copyright_year: Option<String>,
    description: Option<String>,
    dialog: Option<StoredPathBuf>,
    eula: Option<StoredPathBuf>,
    force: bool,
    help_url: Option<String>,
    input: Option<PathBuf>,
    license: Option<StoredPathBuf>,
    manufacturer: Option<String>,
    output: Option<PathBuf>,
    package: Option<String>,
    path_guid: Option<String>,
    product_icon: Option<StoredPathBuf>,
    product_name: Option<String>,
    upgrade_guid: Option<String>,
}

impl Execution {
    /// Generates the necessary files to eventually create, or build, an
    /// installer based on a built context.
    pub fn run(self) -> Result<()> {
        debug!("banner = {:?}", self.banner);
        debug!("binaries = {:?}", self.binaries);
        debug!("copyright_holder = {:?}", self.copyright_holder);
        debug!("copyright_year = {:?}", self.copyright_year);
        debug!("description = {:?}", self.description);
        debug!("dialog = {:?}", self.dialog);
        debug!("eula = {:?}", self.eula);
        debug!("force = {:?}", self.force);
        debug!("help_url = {:?}", self.help_url);
        debug!("input = {:?}", self.input);
        debug!("license = {:?}", self.license);
        debug!("manufacturer = {:?}", self.manufacturer);
        debug!("output = {:?}", self.output);
        debug!("package = {:?}", self.package);
        debug!("path_guid = {:?}", self.path_guid);
        debug!("product_icon = {:?}", self.product_icon);
        debug!("product_name = {:?}", self.product_name);
        debug!("upgrade_guid = {:?}", self.upgrade_guid);
        let manifest = super::manifest(self.input.as_ref())?;
        let package = super::package(&manifest, self.package.as_deref())?;
        let mut destination = self.destination(&package);
        debug!("destination = {:?}", destination);
        if !destination.exists() {
            info!("Creating the '{}' directory", destination);
            fs::create_dir(&destination)?;
        }

        destination.push(WIX_SOURCE_FILE_NAME);
        destination.set_extension(WIX_SOURCE_FILE_EXTENSION);
        if destination.exists() && !self.force {
            return Err(Error::already_exists(&destination));
        } else {
            info!("Creating the '{}' file", destination);
            let mut wxs_printer = print::wxs::Builder::new();
            wxs_printer.banner(self.banner.as_ref().map(|s| s.as_str()));
            wxs_printer.binaries(
                self.binaries
                    .as_ref()
                    .map(|b| b.iter().map(|s| s.as_str()).collect()),
            );
            wxs_printer.description(self.description.as_ref().map(String::as_ref));
            wxs_printer.dialog(self.dialog.as_deref().map(|s| s.as_str()));
            wxs_printer.eula(self.eula.as_deref().map(|p| p.as_str()));
            wxs_printer.help_url(self.help_url.as_ref().map(String::as_ref));
            wxs_printer.input(self.input.as_deref().and_then(Path::to_str));
            wxs_printer.license(self.license.as_deref().map(|p| p.as_str()));
            wxs_printer.manufacturer(self.manufacturer.as_ref().map(String::as_ref));
            wxs_printer.output(Some(destination.as_str()));
            wxs_printer.package(self.package.as_deref());
            wxs_printer.path_guid(self.path_guid.as_ref().map(String::as_ref));
            wxs_printer.product_icon(self.product_icon.as_ref().map(|s| s.as_str()));
            wxs_printer.product_name(self.product_name.as_ref().map(String::as_ref));
            wxs_printer.upgrade_guid(self.upgrade_guid.as_ref().map(String::as_ref));

            wxs_printer.build().run()?;
        }
        Ok(())
    }

    fn destination(&self, package: &Package) -> Utf8PathBuf {
        if let Some(output) = &self.output {
            trace!("An output path has been explicitly specified");
            Utf8PathBuf::from_path_buf(output.to_owned()).unwrap()
        } else {
            trace!("An output path has NOT been explicitly specified. Implicitly determine output from manifest location.");
            package
                .manifest_path
                .parent()
                .map(|p| p.to_path_buf())
                .map(|mut p| {
                    p.push(WIX);
                    p
                })
                .unwrap()
        }
    }
}

impl Default for Execution {
    fn default() -> Self {
        Builder::new().build()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    mod builder {
        use super::*;

        const PATH_GUID: &str = "0B5DFC00-1480-4044-AC1A-BEF00E0A91BB";
        const UPGRADE_GUID: &str = "0631BBDF-4079-4C20-823F-7EA8DE40BF08";

        #[test]
        fn defaults_are_correct() {
            let actual = Builder::new();
            assert!(actual.banner.is_none());
            assert!(actual.binaries.is_none());
            assert!(actual.copyright_year.is_none());
            assert!(actual.copyright_holder.is_none());
            assert!(actual.description.is_none());
            assert!(actual.dialog.is_none());
            assert!(actual.eula.is_none());
            assert!(!actual.force);
            assert!(actual.help_url.is_none());
            assert!(actual.input.is_none());
            assert!(actual.license.is_none());
            assert!(actual.manufacturer.is_none());
            assert!(actual.output.is_none());
            assert!(actual.path_guid.is_none());
            assert!(actual.product_icon.is_none());
            assert!(actual.product_name.is_none());
            assert!(actual.upgrade_guid.is_none());
        }

        #[test]
        fn banner_works() {
            const EXPECTED: &str = "img\\Banner.bmp";
            let mut actual = Builder::new();
            actual.banner(Some(EXPECTED));
            assert_eq!(actual.banner, Some(EXPECTED));
        }

        #[test]
        fn binaries_works() {
            const EXPECTED: &str = "bin\\Example.exe";
            let mut actual = Builder::new();
            actual.binaries(Some(vec![EXPECTED]));
            assert_eq!(actual.binaries, Some(vec![EXPECTED]));
        }

        #[test]
        fn copyright_holder_works() {
            const EXPECTED: &str = "holder";
            let mut actual = Builder::new();
            actual.copyright_holder(Some(EXPECTED));
            assert_eq!(actual.copyright_holder, Some(EXPECTED));
        }

        #[test]
        fn copyright_year_works() {
            const EXPECTED: &str = "2018";
            let mut actual = Builder::new();
            actual.copyright_year(Some(EXPECTED));
            assert_eq!(actual.copyright_year, Some(EXPECTED));
        }

        #[test]
        fn description_works() {
            const EXPECTED: &str = "description";
            let mut actual = Builder::new();
            actual.description(Some(EXPECTED));
            assert_eq!(actual.description, Some(EXPECTED));
        }

        #[test]
        fn dialog_works() {
            const EXPECTED: &str = "img\\Dialog.bmp";
            let mut actual = Builder::new();
            actual.dialog(Some(EXPECTED));
            assert_eq!(actual.dialog, Some(EXPECTED));
        }

        #[test]
        fn eula_works() {
            const EXPECTED: &str = "eula.rtf";
            let mut actual = Builder::new();
            actual.eula(Some(EXPECTED));
            assert_eq!(actual.eula, Some(EXPECTED));
        }

        #[test]
        fn force_works() {
            let mut actual = Builder::new();
            actual.force(true);
            assert!(actual.force);
        }

        #[test]
        fn help_url_works() {
            const EXPECTED: &str = "http://github.com/volks73/cargo-wix";
            let mut actual = Builder::new();
            actual.help_url(Some(EXPECTED));
            assert_eq!(actual.help_url, Some(EXPECTED));
        }

        #[test]
        fn input_works() {
            const EXPECTED: &str = "input.wxs";
            let mut actual = Builder::new();
            actual.input(Some(EXPECTED));
            assert_eq!(actual.input, Some(EXPECTED));
        }

        #[test]
        fn license_works() {
            const EXPECTED: &str = "License.txt";
            let mut actual = Builder::new();
            actual.license(Some(EXPECTED));
            assert_eq!(actual.license, Some(EXPECTED));
        }

        #[test]
        fn manufacturer_works() {
            const EXPECTED: &str = "manufacturer";
            let mut actual = Builder::new();
            actual.manufacturer(Some(EXPECTED));
            assert_eq!(actual.manufacturer, Some(EXPECTED));
        }

        #[test]
        fn output_works() {
            const EXPECTED: &str = "output";
            let mut actual = Builder::new();
            actual.output(Some(EXPECTED));
            assert_eq!(actual.output, Some(EXPECTED));
        }

        #[test]
        fn path_guid_works() {
            let mut actual = Builder::new();
            actual.path_guid(Some(PATH_GUID));
            assert_eq!(actual.path_guid, Some(PATH_GUID));
        }

        #[test]
        fn product_icon_works() {
            const EXPECTED: &str = "img\\Product.ico";
            let mut actual = Builder::new();
            actual.product_icon(Some(EXPECTED));
            assert_eq!(actual.product_icon, Some(EXPECTED));
        }

        #[test]
        fn product_name_works() {
            const EXPECTED: &str = "product name";
            let mut actual = Builder::new();
            actual.product_name(Some(EXPECTED));
            assert_eq!(actual.product_name, Some(EXPECTED));
        }

        #[test]
        fn upgrade_guid_works() {
            let mut actual = Builder::new();
            actual.upgrade_guid(Some(UPGRADE_GUID));
            assert_eq!(actual.upgrade_guid, Some(UPGRADE_GUID));
        }

        #[test]
        fn build_with_defaults_works() {
            let mut b = Builder::new();
            let default_execution = b.build();
            assert!(default_execution.binaries.is_none());
            assert!(default_execution.copyright_year.is_none());
            assert!(default_execution.copyright_holder.is_none());
            assert!(default_execution.description.is_none());
            assert!(default_execution.eula.is_none());
            assert!(!default_execution.force);
            assert!(default_execution.help_url.is_none());
            assert!(default_execution.input.is_none());
            assert!(default_execution.license.is_none());
            assert!(default_execution.manufacturer.is_none());
            assert!(default_execution.output.is_none());
            assert!(default_execution.product_icon.is_none());
            assert!(default_execution.product_name.is_none());
            assert!(default_execution.upgrade_guid.is_none());
        }

        #[test]
        fn build_with_all_works() {
            const EXPECTED_BINARY: &str = "bin\\Example.exe";
            const EXPECTED_COPYRIGHT_HOLDER: &str = "Copyright Holder";
            const EXPECTED_COPYRIGHT_YEAR: &str = "Copyright Year";
            const EXPECTED_DESCRIPTION: &str = "Description";
            const EXPECTED_EULA: &str = "C:\\tmp\\eula.rtf";
            const EXPECTED_URL: &str = "http://github.com/volks73/cargo-wix";
            const EXPECTED_INPUT: &str = "C:\\tmp\\hello_world";
            const EXPECTED_LICENSE: &str = "C:\\tmp\\hello_world\\License.rtf";
            const EXPECTED_MANUFACTURER: &str = "Manufacturer";
            const EXPECTED_OUTPUT: &str = "C:\\tmp\\output";
            const EXPECTED_PRODUCT_ICON: &str = "img\\Product.ico";
            const EXPECTED_PRODUCT_NAME: &str = "Product Name";
            let mut b = Builder::new();
            b.binaries(Some(vec![EXPECTED_BINARY]));
            b.copyright_holder(Some(EXPECTED_COPYRIGHT_HOLDER));
            b.copyright_year(Some(EXPECTED_COPYRIGHT_YEAR));
            b.description(Some(EXPECTED_DESCRIPTION));
            b.eula(Some(EXPECTED_EULA));
            b.force(true);
            b.help_url(Some(EXPECTED_URL));
            b.input(Some(EXPECTED_INPUT));
            b.license(Some(EXPECTED_LICENSE));
            b.manufacturer(Some(EXPECTED_MANUFACTURER));
            b.output(Some(EXPECTED_OUTPUT));
            b.path_guid(Some(PATH_GUID));
            b.product_icon(Some(EXPECTED_PRODUCT_ICON));
            b.product_name(Some(EXPECTED_PRODUCT_NAME));
            b.upgrade_guid(Some(UPGRADE_GUID));
            let execution = b.build();
            assert_eq!(
                execution.binaries,
                Some(vec![EXPECTED_BINARY])
                    .map(|s| s.into_iter().map(StoredPathBuf::from).collect())
            );
            assert_eq!(
                execution.copyright_year,
                Some(String::from(EXPECTED_COPYRIGHT_YEAR))
            );
            assert_eq!(
                execution.copyright_holder,
                Some(String::from(EXPECTED_COPYRIGHT_HOLDER))
            );
            assert_eq!(
                execution.description,
                Some(String::from(EXPECTED_DESCRIPTION))
            );
            assert_eq!(execution.eula, Some(StoredPathBuf::from(EXPECTED_EULA)));
            assert!(execution.force);
            assert_eq!(execution.help_url, Some(String::from(EXPECTED_URL)));
            assert_eq!(execution.input, Some(PathBuf::from(EXPECTED_INPUT)));
            assert_eq!(
                execution.license,
                Some(StoredPathBuf::from(EXPECTED_LICENSE))
            );
            assert_eq!(
                execution.manufacturer,
                Some(String::from(EXPECTED_MANUFACTURER))
            );
            assert_eq!(execution.output, Some(PathBuf::from(EXPECTED_OUTPUT)));
            assert_eq!(execution.path_guid, Some(String::from(PATH_GUID)));
            assert_eq!(
                execution.product_icon,
                Some(StoredPathBuf::from(EXPECTED_PRODUCT_ICON))
            );
            assert_eq!(
                execution.product_name,
                Some(String::from(EXPECTED_PRODUCT_NAME))
            );
            assert_eq!(execution.upgrade_guid, Some(String::from(UPGRADE_GUID)));
        }
    }

    #[cfg(windows)]
    mod execution {
        extern crate assert_fs;

        use super::*;
        use serial_test::serial;
        use std::env;

        const MIN_PACKAGE: &str = r#"[package]
        name = "cargowixtest"
        version = "1.0.0"
        "#;

        #[test]
        #[serial]
        fn destination_is_correct_with_defaults() {
            let original = env::current_dir().unwrap();
            let temp_dir = crate::tests::setup_project(MIN_PACKAGE);
            env::set_current_dir(temp_dir.path()).unwrap();
            let mut expected = env::current_dir().unwrap();
            expected.push(WIX);
            let e = Execution::default();

            let result = crate::manifest(None)
                .and_then(|manifest| crate::package(&manifest, None))
                .map(|package| e.destination(&package));

            env::set_current_dir(original).unwrap();
            let actual = result.unwrap();
            assert_eq!(actual, expected);
        }

        #[test]
        #[serial]
        fn destination_is_correct_with_output() {
            let expected = PathBuf::from("output");
            let temp_dir = crate::tests::setup_project(MIN_PACKAGE);
            let e = Execution {
                output: Some(expected.clone()),
                ..Default::default()
            };
            let actual = crate::manifest(Some(&temp_dir.path().join("Cargo.toml")))
                .and_then(|manifest| crate::package(&manifest, None))
                .map(|package| e.destination(&package))
                .unwrap();

            assert_eq!(actual, expected);
        }
    }
}