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