]>
Commit | Line | Data |
---|---|---|
8cce8996 | 1 | #![feature(phase, macro_rules)] |
3512d997 | 2 | #![deny(unused)] |
92b449b6 | 3 | |
3a15f5b7 | 4 | extern crate serialize; |
ba273af5 | 5 | #[phase(plugin, link)] extern crate log; |
fc982531 | 6 | |
ba273af5 | 7 | extern crate cargo; |
3a15f5b7 | 8 | |
a3f6a404 | 9 | use std::collections::TreeSet; |
739b1309 | 10 | use std::os; |
a3f6a404 | 11 | use std::io; |
a09ad635 | 12 | use std::io::fs::{mod, PathExtensions}; |
6a809efb | 13 | use std::io::process::{Command,InheritFd,ExitStatus,ExitSignal}; |
ba273af5 AC |
14 | |
15 | use cargo::{execute_main_without_stdin, handle_error, shell}; | |
19bea0ad | 16 | use cargo::core::MultiShell; |
8cce8996 | 17 | use cargo::util::{CliError, CliResult}; |
3a15f5b7 | 18 | |
3512d997 AC |
19 | #[deriving(Decodable)] |
20 | struct Flags { | |
21 | flag_list: bool, | |
22 | flag_verbose: bool, | |
23 | arg_command: String, | |
24 | arg_args: Vec<String>, | |
3a15f5b7 YK |
25 | } |
26 | ||
3512d997 | 27 | const USAGE: &'static str = " |
ba273af5 AC |
28 | Rust's package manager |
29 | ||
30 | Usage: | |
31 | cargo <command> [<args>...] | |
fff5de37 | 32 | cargo [options] |
ba273af5 AC |
33 | |
34 | Options: | |
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 | ||
40 | Some 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 | |
50 | See 'cargo help <command>' for more information on a specific command. | |
3512d997 AC |
51 | "; |
52 | ||
53 | fn main() { | |
54 | execute_main_without_stdin(execute, true, USAGE) | |
55 | } | |
3a15f5b7 | 56 | |
8cce8996 AC |
57 | macro_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 | 89 | fn 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 |
136 | fn 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. | |
169 | fn 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 | ||
200 | fn 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. | |
209 | fn 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. | |
217 | fn 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 | } |