]> git.lizzy.rs Git - rust.git/blob - src/librustpkg/util.rs
Add generation of static libraries to rustc
[rust.git] / src / librustpkg / util.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 use std::libc;
12 use std::os;
13 use std::io;
14 use std::io::fs;
15 use extra::workcache;
16 use rustc::driver::{driver, session};
17 use extra::getopts::groups::getopts;
18 use syntax::ast_util::*;
19 use syntax::codemap::{dummy_sp, Spanned};
20 use syntax::ext::base::ExtCtxt;
21 use syntax::{ast, attr, codemap, diagnostic, fold, visit};
22 use syntax::attr::AttrMetaMethods;
23 use syntax::fold::ast_fold;
24 use syntax::visit::Visitor;
25 use syntax::util::small_vector::SmallVector;
26 use rustc::back::link::output_type_exe;
27 use rustc::back::link;
28 use context::{in_target, StopBefore, Link, Assemble, BuildContext};
29 use package_id::PkgId;
30 use package_source::PkgSrc;
31 use workspace::pkg_parent_workspaces;
32 use path_util::{system_library, target_build_dir};
33 use path_util::{default_workspace, built_library_in_workspace};
34 pub use target::{OutputType, Main, Lib, Bench, Test, JustOne, lib_name_of, lib_crate_filename};
35 pub use target::{Target, Build, Install};
36 use extra::treemap::TreeMap;
37 pub use target::{lib_name_of, lib_crate_filename, WhatToBuild, MaybeCustom, Inferred};
38 use workcache_support::{digest_file_with_date, digest_only_date};
39 use messages::error;
40
41 // It would be nice to have the list of commands in just one place -- for example,
42 // you could update the match in rustpkg.rc but forget to update this list. I think
43 // that should be fixed.
44 static COMMANDS: &'static [&'static str] =
45     &["build", "clean", "do", "info", "init", "install", "list", "prefer", "test", "uninstall",
46       "unprefer"];
47
48
49 pub type ExitCode = int; // For now
50
51 pub struct Pkg {
52     id: PkgId,
53     bins: ~[~str],
54     libs: ~[~str],
55 }
56
57 impl ToStr for Pkg {
58     fn to_str(&self) -> ~str {
59         self.id.to_str()
60     }
61 }
62
63 pub fn is_cmd(cmd: &str) -> bool {
64     COMMANDS.iter().any(|&c| c == cmd)
65 }
66
67 struct ListenerFn {
68     cmds: ~[~str],
69     span: codemap::Span,
70     path: ~[ast::Ident]
71 }
72
73 struct ReadyCtx {
74     sess: session::Session,
75     ext_cx: @ExtCtxt,
76     path: ~[ast::Ident],
77     fns: ~[ListenerFn]
78 }
79
80 fn fold_mod(_ctx: @mut ReadyCtx, m: &ast::_mod, fold: &CrateSetup)
81             -> ast::_mod {
82     fn strip_main(item: @ast::item) -> @ast::item {
83         @ast::item {
84             attrs: item.attrs.iter().filter_map(|attr| {
85                 if "main" != attr.name() {
86                     Some(*attr)
87                 } else {
88                     None
89                 }
90             }).collect(),
91             .. (*item).clone()
92         }
93     }
94
95     fold::noop_fold_mod(&ast::_mod {
96         items: m.items.map(|item| strip_main(*item)),
97         .. (*m).clone()
98     }, fold)
99 }
100
101 fn fold_item(ctx: @mut ReadyCtx, item: @ast::item, fold: &CrateSetup)
102              -> SmallVector<@ast::item> {
103     ctx.path.push(item.ident);
104
105     let mut cmds = ~[];
106     let mut had_pkg_do = false;
107
108     for attr in item.attrs.iter() {
109         if "pkg_do" == attr.name() {
110             had_pkg_do = true;
111             match attr.node.value.node {
112                 ast::MetaList(_, ref mis) => {
113                     for mi in mis.iter() {
114                         match mi.node {
115                             ast::MetaWord(cmd) => cmds.push(cmd.to_owned()),
116                             _ => {}
117                         };
118                     }
119                 }
120                 _ => cmds.push(~"build")
121             }
122         }
123     }
124
125     if had_pkg_do {
126         ctx.fns.push(ListenerFn {
127             cmds: cmds,
128             span: item.span,
129             path: /*bad*/ctx.path.clone()
130         });
131     }
132
133     let res = fold::noop_fold_item(item, fold);
134
135     ctx.path.pop();
136
137     res
138 }
139
140 struct CrateSetup {
141     ctx: @mut ReadyCtx,
142 }
143
144 impl fold::ast_fold for CrateSetup {
145     fn fold_item(&self, item: @ast::item) -> SmallVector<@ast::item> {
146         fold_item(self.ctx, item, self)
147     }
148     fn fold_mod(&self, module: &ast::_mod) -> ast::_mod {
149         fold_mod(self.ctx, module, self)
150     }
151 }
152
153 /// Generate/filter main function, add the list of commands, etc.
154 pub fn ready_crate(sess: session::Session,
155                    crate: ast::Crate) -> ast::Crate {
156     let ctx = @mut ReadyCtx {
157         sess: sess,
158         ext_cx: ExtCtxt::new(sess.parse_sess, sess.opts.cfg.clone()),
159         path: ~[],
160         fns: ~[]
161     };
162     let fold = CrateSetup {
163         ctx: ctx,
164     };
165     fold.fold_crate(crate)
166 }
167
168 pub fn compile_input(context: &BuildContext,
169                      exec: &mut workcache::Exec,
170                      pkg_id: &PkgId,
171                      in_file: &Path,
172                      workspace: &Path,
173                      deps: &mut DepMap,
174                      flags: &[~str],
175                      cfgs: &[~str],
176                      opt: session::OptLevel,
177                      what: OutputType) -> Option<Path> {
178     assert!(in_file.components().nth(1).is_some());
179     let input = driver::file_input(in_file.clone());
180     debug!("compile_input: {} / {:?}", in_file.display(), what);
181     // tjc: by default, use the package ID name as the link name
182     // not sure if we should support anything else
183
184     let mut out_dir = target_build_dir(workspace);
185     out_dir.push(&pkg_id.path);
186     // Make the output directory if it doesn't exist already
187     fs::mkdir_recursive(&out_dir, io::UserRWX);
188
189     let binary = os::args()[0].to_managed();
190
191     debug!("flags: {}", flags.connect(" "));
192     debug!("cfgs: {}", cfgs.connect(" "));
193     let csysroot = context.sysroot();
194     debug!("compile_input's sysroot = {}", csysroot.display());
195
196     let crate_type = match what {
197         Lib => session::OutputDylib,
198         Test | Bench | Main => session::OutputExecutable,
199     };
200     let matches = getopts(debug_flags()
201                           + match what {
202                               Lib => ~[~"--lib"],
203                               // --test compiles both #[test] and #[bench] fns
204                               Test | Bench => ~[~"--test"],
205                               Main => ~[]
206                           }
207                           + flags
208                           + context.flag_strs()
209                           + cfgs.flat_map(|c| { ~[~"--cfg", (*c).clone()] }),
210                           driver::optgroups()).unwrap();
211     debug!("rustc flags: {:?}", matches);
212
213     // Hack so that rustpkg can run either out of a rustc target dir,
214     // or the host dir
215     let sysroot_to_use = @if !in_target(&context.sysroot()) {
216         context.sysroot()
217     }
218     else {
219         let mut p = context.sysroot().clone();
220         p.pop();
221         p.pop();
222         p.pop();
223         p
224     };
225     let csysroot = context.sysroot();
226     debug!("compile_input's sysroot = {}", csysroot.display());
227     debug!("sysroot_to_use = {}", sysroot_to_use.display());
228
229     let output_type = match context.compile_upto() {
230         Assemble => link::output_type_assembly,
231         Link     => link::output_type_object,
232         Pretty | Trans | Analysis => link::output_type_none,
233         LLVMAssemble => link::output_type_llvm_assembly,
234         LLVMCompileBitcode => link::output_type_bitcode,
235         Nothing => link::output_type_exe
236     };
237
238     debug!("Output type = {:?}", output_type);
239
240     let options = @session::options {
241         outputs: ~[crate_type],
242         optimize: opt,
243         test: what == Test || what == Bench,
244         maybe_sysroot: Some(sysroot_to_use),
245         addl_lib_search_paths: @mut context.additional_library_paths(),
246         output_type: output_type,
247         .. (*driver::build_session_options(binary,
248                                            &matches,
249                                            @diagnostic::DefaultEmitter as
250                                             @diagnostic::Emitter)).clone()
251     };
252
253     debug!("Created options...");
254
255     let addl_lib_search_paths = @mut options.addl_lib_search_paths;
256     // Make sure all the library directories actually exist, since the linker will complain
257     // otherwise
258     for p in addl_lib_search_paths.iter() {
259         if p.exists() {
260             assert!(p.is_dir())
261         }
262         else {
263             fs::mkdir_recursive(p, io::UserRWX);
264         }
265     }
266
267     debug!("About to build session...");
268
269     let sess = driver::build_session(options,
270                                      @diagnostic::DefaultEmitter as
271                                         @diagnostic::Emitter);
272
273     debug!("About to build config...");
274
275     // Infer dependencies that rustpkg needs to build, by scanning for
276     // `extern mod` directives.
277     let cfg = driver::build_configuration(sess);
278     let mut crate = driver::phase_1_parse_input(sess, cfg.clone(), &input);
279     crate = driver::phase_2_configure_and_expand(sess, cfg.clone(), crate);
280
281     debug!("About to call find_and_install_dependencies...");
282
283     find_and_install_dependencies(context, pkg_id, in_file, sess, exec, &crate, deps,
284                                   |p| {
285                                       debug!("a dependency: {}", p.display());
286                                       // Pass the directory containing a dependency
287                                       // as an additional lib search path
288                                       addl_lib_search_paths.insert(p);
289                                   });
290
291     // Inject the link attributes so we get the right package name and version
292     if attr::find_linkage_metas(crate.attrs).is_empty() {
293         let name_to_use = match what {
294             Test  => format!("{}test", pkg_id.short_name).to_managed(),
295             Bench => format!("{}bench", pkg_id.short_name).to_managed(),
296             _     => pkg_id.short_name.to_managed()
297         };
298         debug!("Injecting link name: {}", name_to_use);
299         // FIXME (#9639): This needs to handle non-utf8 paths
300         let link_options =
301             ~[attr::mk_name_value_item_str(@"name", name_to_use),
302               attr::mk_name_value_item_str(@"vers", pkg_id.version.to_str().to_managed())] +
303             ~[attr::mk_name_value_item_str(@"package_id",
304                                            pkg_id.path.as_str().unwrap().to_managed())];
305
306         debug!("link options: {:?}", link_options);
307         crate.attrs = ~[attr::mk_attr(attr::mk_list_item(@"link", link_options))];
308     }
309
310     debug!("calling compile_crate_from_input, workspace = {},
311            building_library = {:?}", out_dir.display(), sess.building_library);
312     let result = compile_crate_from_input(in_file,
313                                           exec,
314                                           context.compile_upto(),
315                                           &out_dir,
316                                           sess,
317                                           crate);
318     // Discover the output
319     let discovered_output = if what == Lib  {
320         built_library_in_workspace(pkg_id, workspace) // Huh???
321     }
322     else {
323         result
324     };
325     for p in discovered_output.iter() {
326         debug!("About to discover output {}", p.display());
327         if p.exists() {
328             debug!("4. discovering output {}", p.display());
329             // FIXME (#9639): This needs to handle non-utf8 paths
330             exec.discover_output("binary", p.as_str().unwrap(), digest_only_date(p));
331         }
332         // Nothing to do if it doesn't exist -- that could happen if we had the
333         // -S or -emit-llvm flags, etc.
334     }
335     discovered_output
336 }
337
338 // Should use workcache to avoid recompiling when not necessary
339 // Should also rename this to something better
340 // If crate_opt is present, then finish compilation. If it's None, then
341 // call compile_upto and return the crate
342 // also, too many arguments
343 // Returns list of discovered dependencies
344 pub fn compile_crate_from_input(input: &Path,
345                                 exec: &mut workcache::Exec,
346                                 stop_before: StopBefore,
347  // should be of the form <workspace>/build/<pkg id's path>
348                                 out_dir: &Path,
349                                 sess: session::Session,
350 // Returns None if one of the flags that suppresses compilation output was
351 // given
352                                 crate: ast::Crate) -> Option<Path> {
353     debug!("Calling build_output_filenames with {}, building library? {:?}",
354            out_dir.display(), sess.building_library);
355
356     // bad copy
357     debug!("out_dir = {}", out_dir.display());
358     let outputs = driver::build_output_filenames(&driver::file_input(input.clone()),
359                                                  &Some(out_dir.clone()), &None,
360                                                  crate.attrs, sess);
361
362     debug!("Outputs are out_filename: {} and obj_filename: {} and output type = {:?}",
363            outputs.out_filename.display(),
364            outputs.obj_filename.display(),
365            sess.opts.output_type);
366     debug!("additional libraries:");
367     for lib in sess.opts.addl_lib_search_paths.iter() {
368         debug!("an additional library: {}", lib.display());
369     }
370     let analysis = driver::phase_3_run_analysis_passes(sess, &crate);
371     if driver::stop_after_phase_3(sess) { return None; }
372     let translation = driver::phase_4_translate_to_llvm(sess, crate,
373                                                         &analysis,
374                                                         outputs);
375     driver::phase_5_run_llvm_passes(sess, &translation, outputs);
376     // The second check shouldn't be necessary, but rustc seems to ignore
377     // -c
378     if driver::stop_after_phase_5(sess)
379         || stop_before == Link || stop_before == Assemble { return Some(outputs.out_filename); }
380     driver::phase_6_link_output(sess, &translation, outputs);
381
382     // Register dependency on the source file
383     // FIXME (#9639): This needs to handle non-utf8 paths
384     exec.discover_input("file", input.as_str().unwrap(), digest_file_with_date(input));
385
386     debug!("Built {}, date = {:?}", outputs.out_filename.display(),
387            datestamp(&outputs.out_filename));
388     Some(outputs.out_filename)
389 }
390
391 #[cfg(windows)]
392 pub fn exe_suffix() -> ~str { ~".exe" }
393
394 #[cfg(target_os = "linux")]
395 #[cfg(target_os = "android")]
396 #[cfg(target_os = "freebsd")]
397 #[cfg(target_os = "macos")]
398 pub fn exe_suffix() -> ~str { ~"" }
399
400 // Called by build_crates
401 pub fn compile_crate(ctxt: &BuildContext,
402                      exec: &mut workcache::Exec,
403                      pkg_id: &PkgId,
404                      crate: &Path,
405                      workspace: &Path,
406                      deps: &mut DepMap,
407                      flags: &[~str],
408                      cfgs: &[~str],
409                      opt: session::OptLevel,
410                      what: OutputType) -> Option<Path> {
411     debug!("compile_crate: crate={}, workspace={}", crate.display(), workspace.display());
412     debug!("compile_crate: short_name = {}, flags =...", pkg_id.to_str());
413     for fl in flags.iter() {
414         debug!("+++ {}", *fl);
415     }
416     compile_input(ctxt, exec, pkg_id, crate, workspace, deps, flags, cfgs, opt, what)
417 }
418
419 struct ViewItemVisitor<'self> {
420     context: &'self BuildContext,
421     parent: &'self PkgId,
422     parent_crate: &'self Path,
423     sess: session::Session,
424     exec: &'self mut workcache::Exec,
425     c: &'self ast::Crate,
426     save: 'self |Path|,
427     deps: &'self mut DepMap
428 }
429
430 impl<'self> Visitor<()> for ViewItemVisitor<'self> {
431     fn visit_view_item(&mut self, vi: &ast::view_item, env: ()) {
432         use conditions::nonexistent_package::cond;
433
434         match vi.node {
435             // ignore metadata, I guess
436             ast::view_item_extern_mod(lib_ident, path_opt, _, _) => {
437                 let lib_name = match path_opt {
438                     Some((p, _)) => p,
439                     None => self.sess.str_of(lib_ident)
440                 };
441                 debug!("Finding and installing... {}", lib_name);
442                 // Check standard Rust library path first
443                 let whatever = system_library(&self.context.sysroot(), lib_name);
444                 debug!("system library returned {:?}", whatever);
445                 match whatever {
446                     Some(ref installed_path) => {
447                         debug!("It exists: {}", installed_path.display());
448                         // Say that [path for c] has a discovered dependency on
449                         // installed_path
450                         // For binary files, we only hash the datestamp, not the contents.
451                         // I'm not sure what the right thing is.
452                         // Now we know that this crate has a discovered dependency on
453                         // installed_path
454                         // FIXME (#9639): This needs to handle non-utf8 paths
455                         add_dep(self.deps, self.parent_crate.as_str().unwrap().to_owned(),
456                                 (~"binary", installed_path.as_str().unwrap().to_owned()));
457                         self.exec.discover_input("binary",
458                                                  installed_path.as_str().unwrap(),
459                                                  digest_only_date(installed_path));
460                     }
461                     None => {
462                         // FIXME #8711: need to parse version out of path_opt
463                         debug!("Trying to install library {}, rebuilding it",
464                                lib_name.to_str());
465                         // Try to install it
466                         let pkg_id = PkgId::new(lib_name);
467                         // Find all the workspaces in the RUST_PATH that contain this package.
468                         let workspaces = pkg_parent_workspaces(&self.context.context,
469                                                                &pkg_id);
470                         // Three cases:
471                         // (a) `workspaces` is empty. That means there's no local source
472                         // for this package. In that case, we pass the default workspace
473                         // into `PkgSrc::new`, so that if it exists as a remote repository,
474                         // its sources will be fetched into it. We also put the output in the
475                         // same workspace.
476                         // (b) We're using the Rust path hack. In that case, the output goes
477                         // in the destination workspace.
478                         // (c) `workspaces` is non-empty -- we found a local source for this
479                         // package and will build in that workspace.
480                         let (source_workspace, dest_workspace) = if workspaces.is_empty() {
481                             (default_workspace(), default_workspace())
482                         } else {
483                             if self.context.context.use_rust_path_hack {
484                                 (workspaces[0], default_workspace())
485                             } else {
486                                  (workspaces[0].clone(), workspaces[0])
487                             }
488                         };
489                         // In this case, the source and destination workspaces are the same:
490                         // Either it's a remote package, so the local sources don't exist
491                         // and the `PkgSrc` constructor will detect that;
492                         // or else it's already in a workspace and we'll build into that
493                         // workspace
494                         let pkg_src = cond.trap(|_| {
495                                  // Nonexistent package? Then print a better error
496                                  error(format!("Package {} depends on {}, but I don't know \
497                                                how to find it",
498                                                self.parent.path.display(),
499                                                pkg_id.path.display()));
500                                  fail!()
501                         }).inside(|| {
502                             PkgSrc::new(source_workspace.clone(),
503                                         dest_workspace.clone(),
504                                         // Use the rust_path_hack to search for dependencies iff
505                                         // we were already using it
506                                         self.context.context.use_rust_path_hack,
507                                         pkg_id.clone())
508                         });
509                         let (outputs_disc, inputs_disc) =
510                             self.context.install(
511                                 pkg_src,
512                                 &WhatToBuild::new(Inferred,
513                                                   JustOne(Path::init(lib_crate_filename))));
514                         debug!("Installed {}, returned {:?} dependencies and \
515                                {:?} transitive dependencies",
516                                lib_name, outputs_disc.len(), inputs_disc.len());
517                         debug!("discovered outputs = {:?} discovered_inputs = {:?}",
518                                outputs_disc, inputs_disc);
519                         // It must have installed *something*...
520                         assert!(!outputs_disc.is_empty());
521                         for dep in outputs_disc.iter() {
522                             debug!("Discovering a binary input: {}", dep.display());
523                             // FIXME (#9639): This needs to handle non-utf8 paths
524                             self.exec.discover_input("binary",
525                                                      dep.as_str().unwrap(),
526                                                      digest_only_date(dep));
527                             add_dep(self.deps,
528                                     self.parent_crate.as_str().unwrap().to_owned(),
529                                     (~"binary", dep.as_str().unwrap().to_owned()));
530
531                             // Also, add an additional search path
532                             let dep_dir = dep.dir_path();
533                             debug!("Installed {} into {}", dep.display(), dep_dir.display());
534                             (self.save)(dep_dir);
535                         }
536                         debug!("Installed {}, returned {} dependencies and \
537                                 {} transitive dependencies",
538                                 lib_name, outputs_disc.len(), inputs_disc.len());
539                         // It must have installed *something*...
540                         assert!(!outputs_disc.is_empty());
541                         let mut target_workspace = outputs_disc[0].clone();
542                         target_workspace.pop();
543                         for &(ref what, ref dep) in inputs_disc.iter() {
544                             if *what == ~"file" {
545                                 add_dep(self.deps,
546                                         self.parent_crate.as_str().unwrap().to_owned(),
547                                         (~"file", dep.clone()));
548                                 self.exec.discover_input(*what,
549                                                          *dep,
550                                                          digest_file_with_date(
551                                                              &Path::init(dep.as_slice())));
552                             } else if *what == ~"binary" {
553                                 add_dep(self.deps,
554                                         self.parent_crate.as_str().unwrap().to_owned(),
555                                         (~"binary", dep.clone()));
556                                 self.exec.discover_input(*what,
557                                                          *dep,
558                                                          digest_only_date(
559                                                              &Path::init(dep.as_slice())));
560                             } else {
561                                 fail!("Bad kind: {}", *what);
562                             }
563                             // Also, add an additional search path
564                             debug!("Installed {} into {}",
565                                     lib_name, target_workspace.as_str().unwrap().to_owned());
566                             (self.save)(target_workspace.clone());
567                         }
568                     }
569                 }
570             }
571             // Ignore `use`s
572             _ => ()
573         }
574         visit::walk_view_item(self, vi, env)
575     }
576 }
577
578 /// Collect all `extern mod` directives in `c`, then
579 /// try to install their targets, failing if any target
580 /// can't be found.
581 pub fn find_and_install_dependencies(context: &BuildContext,
582                                      parent: &PkgId,
583                                      parent_crate: &Path,
584                                      sess: session::Session,
585                                      exec: &mut workcache::Exec,
586                                      c: &ast::Crate,
587                                      deps: &mut DepMap,
588                                      save: |Path|) {
589     debug!("In find_and_install_dependencies...");
590     let mut visitor = ViewItemVisitor {
591         context: context,
592         parent: parent,
593         parent_crate: parent_crate,
594         sess: sess,
595         exec: exec,
596         c: c,
597         save: save,
598         deps: deps
599     };
600     visit::walk_crate(&mut visitor, c, ())
601 }
602
603 pub fn mk_string_lit(s: @str) -> ast::lit {
604     Spanned {
605         node: ast::lit_str(s, ast::CookedStr),
606         span: dummy_sp()
607     }
608 }
609
610 #[cfg(test)]
611 mod test {
612     use super::is_cmd;
613
614     #[test]
615     fn test_is_cmd() {
616         assert!(is_cmd("build"));
617         assert!(is_cmd("clean"));
618         assert!(is_cmd("do"));
619         assert!(is_cmd("info"));
620         assert!(is_cmd("install"));
621         assert!(is_cmd("prefer"));
622         assert!(is_cmd("test"));
623         assert!(is_cmd("uninstall"));
624         assert!(is_cmd("unprefer"));
625     }
626
627 }
628
629 pub fn option_to_vec<T>(x: Option<T>) -> ~[T] {
630     match x {
631        Some(y) => ~[y],
632        None    => ~[]
633     }
634 }
635
636 // tjc: cheesy
637 fn debug_flags() -> ~[~str] { ~[] }
638 // static DEBUG_FLAGS: ~[~str] = ~[~"-Z", ~"time-passes"];
639
640
641 /// Returns the last-modified date as an Option
642 pub fn datestamp(p: &Path) -> Option<libc::time_t> {
643     debug!("Scrutinizing datestamp for {} - does it exist? {:?}", p.display(),
644            p.exists());
645     match io::result(|| p.stat()) {
646         Ok(s) => {
647             let out = s.modified;
648             debug!("Date = {:?}", out);
649             Some(out as libc::time_t)
650         }
651         Err(..) => None,
652     }
653 }
654
655 pub type DepMap = TreeMap<~str, ~[(~str, ~str)]>;
656
657 /// Records a dependency from `parent` to the kind and value described by `info`,
658 /// in `deps`
659 fn add_dep(deps: &mut DepMap, parent: ~str, info: (~str, ~str)) {
660     let mut done = false;
661     let info_clone = info.clone();
662     match deps.find_mut(&parent) {
663         None => { }
664         Some(v) => { done = true; (*v).push(info) }
665     };
666     if !done {
667         deps.insert(parent, ~[info_clone]);
668     }
669 }