]> git.lizzy.rs Git - rust.git/blob - src/librustpkg/util.rs
Merge remote-tracking branch 'remotes/origin/master' into str-remove-null
[rust.git] / src / librustpkg / util.rs
1 // Copyright 2012 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::{os, result};
12 use std::c_str::ToCStr;
13 use rustc::driver::{driver, session};
14 use rustc::metadata::filesearch;
15 use extra::getopts::groups::getopts;
16 use syntax::ast_util::*;
17 use syntax::codemap::{dummy_sp, spanned};
18 use syntax::ext::base::ExtCtxt;
19 use syntax::{ast, attr, codemap, diagnostic, fold};
20 use syntax::attr::AttrMetaMethods;
21 use rustc::back::link::output_type_exe;
22 use rustc::driver::session::{lib_crate, bin_crate};
23 use context::Ctx;
24 use package_id::PkgId;
25 use search::find_library_in_search_path;
26 use path_util::target_library_in_workspace;
27 pub use target::{OutputType, Main, Lib, Bench, Test};
28
29 // It would be nice to have the list of commands in just one place -- for example,
30 // you could update the match in rustpkg.rc but forget to update this list. I think
31 // that should be fixed.
32 static COMMANDS: &'static [&'static str] =
33     &["build", "clean", "do", "info", "install", "list", "prefer", "test", "uninstall",
34       "unprefer"];
35
36
37 pub type ExitCode = int; // For now
38
39 pub struct Pkg {
40     id: PkgId,
41     bins: ~[~str],
42     libs: ~[~str],
43 }
44
45 impl ToStr for Pkg {
46     fn to_str(&self) -> ~str {
47         self.id.to_str()
48     }
49 }
50
51 pub fn root() -> Path {
52     match filesearch::get_rustpkg_root() {
53         result::Ok(path) => path,
54         result::Err(err) => fail!(err)
55     }
56 }
57
58 pub fn is_cmd(cmd: &str) -> bool {
59     COMMANDS.iter().any(|&c| c == cmd)
60 }
61
62 struct ListenerFn {
63     cmds: ~[~str],
64     span: codemap::span,
65     path: ~[ast::ident]
66 }
67
68 struct ReadyCtx {
69     sess: session::Session,
70     crate: @ast::Crate,
71     ext_cx: @ExtCtxt,
72     path: ~[ast::ident],
73     fns: ~[ListenerFn]
74 }
75
76 fn fold_mod(_ctx: @mut ReadyCtx,
77             m: &ast::_mod,
78             fold: @fold::ast_fold) -> ast::_mod {
79     fn strip_main(item: @ast::item) -> @ast::item {
80         @ast::item {
81             attrs: do item.attrs.iter().filter_map |attr| {
82                 if "main" != attr.name() {
83                     Some(*attr)
84                 } else {
85                     None
86                 }
87             }.collect(),
88             .. (*item).clone()
89         }
90     }
91
92     fold::noop_fold_mod(&ast::_mod {
93         items: do m.items.map |item| {
94             strip_main(*item)
95         },
96         .. (*m).clone()
97     }, fold)
98 }
99
100 fn fold_item(ctx: @mut ReadyCtx,
101              item: @ast::item,
102              fold: @fold::ast_fold) -> Option<@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 /// Generate/filter main function, add the list of commands, etc.
141 pub fn ready_crate(sess: session::Session,
142                    crate: @ast::Crate) -> @ast::Crate {
143     let ctx = @mut ReadyCtx {
144         sess: sess,
145         crate: crate,
146         ext_cx: ExtCtxt::new(sess.parse_sess, sess.opts.cfg.clone()),
147         path: ~[],
148         fns: ~[]
149     };
150     let precursor = @fold::AstFoldFns {
151         // fold_crate: fold::wrap(|a, b| fold_crate(ctx, a, b)),
152         fold_item: |a, b| fold_item(ctx, a, b),
153         fold_mod: |a, b| fold_mod(ctx, a, b),
154         .. *fold::default_ast_fold()
155     };
156
157     let fold = fold::make_fold(precursor);
158
159     @fold.fold_crate(crate)
160 }
161
162 // FIXME (#4432): Use workcache to only compile when needed
163 pub fn compile_input(ctxt: &Ctx,
164                      pkg_id: &PkgId,
165                      in_file: &Path,
166                      out_dir: &Path,
167                      flags: &[~str],
168                      cfgs: &[~str],
169                      opt: bool,
170                      what: OutputType) -> bool {
171
172     let workspace = out_dir.pop().pop();
173
174     assert!(in_file.components.len() > 1);
175     let input = driver::file_input((*in_file).clone());
176     debug!("compile_input: %s / %?", in_file.to_str(), what);
177     // tjc: by default, use the package ID name as the link name
178     // not sure if we should support anything else
179
180     let binary = os::args()[0].to_managed();
181
182     debug!("flags: %s", flags.connect(" "));
183     debug!("cfgs: %s", cfgs.connect(" "));
184     debug!("compile_input's sysroot = %?", ctxt.sysroot_opt);
185
186     let crate_type = match what {
187         Lib => lib_crate,
188         Test | Bench | Main => bin_crate
189     };
190     let matches = getopts(debug_flags()
191                           + match what {
192                               Lib => ~[~"--lib"],
193                               // --test compiles both #[test] and #[bench] fns
194                               Test | Bench => ~[~"--test"],
195                               Main => ~[]
196                           }
197                           + flags
198                           + cfgs.flat_map(|c| { ~[~"--cfg", (*c).clone()] }),
199                           driver::optgroups()).get();
200     let options = @session::options {
201         crate_type: crate_type,
202         optimize: if opt { session::Aggressive } else { session::No },
203         test: what == Test || what == Bench,
204         maybe_sysroot: ctxt.sysroot_opt,
205         addl_lib_search_paths: @mut (~[(*out_dir).clone()]),
206         // output_type should be conditional
207         output_type: output_type_exe, // Use this to get a library? That's weird
208         .. (*driver::build_session_options(binary, &matches, diagnostic::emit)).clone()
209     };
210
211     let addl_lib_search_paths = @mut options.addl_lib_search_paths;
212     // Make sure all the library directories actually exist, since the linker will complain
213     // otherwise
214     for p in addl_lib_search_paths.iter() {
215         assert!(os::path_is_dir(p));
216     }
217
218     let sess = driver::build_session(options, diagnostic::emit);
219
220     // Infer dependencies that rustpkg needs to build, by scanning for
221     // `extern mod` directives.
222     let cfg = driver::build_configuration(sess, binary, &input);
223     let mut crate = driver::phase_1_parse_input(sess, cfg.clone(), &input);
224     crate = driver::phase_2_configure_and_expand(sess, cfg, crate);
225
226     // Not really right. Should search other workspaces too, and the installed
227     // database (which doesn't exist yet)
228     find_and_install_dependencies(ctxt, sess, &workspace, crate,
229                                   |p| {
230                                       debug!("a dependency: %s", p.to_str());
231                                       // Pass the directory containing a dependency
232                                       // as an additional lib search path
233                                       addl_lib_search_paths.push(p);
234                                   });
235
236     // Inject the link attributes so we get the right package name and version
237     if attr::find_linkage_metas(crate.attrs).is_empty() {
238         let short_name_to_use = match what {
239             Test  => fmt!("%stest", pkg_id.short_name),
240             Bench => fmt!("%sbench", pkg_id.short_name),
241             _     => pkg_id.short_name.clone()
242         };
243         debug!("Injecting link name: %s", short_name_to_use);
244         let link_options =
245             ~[attr::mk_name_value_item_str(@"name", short_name_to_use.to_managed()),
246               attr::mk_name_value_item_str(@"vers", pkg_id.version.to_str().to_managed())];
247
248         crate = @ast::Crate {
249             attrs: ~[attr::mk_attr(attr::mk_list_item(@"link", link_options))],
250             .. (*crate).clone()
251         };
252     }
253
254     debug!("calling compile_crate_from_input, out_dir = %s,
255            building_library = %?", out_dir.to_str(), sess.building_library);
256     compile_crate_from_input(&input, out_dir, sess, crate);
257     true
258 }
259
260 // Should use workcache to avoid recompiling when not necessary
261 // Should also rename this to something better
262 // If crate_opt is present, then finish compilation. If it's None, then
263 // call compile_upto and return the crate
264 // also, too many arguments
265 pub fn compile_crate_from_input(input: &driver::input,
266                                 build_dir: &Path,
267                                 sess: session::Session,
268                                 crate: @ast::Crate) {
269     debug!("Calling build_output_filenames with %s, building library? %?",
270            build_dir.to_str(), sess.building_library);
271
272     // bad copy
273     let outputs = driver::build_output_filenames(input, &Some((*build_dir).clone()), &None,
274                                                  crate.attrs, sess);
275
276     debug!("Outputs are %? and output type = %?", outputs, sess.opts.output_type);
277     debug!("additional libraries:");
278     for lib in sess.opts.addl_lib_search_paths.iter() {
279         debug!("an additional library: %s", lib.to_str());
280     }
281     let analysis = driver::phase_3_run_analysis_passes(sess, crate);
282     let translation = driver::phase_4_translate_to_llvm(sess, crate,
283                                                         &analysis,
284                                                         outputs);
285     driver::phase_5_run_llvm_passes(sess, &translation, outputs);
286     if driver::stop_after_phase_5(sess) { return; }
287     driver::phase_6_link_output(sess, &translation, outputs);
288 }
289
290 #[cfg(windows)]
291 pub fn exe_suffix() -> ~str { ~".exe" }
292
293 #[cfg(target_os = "linux")]
294 #[cfg(target_os = "android")]
295 #[cfg(target_os = "freebsd")]
296 #[cfg(target_os = "macos")]
297 pub fn exe_suffix() -> ~str { ~"" }
298
299 // Called by build_crates
300 // FIXME (#4432): Use workcache to only compile when needed
301 pub fn compile_crate(ctxt: &Ctx, pkg_id: &PkgId,
302                      crate: &Path, dir: &Path,
303                      flags: &[~str], cfgs: &[~str], opt: bool,
304                      what: OutputType) -> bool {
305     debug!("compile_crate: crate=%s, dir=%s", crate.to_str(), dir.to_str());
306     debug!("compile_crate: short_name = %s, flags =...", pkg_id.to_str());
307     for fl in flags.iter() {
308         debug!("+++ %s", *fl);
309     }
310     compile_input(ctxt, pkg_id, crate, dir, flags, cfgs, opt, what)
311 }
312
313
314 /// Collect all `extern mod` directives in `c`, then
315 /// try to install their targets, failing if any target
316 /// can't be found.
317 pub fn find_and_install_dependencies(ctxt: &Ctx,
318                                  sess: session::Session,
319                                  workspace: &Path,
320                                  c: &ast::Crate,
321                                  save: @fn(Path)
322                                 ) {
323     // :-(
324     debug!("In find_and_install_dependencies...");
325     let my_workspace = (*workspace).clone();
326     let my_ctxt      = *ctxt;
327     do c.each_view_item() |vi: &ast::view_item| {
328         debug!("A view item!");
329         match vi.node {
330             // ignore metadata, I guess
331             ast::view_item_extern_mod(lib_ident, _, _) => {
332                 match my_ctxt.sysroot_opt {
333                     Some(ref x) => debug!("sysroot: %s", x.to_str()),
334                     None => debug!("No sysroot given")
335                 };
336                 let lib_name = sess.str_of(lib_ident);
337                 match find_library_in_search_path(my_ctxt.sysroot_opt, lib_name) {
338                     Some(installed_path) => {
339                         debug!("It exists: %s", installed_path.to_str());
340                     }
341                     None => {
342                         // Try to install it
343                         let pkg_id = PkgId::new(lib_name, &os::getcwd());
344                         my_ctxt.install(&my_workspace, &pkg_id);
345                         // Also, add an additional search path
346                         debug!("let installed_path...")
347                         let installed_path = target_library_in_workspace(&pkg_id,
348                                                                          &my_workspace).pop();
349                         debug!("Great, I installed %s, and it's in %s",
350                                lib_name, installed_path.to_str());
351                         save(installed_path);
352                     }
353                 }
354             }
355             // Ignore `use`s
356             _ => ()
357         }
358         true
359     };
360 }
361
362 #[cfg(windows)]
363 pub fn link_exe(_src: &Path, _dest: &Path) -> bool {
364     /* FIXME (#1768): Investigate how to do this on win32
365        Node wraps symlinks by having a .bat,
366        but that won't work with minGW. */
367
368     false
369 }
370
371 #[cfg(target_os = "linux")]
372 #[cfg(target_os = "android")]
373 #[cfg(target_os = "freebsd")]
374 #[cfg(target_os = "macos")]
375 pub fn link_exe(src: &Path, dest: &Path) -> bool {
376     use std::libc;
377     unsafe {
378         do src.to_c_str().with_ref |src_buf| {
379             do dest.to_c_str().with_ref |dest_buf| {
380                 libc::link(src_buf, dest_buf) == 0 as libc::c_int &&
381                     libc::chmod(dest_buf, 755) == 0 as libc::c_int
382             }
383         }
384     }
385 }
386
387 pub fn mk_string_lit(s: @str) -> ast::lit {
388     spanned {
389         node: ast::lit_str(s),
390         span: dummy_sp()
391     }
392 }
393
394 #[cfg(test)]
395 mod test {
396     use super::is_cmd;
397
398     #[test]
399     fn test_is_cmd() {
400         assert!(is_cmd("build"));
401         assert!(is_cmd("clean"));
402         assert!(is_cmd("do"));
403         assert!(is_cmd("info"));
404         assert!(is_cmd("install"));
405         assert!(is_cmd("prefer"));
406         assert!(is_cmd("test"));
407         assert!(is_cmd("uninstall"));
408         assert!(is_cmd("unprefer"));
409     }
410
411 }
412
413 // tjc: cheesy
414 fn debug_flags() -> ~[~str] { ~[] }
415 // static DEBUG_FLAGS: ~[~str] = ~[~"-Z", ~"time-passes"];