1 //! The `cargo lock` subcommand
2 
3 #![forbid(unsafe_code)]
4 #![warn(rust_2018_idioms, unused_qualifications)]
5 
6 use cargo_lock::{package, Dependency, Lockfile, ResolveVersion};
7 use gumdrop::Options;
8 use std::{
9     env, fs, io,
10     path::{Path, PathBuf},
11     process::exit,
12 };
13 
14 #[cfg(feature = "dependency-tree")]
15 use cargo_lock::dependency::graph::EdgeDirection;
16 
17 /// Wrapper toplevel command for the `cargo lock` subcommand
18 #[derive(Options)]
19 enum CargoLock {
20     #[options(help = "the `cargo lock` Cargo subcommand")]
21     Lock(Command),
22 }
23 
24 /// `cargo lock` subcommands
25 #[derive(Debug, Options)]
26 enum Command {
27     /// The `cargo lock list` subcommand
28     #[options(help = "list packages in Cargo.toml")]
29     List(ListCmd),
30 
31     /// The `cargo lock translate` subcommand
32     #[options(help = "translate a Cargo.toml file")]
33     Translate(TranslateCmd),
34 
35     /// The `cargo lock tree` subcommand
36     #[cfg(feature = "dependency-tree")]
37     #[options(help = "print a dependency tree for the given dependency")]
38     Tree(TreeCmd),
39 }
40 
41 /// The `cargo lock list` subcommand
42 #[derive(Debug, Options)]
43 struct ListCmd {
44     /// Input `Cargo.lock` file
45     #[options(short = "f", help = "input Cargo.lock file to translate")]
46     file: Option<PathBuf>,
47 }
48 
49 impl ListCmd {
50     /// Display dependency summary from `Cargo.lock`
run(&self)51     pub fn run(&self) {
52         for package in &load_lockfile(&self.file).packages {
53             println!("- {}", Dependency::from(package));
54         }
55     }
56 }
57 
58 /// The `cargo lock translate` subcommand
59 #[derive(Debug, Options)]
60 struct TranslateCmd {
61     /// Input `Cargo.lock` file
62     #[options(short = "f", help = "input Cargo.lock file to translate")]
63     file: Option<PathBuf>,
64 
65     /// Output `Cargo.lock` file
66     #[options(short = "o", help = "output Cargo.lock file (default STDOUT)")]
67     output: Option<PathBuf>,
68 
69     /// Cargo.lock format version to translate to
70     #[options(short = "v", help = "Cargo.lock resolve version to output")]
71     version: Option<ResolveVersion>,
72 }
73 
74 impl TranslateCmd {
75     /// Translate `Cargo.lock` to a different format version
run(&self)76     pub fn run(&self) {
77         let output = self
78             .output
79             .as_ref()
80             .map(AsRef::as_ref)
81             .unwrap_or_else(|| Path::new("-"));
82 
83         let mut lockfile = load_lockfile(&self.file);
84 
85         lockfile.version = self.version.unwrap_or_default();
86         let lockfile_toml = lockfile.to_string();
87 
88         if output == Path::new("-") {
89             println!("{}", &lockfile_toml);
90         } else {
91             fs::write(output, lockfile_toml.as_bytes()).unwrap_or_else(|e| {
92                 eprintln!("*** error: {}", e);
93                 exit(1);
94             });
95         }
96     }
97 }
98 
99 /// The `cargo lock tree` subcommand
100 #[cfg(feature = "dependency-tree")]
101 #[derive(Debug, Options)]
102 struct TreeCmd {
103     /// Input `Cargo.lock` file
104     #[options(short = "f", help = "input Cargo.lock file to translate")]
105     file: Option<PathBuf>,
106 
107     /// Dependencies names to draw a tree for
108     #[options(free, help = "dependency names to draw trees for")]
109     dependencies: Vec<package::Name>,
110 }
111 
112 #[cfg(feature = "dependency-tree")]
113 impl TreeCmd {
114     /// Display dependency trees from `Cargo.lock`
run(&self)115     pub fn run(&self) {
116         let lockfile = load_lockfile(&self.file);
117 
118         let tree = lockfile.dependency_tree().unwrap_or_else(|e| {
119             eprintln!("*** error: {}", e);
120             exit(1);
121         });
122 
123         // TODO(tarcieri): detect root package(s), automatically use those?
124         if self.dependencies.is_empty() {
125             eprintln!("*** error: no dependency names given");
126             exit(1);
127         }
128 
129         for (i, dep) in self.dependencies.iter().enumerate() {
130             if i > 0 {
131                 println!();
132             }
133 
134             let package = lockfile
135                 .packages
136                 .iter()
137                 .find(|pkg| pkg.name == *dep)
138                 .unwrap_or_else(|| {
139                     eprintln!("*** error: invalid dependency name: `{}`", dep);
140                     exit(1);
141                 });
142 
143             let index = tree.nodes()[&package.into()];
144             tree.render(&mut io::stdout(), index, EdgeDirection::Incoming)
145                 .unwrap();
146         }
147     }
148 }
149 
150 /// Load a lockfile from the given path (or `Cargo.toml`)
load_lockfile(path: &Option<PathBuf>) -> Lockfile151 fn load_lockfile(path: &Option<PathBuf>) -> Lockfile {
152     let path = path
153         .as_ref()
154         .map(AsRef::as_ref)
155         .unwrap_or_else(|| Path::new("Cargo.lock"));
156 
157     Lockfile::load(path).unwrap_or_else(|e| {
158         eprintln!("*** error: {}", e);
159         exit(1);
160     })
161 }
162 
main()163 fn main() {
164     let args = env::args().collect::<Vec<_>>();
165 
166     let CargoLock::Lock(cmd) = CargoLock::parse_args_default(&args[1..]).unwrap_or_else(|e| {
167         eprintln!("*** error: {}", e);
168         eprintln!("USAGE:");
169         eprintln!("{}", Command::usage());
170         exit(1);
171     });
172 
173     match cmd {
174         Command::List(list) => list.run(),
175         Command::Translate(translate) => translate.run(),
176         #[cfg(feature = "dependency-tree")]
177         Command::Tree(tree) => tree.run(),
178     }
179 }
180