]> git.proxmox.com Git - cargo.git/blame - src/bin/cargo.rs
Auto merge of #1278 - alexcrichton:style, r=brson
[cargo.git] / src / bin / cargo.rs
CommitLineData
ee5e24ff 1#![feature(collections, core, io, path, env)]
92b449b6 2
bd70721d 3extern crate "rustc-serialize" as rustc_serialize;
55321111 4extern crate cargo;
98854f6f 5extern crate env_logger;
ee5e24ff 6#[macro_use] extern crate log;
3a15f5b7 7
8ab8b58b 8use std::collections::BTreeSet;
ee5e24ff 9use std::env;
af45e088
GSF
10use std::old_io::fs::{self, PathExtensions};
11use std::old_io::process::{Command,InheritFd,ExitStatus,ExitSignal};
ee5e24ff 12use std::old_io;
ba273af5
AC
13
14use cargo::{execute_main_without_stdin, handle_error, shell};
19bea0ad 15use cargo::core::MultiShell;
5d0cb3f2 16use cargo::util::{CliError, CliResult, lev_distance, Config};
3a15f5b7 17
ba280047 18#[derive(RustcDecodable)]
3512d997
AC
19struct Flags {
20 flag_list: bool,
21 flag_verbose: bool,
22 arg_command: String,
23 arg_args: Vec<String>,
3a15f5b7
YK
24}
25
3512d997 26const USAGE: &'static str = "
ba273af5
AC
27Rust's package manager
28
29Usage:
30 cargo <command> [<args>...]
fff5de37 31 cargo [options]
ba273af5
AC
32
33Options:
34 -h, --help Display this message
35 -V, --version Print version info and exit
a3f6a404 36 --list List installed commands
ba273af5
AC
37 -v, --verbose Use verbose output
38
39Some common cargo commands are:
40 build Compile the current project
41 clean Remove the target directory
42 doc Build this project's and its dependencies' documentation
43 new Create a new cargo project
44 run Build and execute src/main.rs
45 test Run the tests
92e40c43 46 bench Run the benchmarks
620b2fe1 47 update Update dependencies listed in Cargo.lock
ba273af5
AC
48
49See 'cargo help <command>' for more information on a specific command.
3512d997
AC
50";
51
52fn main() {
98854f6f 53 env_logger::init().unwrap();
3512d997
AC
54 execute_main_without_stdin(execute, true, USAGE)
55}
3a15f5b7 56
55321111
AC
57macro_rules! each_subcommand{ ($mac:ident) => ({
58 $mac!(bench);
59 $mac!(build);
60 $mac!(clean);
55321111
AC
61 $mac!(doc);
62 $mac!(fetch);
63 $mac!(generate_lockfile);
64 $mac!(git_checkout);
65 $mac!(help);
66 $mac!(locate_project);
67 $mac!(login);
68 $mac!(new);
69 $mac!(owner);
70 $mac!(package);
71 $mac!(pkgid);
72 $mac!(publish);
73 $mac!(read_manifest);
74 $mac!(run);
75 $mac!(search);
76 $mac!(test);
77 $mac!(update);
78 $mac!(verify_project);
79 $mac!(version);
80 $mac!(yank);
157d639a 81}) }
8cce8996 82
739b1309
YK
83/**
84 The top-level `cargo` command handles configuration and project location
85 because they are fundamental (and intertwined). Other commands can rely
86 on this top-level information.
87*/
5d0cb3f2 88fn execute(flags: Flags, config: &Config) -> CliResult<Option<()>> {
5d0cb3f2 89 config.shell().set_verbose(flags.flag_verbose);
8cce8996 90
a3f6a404 91 if flags.flag_list {
92 println!("Installed Commands:");
03e3dba1 93 for command in list_commands().into_iter() {
a3f6a404 94 println!(" {}", command);
a3f6a404 95 };
96 return Ok(None)
97 }
8cce8996 98
25e537aa 99 let (mut args, command) = match &flags.arg_command[] {
6968611f 100 "" | "help" if flags.arg_args.len() == 0 => {
5d0cb3f2 101 config.shell().set_verbose(true);
ee5e24ff 102 let args = &["foo".to_string(), "-h".to_string()];
5d0cb3f2 103 let r = cargo::call_main_without_stdin(execute, config, USAGE, args,
e602263a 104 false);
5d0cb3f2 105 cargo::process_executed(r, &mut **config.shell());
8cce8996 106 return Ok(None)
a3f6a404 107 }
25e537aa
AC
108 "help" if flags.arg_args[0] == "-h" ||
109 flags.arg_args[0] == "--help" =>
8a3706ac 110 (flags.arg_args, "help"),
25e537aa 111 "help" => (vec!["-h".to_string()], &flags.arg_args[0][]),
8cce8996
AC
112 s => (flags.arg_args.clone(), s),
113 };
114 args.insert(0, command.to_string());
ee5e24ff 115 args.insert(0, "foo".to_string());
8cce8996 116
157d639a 117 macro_rules! cmd{ ($name:ident) => (
25e537aa 118 if command == stringify!($name).replace("_", "-") {
8cce8996 119 mod $name;
5d0cb3f2
AC
120 config.shell().set_verbose(true);
121 let r = cargo::call_main_without_stdin($name::execute, config,
3512d997 122 $name::USAGE,
25e537aa 123 &args,
8cce8996 124 false);
5d0cb3f2 125 cargo::process_executed(r, &mut **config.shell());
8cce8996
AC
126 return Ok(None)
127 }
157d639a
AC
128 ) }
129 each_subcommand!(cmd);
8cce8996 130
25e537aa 131 execute_subcommand(&command, &args, &mut config.shell());
a3f6a404 132 Ok(None)
133}
134
12f5de8e
PW
135fn find_closest(cmd: &str) -> Option<String> {
136 match list_commands().iter()
ee5e24ff
AC
137 // doing it this way (instead of just .min_by(|c|
138 // c.lev_distance(cmd))) allows us to only make
139 // suggestions that have an edit distance of
12f5de8e 140 // 3 or less
25e537aa 141 .map(|c| (lev_distance(&c, cmd), c))
55321111 142 .filter(|&(d, _): &(usize, &String)| d < 4)
12f5de8e
PW
143 .min_by(|&(d, _)| d) {
144 Some((_, c)) => {
145 Some(c.to_string())
146 },
147 None => None
148 }
149}
150
8cce8996
AC
151fn execute_subcommand(cmd: &str, args: &[String], shell: &mut MultiShell) {
152 let command = match find_command(cmd) {
153 Some(command) => command,
12f5de8e
PW
154 None => {
155 let msg = match find_closest(cmd) {
5d0cb3f2
AC
156 Some(closest) => format!("No such subcommand\n\n\t\
157 Did you mean `{}`?\n", closest),
12f5de8e
PW
158 None => "No such subcommand".to_string()
159 };
25e537aa 160 return handle_error(CliError::new(&msg, 127), shell)
12f5de8e 161 }
8cce8996
AC
162 };
163 let status = Command::new(command)
164 .args(args)
165 .stdin(InheritFd(0))
166 .stdout(InheritFd(1))
167 .stderr(InheritFd(2))
168 .status();
169
170 match status {
171 Ok(ExitStatus(0)) => (),
172 Ok(ExitStatus(i)) => {
ee5e24ff 173 handle_error(CliError::new("", i as i32), shell)
8cce8996
AC
174 }
175 Ok(ExitSignal(i)) => {
176 let msg = format!("subcommand failed with signal: {}", i);
25e537aa 177 handle_error(CliError::new(&msg, i as i32), shell)
8cce8996 178 }
af45e088 179 Err(old_io::IoError{kind, ..}) if kind == old_io::FileNotFound =>
8cce8996
AC
180 handle_error(CliError::new("No such subcommand", 127), shell),
181 Err(err) => handle_error(
182 CliError::new(
25e537aa 183 &format!("Subcommand failed to run: {}", err), 127),
8cce8996 184 shell)
a3f6a404 185 }
186}
187
188/// List all runnable commands. find_command should always succeed
189/// if given one of returned command.
8ab8b58b 190fn list_commands() -> BTreeSet<String> {
a3f6a404 191 let command_prefix = "cargo-";
8ab8b58b 192 let mut commands = BTreeSet::new();
a3f6a404 193 for dir in list_command_directory().iter() {
194 let entries = match fs::readdir(dir) {
195 Ok(entries) => entries,
196 _ => continue
197 };
198 for entry in entries.iter() {
199 let filename = match entry.filename_str() {
200 Some(filename) => filename,
201 _ => continue
202 };
203 if filename.starts_with(command_prefix) &&
ee5e24ff 204 filename.ends_with(env::consts::EXE_SUFFIX) &&
a3f6a404 205 is_executable(entry) {
d73a110d
AC
206 let command = &filename[
207 command_prefix.len()..
ee5e24ff 208 filename.len() - env::consts::EXE_SUFFIX.len()];
a3f6a404 209 commands.insert(String::from_str(command));
64ff29ff 210 }
6a809efb 211 }
92b449b6 212 }
8cce8996 213
157d639a 214 macro_rules! add_cmd{ ($cmd:ident) => ({
8cce8996 215 commands.insert(stringify!($cmd).replace("_", "-"));
157d639a 216 }) }
8cce8996 217 each_subcommand!(add_cmd);
a3f6a404 218 commands
219}
220
221fn is_executable(path: &Path) -> bool {
222 match fs::stat(path) {
af45e088
GSF
223 Ok(old_io::FileStat{ kind: old_io::FileType::RegularFile, perm, ..}) =>
224 perm.contains(old_io::OTHER_EXECUTE),
a3f6a404 225 _ => false
226 }
227}
228
229/// Get `Command` to run given command.
230fn find_command(cmd: &str) -> Option<Path> {
ee5e24ff 231 let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX);
a3f6a404 232 let dirs = list_command_directory();
25e537aa 233 let mut command_paths = dirs.iter().map(|dir| dir.join(&command_exe));
a3f6a404 234 command_paths.find(|path| path.exists())
235}
236
237/// List candidate locations where subcommands might be installed.
238fn list_command_directory() -> Vec<Path> {
239 let mut dirs = vec![];
ee5e24ff
AC
240 if let Ok(mut path) = env::current_exe() {
241 path.pop();
242 dirs.push(path.join("../lib/cargo"));
243 dirs.push(path);
244 }
245 if let Some(val) = env::var("PATH") {
246 dirs.extend(env::split_paths(&val));
247 }
a3f6a404 248 dirs
3a15f5b7 249}