]> git.lizzy.rs Git - rust.git/blob - src/librustpkg/lib.rs
Register new snapshots
[rust.git] / src / librustpkg / lib.rs
1 // Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // rustpkg - a package manager and build system for Rust
12
13 #[link(name = "rustpkg",
14        package_id = "rustpkg",
15        vers = "0.9-pre",
16        uuid = "25de5e6e-279e-4a20-845c-4cabae92daaf",
17        url = "https://github.com/mozilla/rust/tree/master/src/librustpkg")];
18
19 #[license = "MIT/ASL2"];
20 #[crate_type = "dylib"];
21
22 #[feature(globs, managed_boxes)];
23
24 extern mod extra;
25 extern mod rustc;
26 extern mod syntax;
27
28 use std::{os, result, run, str, task};
29 use std::io::process;
30 use std::hashmap::HashSet;
31 use std::io;
32 use std::io::fs;
33 pub use std::path::Path;
34
35 use extra::workcache;
36 use rustc::driver::{driver, session};
37 use rustc::metadata::filesearch;
38 use rustc::metadata::filesearch::rust_path;
39 use extra::{getopts};
40 use syntax::{ast, diagnostic};
41 use messages::{error, warn, note};
42 use path_util::{build_pkg_id_in_workspace, built_test_in_workspace};
43 use path_util::in_rust_path;
44 use path_util::{built_executable_in_workspace, built_library_in_workspace, default_workspace};
45 use path_util::{target_executable_in_workspace, target_library_in_workspace, dir_has_crate_file};
46 use source_control::{CheckedOutSources, is_git_dir, make_read_only};
47 use workspace::{each_pkg_parent_workspace, pkg_parent_workspaces, cwd_to_workspace};
48 use workspace::determine_destination;
49 use context::{Context, BuildContext,
50                        RustcFlags, Trans, Link, Nothing, Pretty, Analysis, Assemble,
51                        LLVMAssemble, LLVMCompileBitcode};
52 use package_id::PkgId;
53 use package_source::PkgSrc;
54 use target::{WhatToBuild, Everything, is_lib, is_main, is_test, is_bench};
55 use target::{Tests, MaybeCustom, Inferred, JustOne};
56 use workcache_support::digest_only_date;
57 use exit_codes::{COPY_FAILED_CODE, BAD_FLAG_CODE};
58
59 pub mod api;
60 mod conditions;
61 pub mod context;
62 mod crate;
63 pub mod exit_codes;
64 mod installed_packages;
65 mod messages;
66 mod package_id;
67 mod package_source;
68 mod path_util;
69 mod search;
70 mod sha1;
71 mod source_control;
72 mod target;
73 #[cfg(not(windows), test)] // FIXME test failure on windows: #10471
74 mod tests;
75 mod util;
76 pub mod version;
77 pub mod workcache_support;
78 mod workspace;
79
80 pub mod usage;
81
82 /// A PkgScript represents user-supplied custom logic for
83 /// special build hooks. This only exists for packages with
84 /// an explicit package script.
85 struct PkgScript<'self> {
86     /// Uniquely identifies this package
87     id: &'self PkgId,
88     /// File path for the package script
89     input: Path,
90     /// The session to use *only* for compiling the custom
91     /// build script
92     sess: session::Session,
93     /// The config for compiling the custom build script
94     cfg: ast::CrateConfig,
95     /// The crate for the custom build script
96     crate: Option<ast::Crate>,
97     /// Directory in which to store build output
98     build_dir: Path
99 }
100
101 impl<'self> PkgScript<'self> {
102     /// Given the path name for a package script
103     /// and a package ID, parse the package script into
104     /// a PkgScript that we can then execute
105     fn parse<'a>(sysroot: Path,
106                  script: Path,
107                  workspace: &Path,
108                  id: &'a PkgId) -> PkgScript<'a> {
109         // Get the executable name that was invoked
110         let binary = os::args()[0].to_managed();
111         // Build the rustc session data structures to pass
112         // to the compiler
113         debug!("pkgscript parse: {}", sysroot.display());
114         let options = @session::options {
115             binary: binary,
116             maybe_sysroot: Some(@sysroot),
117             outputs: ~[session::OutputExecutable],
118             .. (*session::basic_options()).clone()
119         };
120         let input = driver::file_input(script.clone());
121         let sess = driver::build_session(options,
122                                          @diagnostic::DefaultEmitter as
123                                             @diagnostic::Emitter);
124         let cfg = driver::build_configuration(sess);
125         let crate = driver::phase_1_parse_input(sess, cfg.clone(), &input);
126         let crate = driver::phase_2_configure_and_expand(sess, cfg.clone(), crate);
127         let work_dir = build_pkg_id_in_workspace(id, workspace);
128
129         debug!("Returning package script with id {}", id.to_str());
130
131         PkgScript {
132             id: id,
133             input: script,
134             sess: sess,
135             cfg: cfg,
136             crate: Some(crate),
137             build_dir: work_dir
138         }
139     }
140
141     fn build_custom(&mut self, exec: &mut workcache::Exec) -> ~str {
142         let sess = self.sess;
143
144         debug!("Working directory = {}", self.build_dir.display());
145         // Collect together any user-defined commands in the package script
146         let crate = util::ready_crate(sess, self.crate.take_unwrap());
147         debug!("Building output filenames with script name {}",
148                driver::source_name(&driver::file_input(self.input.clone())));
149         let exe = self.build_dir.join("pkg" + util::exe_suffix());
150         util::compile_crate_from_input(&self.input,
151                                        exec,
152                                        Nothing,
153                                        &self.build_dir,
154                                        sess,
155                                        crate);
156         // Discover the output
157         // FIXME (#9639): This needs to handle non-utf8 paths
158         // Discover the output
159         exec.discover_output("binary", exe.as_str().unwrap().to_owned(), digest_only_date(&exe));
160         exe.as_str().unwrap().to_owned()
161     }
162
163
164     /// Run the contents of this package script, where <what>
165     /// is the command to pass to it (e.g., "build", "clean", "install")
166     /// Returns a pair of an exit code and list of configs (obtained by
167     /// calling the package script's configs() function if it exists
168     fn run_custom(exe: &Path, sysroot: &Path) -> (~[~str], process::ProcessExit) {
169         debug!("Running program: {} {} {}", exe.as_str().unwrap().to_owned(),
170                sysroot.display(), "install");
171         // FIXME #7401 should support commands besides `install`
172         // FIXME (#9639): This needs to handle non-utf8 paths
173         let status = run::process_status(exe.as_str().unwrap(),
174                                          [sysroot.as_str().unwrap().to_owned(), ~"install"]);
175         if !status.success() {
176             debug!("run_custom: first pkg command failed with {:?}", status);
177             (~[], status)
178         }
179         else {
180             debug!("Running program (configs): {} {} {}",
181                    exe.display(), sysroot.display(), "configs");
182             // FIXME (#9639): This needs to handle non-utf8 paths
183             let output = run::process_output(exe.as_str().unwrap(),
184                                              [sysroot.as_str().unwrap().to_owned(), ~"configs"]);
185             debug!("run_custom: second pkg command did {:?}", output.status);
186             // Run the configs() function to get the configs
187             let cfgs = str::from_utf8_slice(output.output).words()
188                 .map(|w| w.to_owned()).collect();
189             (cfgs, output.status)
190         }
191     }
192
193     fn hash(&self) -> ~str {
194         self.id.hash()
195     }
196 }
197
198 pub trait CtxMethods {
199     fn run(&self, cmd: &str, args: ~[~str]);
200     fn do_cmd(&self, _cmd: &str, _pkgname: &str);
201     /// Returns a pair of the selected package ID, and the destination workspace
202     fn build_args(&self, args: ~[~str], what: &WhatToBuild) -> Option<(PkgId, Path)>;
203     /// Returns the destination workspace
204     fn build(&self, pkg_src: &mut PkgSrc, what: &WhatToBuild);
205     fn clean(&self, workspace: &Path, id: &PkgId);
206     fn info(&self);
207     /// Returns a pair. First component is a list of installed paths,
208     /// second is a list of declared and discovered inputs
209     fn install(&self, src: PkgSrc, what: &WhatToBuild) -> (~[Path], ~[(~str, ~str)]);
210     /// Returns a list of installed files
211     fn install_no_build(&self,
212                         build_workspace: &Path,
213                         build_inputs: &[Path],
214                         target_workspace: &Path,
215                         id: &PkgId) -> ~[~str];
216     fn prefer(&self, _id: &str, _vers: Option<~str>);
217     fn test(&self, id: &PkgId, workspace: &Path);
218     fn uninstall(&self, _id: &str, _vers: Option<~str>);
219     fn unprefer(&self, _id: &str, _vers: Option<~str>);
220     fn init(&self);
221 }
222
223 impl CtxMethods for BuildContext {
224     fn build_args(&self, args: ~[~str], what: &WhatToBuild) -> Option<(PkgId, Path)> {
225         let cwd = os::getcwd();
226
227         if args.len() < 1 {
228             match cwd_to_workspace() {
229                 None  if dir_has_crate_file(&cwd) => {
230                     // FIXME (#9639): This needs to handle non-utf8 paths
231                     let pkgid = PkgId::new(cwd.filename_str().unwrap());
232                     let mut pkg_src = PkgSrc::new(cwd, default_workspace(), true, pkgid);
233                     self.build(&mut pkg_src, what);
234                     match pkg_src {
235                         PkgSrc { destination_workspace: ws,
236                                  id: id, .. } => {
237                             Some((id, ws))
238                         }
239                     }
240                 }
241                 None => { usage::build(); None }
242                 Some((ws, pkgid)) => {
243                     let mut pkg_src = PkgSrc::new(ws.clone(), ws, false, pkgid);
244                     self.build(&mut pkg_src, what);
245                     match pkg_src {
246                         PkgSrc { destination_workspace: ws,
247                                  id: id, .. } => {
248                             Some((id, ws))
249                         }
250                     }
251                 }
252             }
253         } else {
254             // The package id is presumed to be the first command-line
255             // argument
256             let pkgid = PkgId::new(args[0].clone());
257             let mut dest_ws = default_workspace();
258             each_pkg_parent_workspace(&self.context, &pkgid, |workspace| {
259                 debug!("found pkg {} in workspace {}, trying to build",
260                        pkgid.to_str(), workspace.display());
261                 dest_ws = determine_destination(os::getcwd(),
262                                                 self.context.use_rust_path_hack,
263                                                 workspace);
264                 let mut pkg_src = PkgSrc::new(workspace.clone(), dest_ws.clone(),
265                                               false, pkgid.clone());
266                 self.build(&mut pkg_src, what);
267                 true
268             });
269             // n.b. If this builds multiple packages, it only returns the workspace for
270             // the last one. The whole building-multiple-packages-with-the-same-ID is weird
271             // anyway and there are no tests for it, so maybe take it out
272             Some((pkgid, dest_ws))
273         }
274     }
275     fn run(&self, cmd: &str, args: ~[~str]) {
276         let cwd = os::getcwd();
277         match cmd {
278             "build" => {
279                 self.build_args(args, &WhatToBuild::new(MaybeCustom, Everything));
280             }
281             "clean" => {
282                 if args.len() < 1 {
283                     match cwd_to_workspace() {
284                         None => { usage::clean(); return }
285                         // tjc: Maybe clean should clean all the packages in the
286                         // current workspace, though?
287                         Some((ws, pkgid)) => self.clean(&ws, &pkgid)
288                     }
289
290                 }
291                 else {
292                     // The package id is presumed to be the first command-line
293                     // argument
294                     let pkgid = PkgId::new(args[0].clone());
295                     self.clean(&cwd, &pkgid); // tjc: should use workspace, not cwd
296                 }
297             }
298             "do" => {
299                 if args.len() < 2 {
300                     return usage::do_cmd();
301                 }
302
303                 self.do_cmd(args[0].clone(), args[1].clone());
304             }
305             "info" => {
306                 self.info();
307             }
308             "install" => {
309                if args.len() < 1 {
310                     match cwd_to_workspace() {
311                         None if dir_has_crate_file(&cwd) => {
312                             // FIXME (#9639): This needs to handle non-utf8 paths
313
314                             let inferred_pkgid =
315                                 PkgId::new(cwd.filename_str().unwrap());
316                             self.install(PkgSrc::new(cwd, default_workspace(),
317                                                      true, inferred_pkgid),
318                                          &WhatToBuild::new(MaybeCustom, Everything));
319                         }
320                         None  => { usage::install(); return; }
321                         Some((ws, pkgid))                => {
322                             let pkg_src = PkgSrc::new(ws.clone(), ws.clone(), false, pkgid);
323                             self.install(pkg_src, &WhatToBuild::new(MaybeCustom,
324                                                                     Everything));
325                       }
326                   }
327                 }
328                 else {
329                     // The package id is presumed to be the first command-line
330                     // argument
331                     let pkgid = PkgId::new(args[0]);
332                     let workspaces = pkg_parent_workspaces(&self.context, &pkgid);
333                     debug!("package ID = {}, found it in {:?} workspaces",
334                            pkgid.to_str(), workspaces.len());
335                     if workspaces.is_empty() {
336                         let d = default_workspace();
337                         let src = PkgSrc::new(d.clone(), d, false, pkgid.clone());
338                         self.install(src, &WhatToBuild::new(MaybeCustom, Everything));
339                     }
340                     else {
341                         for workspace in workspaces.iter() {
342                             let dest = determine_destination(os::getcwd(),
343                                                              self.context.use_rust_path_hack,
344                                                              workspace);
345                             let src = PkgSrc::new(workspace.clone(),
346                                                   dest,
347                                                   self.context.use_rust_path_hack,
348                                                   pkgid.clone());
349                             self.install(src, &WhatToBuild::new(MaybeCustom, Everything));
350                         };
351                     }
352                 }
353             }
354             "list" => {
355                 println("Installed packages:");
356                 installed_packages::list_installed_packages(|pkg_id| {
357                     pkg_id.path.display().with_str(|s| println(s));
358                     true
359                 });
360             }
361             "prefer" => {
362                 if args.len() < 1 {
363                     return usage::uninstall();
364                 }
365
366                 self.prefer(args[0], None);
367             }
368             "test" => {
369                 // Build the test executable
370                 let maybe_id_and_workspace = self.build_args(args,
371                                                              &WhatToBuild::new(MaybeCustom, Tests));
372                 match maybe_id_and_workspace {
373                     Some((pkg_id, workspace)) => {
374                         // Assuming it's built, run the tests
375                         self.test(&pkg_id, &workspace);
376                     }
377                     None => {
378                         error("Testing failed because building the specified package failed.");
379                     }
380                 }
381             }
382             "init" => {
383                 if args.len() != 0 {
384                     return usage::init();
385                 } else {
386                     self.init();
387                 }
388             }
389             "uninstall" => {
390                 if args.len() < 1 {
391                     return usage::uninstall();
392                 }
393
394                 let pkgid = PkgId::new(args[0]);
395                 if !installed_packages::package_is_installed(&pkgid) {
396                     warn(format!("Package {} doesn't seem to be installed! \
397                                   Doing nothing.", args[0]));
398                     return;
399                 }
400                 else {
401                     let rp = rust_path();
402                     assert!(!rp.is_empty());
403                     each_pkg_parent_workspace(&self.context, &pkgid, |workspace| {
404                         path_util::uninstall_package_from(workspace, &pkgid);
405                         note(format!("Uninstalled package {} (was installed in {})",
406                                   pkgid.to_str(), workspace.display()));
407                         true
408                     });
409                 }
410             }
411             "unprefer" => {
412                 if args.len() < 1 {
413                     return usage::unprefer();
414                 }
415
416                 self.unprefer(args[0], None);
417             }
418             _ => fail!("I don't know the command `{}`", cmd)
419         }
420     }
421
422     fn do_cmd(&self, _cmd: &str, _pkgname: &str)  {
423         // stub
424         fail!("`do` not yet implemented");
425     }
426
427     fn build(&self, pkg_src: &mut PkgSrc, what_to_build: &WhatToBuild) {
428         use conditions::git_checkout_failed::cond;
429
430         let workspace = pkg_src.source_workspace.clone();
431         let pkgid = pkg_src.id.clone();
432
433         debug!("build: workspace = {} (in Rust path? {:?} is git dir? {:?} \
434                 pkgid = {} pkgsrc start_dir = {}", workspace.display(),
435                in_rust_path(&workspace), is_git_dir(&workspace.join(&pkgid.path)),
436                pkgid.to_str(), pkg_src.start_dir.display());
437         debug!("build: what to build = {:?}", what_to_build);
438
439         // If workspace isn't in the RUST_PATH, and it's a git repo,
440         // then clone it into the first entry in RUST_PATH, and repeat
441         if !in_rust_path(&workspace) && is_git_dir(&workspace.join(&pkgid.path)) {
442             let mut out_dir = default_workspace().join("src");
443             out_dir.push(&pkgid.path);
444             let git_result = source_control::safe_git_clone(&workspace.join(&pkgid.path),
445                                                             &pkgid.version,
446                                                             &out_dir);
447             match git_result {
448                 CheckedOutSources => make_read_only(&out_dir),
449                 // FIXME (#9639): This needs to handle non-utf8 paths
450                 _ => cond.raise((pkgid.path.as_str().unwrap().to_owned(), out_dir.clone()))
451             };
452             let default_ws = default_workspace();
453             debug!("Calling build recursively with {:?} and {:?}", default_ws.display(),
454                    pkgid.to_str());
455             return self.build(&mut PkgSrc::new(default_ws.clone(),
456                                                default_ws,
457                                                false,
458                                                pkgid.clone()), what_to_build);
459         }
460
461         // Is there custom build logic? If so, use it
462         let mut custom = false;
463         debug!("Package source directory = {}", pkg_src.to_str());
464         let opt = pkg_src.package_script_option();
465         debug!("Calling pkg_script_option on {:?}", opt);
466         let cfgs = match (pkg_src.package_script_option(), what_to_build.build_type) {
467             (Some(package_script_path), MaybeCustom)  => {
468                 let sysroot = self.sysroot_to_use();
469                 // Build the package script if needed
470                 let script_build = format!("build_package_script({})",
471                                            package_script_path.display());
472                 let pkg_exe = self.workcache_context.with_prep(script_build, |prep| {
473                     let subsysroot = sysroot.clone();
474                     let psp = package_script_path.clone();
475                     let ws = workspace.clone();
476                     let pid = pkgid.clone();
477                     prep.exec(proc(exec) {
478                         let mut pscript = PkgScript::parse(subsysroot.clone(),
479                                                            psp.clone(),
480                                                            &ws,
481                                                            &pid);
482                         pscript.build_custom(exec)
483                     })
484                 });
485                 // We always *run* the package script
486                 let (cfgs, hook_result) = PkgScript::run_custom(&Path::init(pkg_exe), &sysroot);
487                 debug!("Command return code = {:?}", hook_result);
488                 if !hook_result.success() {
489                     fail!("Error running custom build command")
490                 }
491                 custom = true;
492                 // otherwise, the package script succeeded
493                 cfgs
494             }
495             (Some(_), Inferred) => {
496                 debug!("There is a package script, but we're ignoring it");
497                 ~[]
498             }
499             (None, _) => {
500                 debug!("No package script, continuing");
501                 ~[]
502             }
503         } + self.context.cfgs;
504
505         // If there was a package script, it should have finished
506         // the build already. Otherwise...
507         if !custom {
508             match what_to_build.sources {
509                 // Find crates inside the workspace
510                 Everything => pkg_src.find_crates(),
511                 // Find only tests
512                 Tests => pkg_src.find_crates_with_filter(|s| { is_test(&Path::init(s)) }),
513                 // Don't infer any crates -- just build the one that was requested
514                 JustOne(ref p) => {
515                     // We expect that p is relative to the package source's start directory,
516                     // so check that assumption
517                     debug!("JustOne: p = {}", p.display());
518                     assert!(pkg_src.start_dir.join(p).exists());
519                     if is_lib(p) {
520                         PkgSrc::push_crate(&mut pkg_src.libs, 0, p);
521                     } else if is_main(p) {
522                         PkgSrc::push_crate(&mut pkg_src.mains, 0, p);
523                     } else if is_test(p) {
524                         PkgSrc::push_crate(&mut pkg_src.tests, 0, p);
525                     } else if is_bench(p) {
526                         PkgSrc::push_crate(&mut pkg_src.benchs, 0, p);
527                     } else {
528                         warn(format!("Not building any crates for dependency {}", p.display()));
529                         return;
530                     }
531                 }
532             }
533             // Build it!
534             pkg_src.build(self, cfgs, []);
535         }
536     }
537
538     fn clean(&self, workspace: &Path, id: &PkgId)  {
539         // Could also support a custom build hook in the pkg
540         // script for cleaning files rustpkg doesn't know about.
541         // Do something reasonable for now
542
543         let dir = build_pkg_id_in_workspace(id, workspace);
544         note(format!("Cleaning package {} (removing directory {})",
545                         id.to_str(), dir.display()));
546         if dir.exists() {
547             fs::rmdir_recursive(&dir);
548             note(format!("Removed directory {}", dir.display()));
549         }
550
551         note(format!("Cleaned package {}", id.to_str()));
552     }
553
554     fn info(&self) {
555         // stub
556         fail!("info not yet implemented");
557     }
558
559     fn install(&self, mut pkg_src: PkgSrc, what: &WhatToBuild) -> (~[Path], ~[(~str, ~str)]) {
560
561         let id = pkg_src.id.clone();
562
563         let mut installed_files = ~[];
564         let mut inputs = ~[];
565         let mut build_inputs = ~[];
566
567         debug!("Installing package source: {}", pkg_src.to_str());
568
569         // workcache only knows about *crates*. Building a package
570         // just means inferring all the crates in it, then building each one.
571         self.build(&mut pkg_src, what);
572
573         debug!("Done building package source {}", pkg_src.to_str());
574
575         let to_do = ~[pkg_src.libs.clone(), pkg_src.mains.clone(),
576                       pkg_src.tests.clone(), pkg_src.benchs.clone()];
577         debug!("In declare inputs for {}", id.to_str());
578         for cs in to_do.iter() {
579             for c in cs.iter() {
580                 let path = pkg_src.start_dir.join(&c.file);
581                 debug!("Recording input: {}", path.display());
582                 // FIXME (#9639): This needs to handle non-utf8 paths
583                 inputs.push((~"file", path.as_str().unwrap().to_owned()));
584                 build_inputs.push(path);
585             }
586         }
587
588         let result = self.install_no_build(pkg_src.build_workspace(),
589                                            build_inputs,
590                                            &pkg_src.destination_workspace,
591                                            &id).map(|s| Path::init(s.as_slice()));
592         installed_files = installed_files + result;
593         note(format!("Installed package {} to {}",
594                      id.to_str(),
595                      pkg_src.destination_workspace.display()));
596         (installed_files, inputs)
597     }
598
599     // again, working around lack of Encodable for Path
600     fn install_no_build(&self,
601                         build_workspace: &Path,
602                         build_inputs: &[Path],
603                         target_workspace: &Path,
604                         id: &PkgId) -> ~[~str] {
605
606         debug!("install_no_build: assuming {} comes from {} with target {}",
607                id.to_str(), build_workspace.display(), target_workspace.display());
608
609         // Now copy stuff into the install dirs
610         let maybe_executable = built_executable_in_workspace(id, build_workspace);
611         let maybe_library = built_library_in_workspace(id, build_workspace);
612         let target_exec = target_executable_in_workspace(id, target_workspace);
613         let target_lib = maybe_library.as_ref()
614             .map(|_| target_library_in_workspace(id, target_workspace));
615
616         debug!("target_exec = {} target_lib = {:?} \
617                maybe_executable = {:?} maybe_library = {:?}",
618                target_exec.display(), target_lib,
619                maybe_executable, maybe_library);
620
621         self.workcache_context.with_prep(id.install_tag(), |prep| {
622             for ee in maybe_executable.iter() {
623                 // FIXME (#9639): This needs to handle non-utf8 paths
624                 prep.declare_input("binary",
625                                    ee.as_str().unwrap(),
626                                    workcache_support::digest_only_date(ee));
627             }
628             for ll in maybe_library.iter() {
629                 // FIXME (#9639): This needs to handle non-utf8 paths
630                 prep.declare_input("binary",
631                                    ll.as_str().unwrap(),
632                                    workcache_support::digest_only_date(ll));
633             }
634             let subex = maybe_executable.clone();
635             let sublib = maybe_library.clone();
636             let sub_target_ex = target_exec.clone();
637             let sub_target_lib = target_lib.clone();
638             let sub_build_inputs = build_inputs.to_owned();
639             prep.exec(proc(exe_thing) {
640                 let mut outputs = ~[];
641                 // Declare all the *inputs* to the declared input too, as inputs
642                 for executable in subex.iter() {
643                     exe_thing.discover_input("binary",
644                                              executable.as_str().unwrap().to_owned(),
645                                              workcache_support::digest_only_date(executable));
646                 }
647                 for library in sublib.iter() {
648                     exe_thing.discover_input("binary",
649                                              library.as_str().unwrap().to_owned(),
650                                              workcache_support::digest_only_date(library));
651                 }
652
653                 for transitive_dependency in sub_build_inputs.iter() {
654                     exe_thing.discover_input(
655                         "file",
656                         transitive_dependency.as_str().unwrap().to_owned(),
657                         workcache_support::digest_file_with_date(transitive_dependency));
658                 }
659
660
661                 for exec in subex.iter() {
662                     debug!("Copying: {} -> {}", exec.display(), sub_target_ex.display());
663                     fs::mkdir_recursive(&sub_target_ex.dir_path(), io::UserRWX);
664                     fs::copy(exec, &sub_target_ex);
665                     // FIXME (#9639): This needs to handle non-utf8 paths
666                     exe_thing.discover_output("binary",
667                         sub_target_ex.as_str().unwrap(),
668                         workcache_support::digest_only_date(&sub_target_ex));
669                     outputs.push(sub_target_ex.as_str().unwrap().to_owned());
670                 }
671                 for lib in sublib.iter() {
672                     let mut target_lib = sub_target_lib
673                         .clone().expect(format!("I built {} but apparently \
674                                              didn't install it!", lib.display()));
675                     target_lib.set_filename(lib.filename().expect("weird target lib"));
676                     fs::mkdir_recursive(&target_lib.dir_path(), io::UserRWX);
677                     fs::copy(lib, &target_lib);
678                     debug!("3. discovering output {}", target_lib.display());
679                     exe_thing.discover_output("binary",
680                                               target_lib.as_str().unwrap(),
681                                               workcache_support::digest_only_date(&target_lib));
682                     outputs.push(target_lib.as_str().unwrap().to_owned());
683                 }
684                 outputs
685             })
686         })
687     }
688
689     fn prefer(&self, _id: &str, _vers: Option<~str>)  {
690         fail!("prefer not yet implemented");
691     }
692
693     fn test(&self, pkgid: &PkgId, workspace: &Path)  {
694         match built_test_in_workspace(pkgid, workspace) {
695             Some(test_exec) => {
696                 debug!("test: test_exec = {}", test_exec.display());
697                 // FIXME (#9639): This needs to handle non-utf8 paths
698                 let status = run::process_status(test_exec.as_str().unwrap(), [~"--test"]);
699                 if !status.success() {
700                     fail!("Some tests failed");
701                 }
702             }
703             None => {
704                 error(format!("Internal error: test executable for package ID {} in workspace {} \
705                            wasn't built! Please report this as a bug.",
706                            pkgid.to_str(), workspace.display()));
707             }
708         }
709     }
710
711     fn init(&self) {
712         fs::mkdir_recursive(&Path::init("src"), io::UserRWX);
713         fs::mkdir_recursive(&Path::init("bin"), io::UserRWX);
714         fs::mkdir_recursive(&Path::init("lib"), io::UserRWX);
715         fs::mkdir_recursive(&Path::init("build"), io::UserRWX);
716     }
717
718     fn uninstall(&self, _id: &str, _vers: Option<~str>)  {
719         fail!("uninstall not yet implemented");
720     }
721
722     fn unprefer(&self, _id: &str, _vers: Option<~str>)  {
723         fail!("unprefer not yet implemented");
724     }
725 }
726
727 pub fn main() {
728     println("WARNING: The Rust package manager is experimental and may be unstable");
729     os::set_exit_status(main_args(os::args()));
730 }
731
732 pub fn main_args(args: &[~str]) -> int {
733     let opts = ~[getopts::optflag("h"), getopts::optflag("help"),
734                                         getopts::optflag("no-link"),
735                                         getopts::optflag("no-trans"),
736                  // n.b. Ignores different --pretty options for now
737                                         getopts::optflag("pretty"),
738                                         getopts::optflag("parse-only"),
739                  getopts::optflag("S"), getopts::optflag("assembly"),
740                  getopts::optmulti("c"), getopts::optmulti("cfg"),
741                  getopts::optflag("v"), getopts::optflag("version"),
742                  getopts::optflag("r"), getopts::optflag("rust-path-hack"),
743                                         getopts::optopt("sysroot"),
744                                         getopts::optflag("emit-llvm"),
745                                         getopts::optopt("linker"),
746                                         getopts::optopt("link-args"),
747                                         getopts::optopt("opt-level"),
748                  getopts::optflag("O"),
749                                         getopts::optflag("save-temps"),
750                                         getopts::optopt("target"),
751                                         getopts::optopt("target-cpu"),
752                  getopts::optmulti("Z")                                   ];
753     let matches = &match getopts::getopts(args, opts) {
754         result::Ok(m) => m,
755         result::Err(f) => {
756             error(format!("{}", f.to_err_msg()));
757
758             return 1;
759         }
760     };
761     let help = matches.opt_present("h") ||
762                    matches.opt_present("help");
763     let no_link = matches.opt_present("no-link");
764     let no_trans = matches.opt_present("no-trans");
765     let supplied_sysroot = matches.opt_str("sysroot");
766     let generate_asm = matches.opt_present("S") ||
767         matches.opt_present("assembly");
768     let parse_only = matches.opt_present("parse-only");
769     let pretty = matches.opt_present("pretty");
770     let emit_llvm = matches.opt_present("emit-llvm");
771
772     if matches.opt_present("v") ||
773        matches.opt_present("version") {
774         rustc::version(args[0]);
775         return 0;
776     }
777
778     let use_rust_path_hack = matches.opt_present("r") ||
779                              matches.opt_present("rust-path-hack");
780
781     let linker = matches.opt_str("linker");
782     let link_args = matches.opt_str("link-args");
783     let cfgs = matches.opt_strs("cfg") + matches.opt_strs("c");
784     let mut user_supplied_opt_level = true;
785     let opt_level = match matches.opt_str("opt-level") {
786         Some(~"0") => session::No,
787         Some(~"1") => session::Less,
788         Some(~"2") => session::Default,
789         Some(~"3") => session::Aggressive,
790         _ if matches.opt_present("O") => session::Default,
791         _ => {
792             user_supplied_opt_level = false;
793             session::No
794         }
795     };
796
797     let save_temps = matches.opt_present("save-temps");
798     let target     = matches.opt_str("target");
799     let target_cpu = matches.opt_str("target-cpu");
800     let experimental_features = {
801         let strs = matches.opt_strs("Z");
802         if matches.opt_present("Z") {
803             Some(strs)
804         }
805         else {
806             None
807         }
808     };
809
810     let mut args = matches.free.clone();
811     args.shift();
812
813     if (args.len() < 1) {
814         usage::general();
815         return 1;
816     }
817
818     let rustc_flags = RustcFlags {
819         linker: linker,
820         link_args: link_args,
821         optimization_level: opt_level,
822         compile_upto: if no_trans {
823             Trans
824         } else if no_link {
825             Link
826         } else if pretty {
827             Pretty
828         } else if parse_only {
829             Analysis
830         } else if emit_llvm && generate_asm {
831             LLVMAssemble
832         } else if generate_asm {
833             Assemble
834         } else if emit_llvm {
835             LLVMCompileBitcode
836         } else {
837             Nothing
838         },
839         save_temps: save_temps,
840         target: target,
841         target_cpu: target_cpu,
842         additional_library_paths:
843             HashSet::new(), // No way to set this from the rustpkg command line
844         experimental_features: experimental_features
845     };
846
847     let mut cmd_opt = None;
848     for a in args.iter() {
849         if util::is_cmd(*a) {
850             cmd_opt = Some(a);
851             break;
852         }
853     }
854     let cmd = match cmd_opt {
855         None => {
856             usage::general();
857             return 0;
858         }
859         Some(cmd) => {
860             let bad_option = context::flags_forbidden_for_cmd(&rustc_flags,
861                                                               cfgs,
862                                                               *cmd,
863                                                               user_supplied_opt_level);
864             if help || bad_option {
865                 match *cmd {
866                     ~"build" => usage::build(),
867                     ~"clean" => usage::clean(),
868                     ~"do" => usage::do_cmd(),
869                     ~"info" => usage::info(),
870                     ~"install" => usage::install(),
871                     ~"list"    => usage::list(),
872                     ~"prefer" => usage::prefer(),
873                     ~"test" => usage::test(),
874                     ~"init" => usage::init(),
875                     ~"uninstall" => usage::uninstall(),
876                     ~"unprefer" => usage::unprefer(),
877                     _ => usage::general()
878                 };
879                 if bad_option {
880                     return BAD_FLAG_CODE;
881                 }
882                 else {
883                     return 0;
884                 }
885             } else {
886                 cmd
887             }
888         }
889     };
890
891     // Pop off all flags, plus the command
892     let remaining_args = args.iter().skip_while(|s| !util::is_cmd(**s));
893     // I had to add this type annotation to get the code to typecheck
894     let mut remaining_args: ~[~str] = remaining_args.map(|s| (*s).clone()).collect();
895     remaining_args.shift();
896     let sroot = match supplied_sysroot {
897         Some(s) => Path::init(s),
898         _ => filesearch::get_or_default_sysroot()
899     };
900
901     debug!("Using sysroot: {}", sroot.display());
902     let ws = default_workspace();
903     debug!("Will store workcache in {}", ws.display());
904
905     let rm_args = remaining_args.clone();
906     let sub_cmd = cmd.clone();
907     // Wrap the rest in task::try in case of a condition failure in a task
908     let result = do task::try {
909         BuildContext {
910             context: Context {
911                 cfgs: cfgs.clone(),
912                 rustc_flags: rustc_flags.clone(),
913                 use_rust_path_hack: use_rust_path_hack,
914                 sysroot: sroot.clone(), // Currently, only tests override this
915             },
916             workcache_context: api::default_context(sroot.clone(),
917                                                     default_workspace()).workcache_context
918         }.run(sub_cmd, rm_args.clone())
919     };
920     // FIXME #9262: This is using the same error code for all errors,
921     // and at least one test case succeeds if rustpkg returns COPY_FAILED_CODE,
922     // when actually, it might set the exit code for that even if a different
923     // unhandled condition got raised.
924     if result.is_err() { return COPY_FAILED_CODE; }
925     return 0;
926 }
927
928 fn declare_package_script_dependency(prep: &mut workcache::Prep, pkg_src: &PkgSrc) {
929     match pkg_src.package_script_option() {
930         // FIXME (#9639): This needs to handle non-utf8 paths
931         Some(ref p) => prep.declare_input("file", p.as_str().unwrap(),
932                                       workcache_support::digest_file_with_date(p)),
933         None => ()
934     }
935 }