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