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