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