From 8e6e846d8a26e5a9d3aafd0bdcc18ed3ddf0cbca Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 20 Oct 2014 23:04:16 -0700 Subject: [PATCH] rustc: Implement -l and include! tweaks This is an implementation of the rustc bits of [RFC 403][rfc]. This adds a new flag to the compiler, `-l`, as well as tweaking the `include!` macro (and related source-centric macros). The compiler's new `-l` flag is used to link libraries in from the command line. This flag stacks with `#[link]` directives already found in the program. The purpose of this flag, also stated in the RFC, is to ease linking against native libraries which have wildly different requirements across platforms and even within distributions of one platform. This flag accepts a string of the form `NAME[:KIND]` where `KIND` is optional or one of dylib, static, or framework. This is roughly equivalent to if the equivalent `#[link]` directive were just written in the program. The `include!` macro has been modified to recursively expand macros to allow usage of `concat!` as an argument, for example. The use case spelled out in RFC 403 was for `env!` to be used as well to include compile-time generated files. The macro also received a bit of tweaking to allow it to expand to either an expression or a series of items, depending on what context it's used in. [rfc]: https://github.com/rust-lang/rfcs/pull/403 --- src/librustc/driver/config.rs | 27 +++++++++- src/librustc/metadata/creader.rs | 50 ++++++++++++------- src/librustc/metadata/cstore.rs | 2 +- src/libsyntax/ext/base.rs | 23 +++------ src/libsyntax/ext/source_util.rs | 35 ++++++++++--- .../auxiliary/macro-include-items-expr.rs | 13 +++++ .../auxiliary/macro-include-items-item.rs | 13 +++++ src/test/compile-fail/manual-link-bad-form.rs | 17 +++++++ src/test/compile-fail/manual-link-bad-kind.rs | 16 ++++++ .../compile-fail/manual-link-framework.rs | 17 +++++++ src/test/run-make/manual-link/Makefile | 7 +++ src/test/run-make/manual-link/bar.c | 1 + src/test/run-make/manual-link/foo.c | 1 + src/test/run-make/manual-link/foo.rs | 19 +++++++ src/test/run-make/manual-link/main.rs | 15 ++++++ src/test/run-pass/macro-include-items.rs | 18 +++++++ 16 files changed, 234 insertions(+), 40 deletions(-) create mode 100644 src/test/auxiliary/macro-include-items-expr.rs create mode 100644 src/test/auxiliary/macro-include-items-item.rs create mode 100644 src/test/compile-fail/manual-link-bad-form.rs create mode 100644 src/test/compile-fail/manual-link-bad-kind.rs create mode 100644 src/test/compile-fail/manual-link-framework.rs create mode 100644 src/test/run-make/manual-link/Makefile create mode 100644 src/test/run-make/manual-link/bar.c create mode 100644 src/test/run-make/manual-link/foo.c create mode 100644 src/test/run-make/manual-link/foo.rs create mode 100644 src/test/run-make/manual-link/main.rs create mode 100644 src/test/run-pass/macro-include-items.rs diff --git a/src/librustc/driver/config.rs b/src/librustc/driver/config.rs index 43687a31453d0..b07b58e56b5d5 100644 --- a/src/librustc/driver/config.rs +++ b/src/librustc/driver/config.rs @@ -20,6 +20,7 @@ use back::write; use back::target_strs; use back::{arm, x86, x86_64, mips, mipsel}; use lint; +use metadata::cstore; use syntax::abi; use syntax::ast; @@ -78,6 +79,7 @@ pub struct Options { // parsed code. It remains mutable in case its replacements wants to use // this. pub addl_lib_search_paths: RefCell>, + pub libs: Vec<(String, cstore::NativeLibaryKind)>, pub maybe_sysroot: Option, pub target_triple: String, // User-specified cfg meta items. The compiler itself will add additional @@ -130,6 +132,7 @@ pub fn basic_options() -> Options { externs: HashMap::new(), crate_name: None, alt_std_name: None, + libs: Vec::new(), } } @@ -575,6 +578,10 @@ pub fn optgroups() -> Vec { optflag("h", "help", "Display this message"), optmulti("", "cfg", "Configure the compilation environment", "SPEC"), optmulti("L", "", "Add a directory to the library search path", "PATH"), + optmulti("l", "", "Link the generated crate(s) to the specified native + library NAME. The optional KIND can be one of, + static, dylib, or framework. If omitted, dylib is + assumed.", "NAME[:KIND]"), optmulti("", "crate-type", "Comma separated list of types of crates for the compiler to emit", "[bin|lib|rlib|dylib|staticlib]"), @@ -767,6 +774,23 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { Path::new(s.as_slice()) }).collect(); + let libs = matches.opt_strs("l").into_iter().map(|s| { + let mut parts = s.as_slice().rsplitn(1, ':'); + let kind = parts.next().unwrap(); + let (name, kind) = match (parts.next(), kind) { + (None, name) | + (Some(name), "dylib") => (name, cstore::NativeUnknown), + (Some(name), "framework") => (name, cstore::NativeFramework), + (Some(name), "static") => (name, cstore::NativeStatic), + (_, s) => { + early_error(format!("unknown library kind `{}`, expected \ + one of dylib, framework, or static", + s).as_slice()); + } + }; + (name.to_string(), kind) + }).collect(); + let cfg = parse_cfgspecs(matches.opt_strs("cfg")); let test = matches.opt_present("test"); let write_dependency_info = (matches.opt_present("dep-info"), @@ -843,7 +867,8 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { color: color, externs: externs, crate_name: crate_name, - alt_std_name: None + alt_std_name: None, + libs: libs, } } diff --git a/src/librustc/metadata/creader.rs b/src/librustc/metadata/creader.rs index 43d0156d72770..100ccf78f3e81 100644 --- a/src/librustc/metadata/creader.rs +++ b/src/librustc/metadata/creader.rs @@ -52,7 +52,11 @@ pub fn read_crates(sess: &Session, visit_crate(&e, krate); visit::walk_crate(&mut e, krate); dump_crates(&sess.cstore); - warn_if_multiple_versions(sess.diagnostic(), &sess.cstore) + warn_if_multiple_versions(sess.diagnostic(), &sess.cstore); + + for &(ref name, kind) in sess.opts.libs.iter() { + register_native_lib(sess, None, name.clone(), kind); + } } impl<'a, 'v> visit::Visitor<'v> for Env<'a> { @@ -233,14 +237,7 @@ fn visit_item(e: &Env, i: &ast::Item) { Some(k) => { if k.equiv(&("static")) { cstore::NativeStatic - } else if (e.sess.targ_cfg.os == abi::OsMacos || - e.sess.targ_cfg.os == abi::OsiOS) && - k.equiv(&("framework")) { - cstore::NativeFramework } else if k.equiv(&("framework")) { - e.sess.span_err(m.span, - "native frameworks are only available \ - on OSX targets"); cstore::NativeUnknown } else { e.sess.span_err(m.span, @@ -263,15 +260,8 @@ fn visit_item(e: &Env, i: &ast::Item) { InternedString::new("foo") } }; - if n.get().is_empty() { - e.sess.span_err(m.span, - "#[link(name = \"\")] given with \ - empty name"); - } else { - e.sess - .cstore - .add_used_library(n.get().to_string(), kind); - } + register_native_lib(e.sess, Some(m.span), + n.get().to_string(), kind); } None => {} } @@ -281,6 +271,32 @@ fn visit_item(e: &Env, i: &ast::Item) { } } +fn register_native_lib(sess: &Session, span: Option, name: String, + kind: cstore::NativeLibaryKind) { + if name.as_slice().is_empty() { + match span { + Some(span) => { + sess.span_err(span, "#[link(name = \"\")] given with \ + empty name"); + } + None => { + sess.err("empty library name given via `-l`"); + } + } + return + } + let is_osx = sess.targ_cfg.os == abi::OsMacos || + sess.targ_cfg.os == abi::OsiOS; + if kind == cstore::NativeFramework && !is_osx { + let msg = "native frameworks are only available on OSX targets"; + match span { + Some(span) => sess.span_err(span, msg), + None => sess.err(msg), + } + } + sess.cstore.add_used_library(name, kind); +} + fn existing_match(e: &Env, name: &str, hash: Option<&Svh>) -> Option { let mut ret = None; diff --git a/src/librustc/metadata/cstore.rs b/src/librustc/metadata/cstore.rs index 1d1012d9e4f04..e8c5f6f4910e0 100644 --- a/src/librustc/metadata/cstore.rs +++ b/src/librustc/metadata/cstore.rs @@ -50,7 +50,7 @@ pub enum LinkagePreference { RequireStatic, } -#[deriving(PartialEq, FromPrimitive)] +#[deriving(PartialEq, FromPrimitive, Clone)] pub enum NativeLibaryKind { NativeStatic, // native static library (.a archive) NativeFramework, // OSX-specific diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index a8326e79ef368..d474cc8cf0a3e 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -675,26 +675,19 @@ pub fn check_zero_tts(cx: &ExtCtxt, /// Extract the string literal from the first token of `tts`. If this /// is not a string literal, emit an error and return None. -pub fn get_single_str_from_tts(cx: &ExtCtxt, +pub fn get_single_str_from_tts(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree], name: &str) -> Option { - if tts.len() != 1 { - cx.span_err(sp, format!("{} takes 1 argument.", name).as_slice()); - } else { - match tts[0] { - ast::TtToken(_, token::LitStr(ident)) => return Some(parse::str_lit(ident.as_str())), - ast::TtToken(_, token::LitStrRaw(ident, _)) => { - return Some(parse::raw_str_lit(ident.as_str())) - } - _ => { - cx.span_err(sp, - format!("{} requires a string.", name).as_slice()) - } - } + let mut p = cx.new_parser_from_tts(tts); + let ret = cx.expander().fold_expr(p.parse_expr()); + if p.token != token::Eof { + cx.span_err(sp, format!("{} takes 1 argument", name).as_slice()); } - None + expr_to_string(cx, ret, "argument must be a string literal").map(|(s, _)| { + s.get().to_string() + }) } /// Extract comma-separated expressions from `tts`. If there is a diff --git a/src/libsyntax/ext/source_util.rs b/src/libsyntax/ext/source_util.rs index 41967b0680cff..f192308440998 100644 --- a/src/libsyntax/ext/source_util.rs +++ b/src/libsyntax/ext/source_util.rs @@ -9,14 +9,16 @@ // except according to those terms. use ast; -use codemap; use codemap::{Pos, Span}; +use codemap; use ext::base::*; use ext::base; use ext::build::AstBuilder; -use parse; use parse::token; +use parse; use print::pprust; +use ptr::P; +use util::small_vector::SmallVector; use std::io::File; use std::rc::Rc; @@ -82,14 +84,14 @@ pub fn expand_mod(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree]) /// include! : parse the given file as an expr /// This is generally a bad idea because it's going to behave /// unhygienically. -pub fn expand_include(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree]) - -> Box { +pub fn expand_include<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[ast::TokenTree]) + -> Box { let file = match get_single_str_from_tts(cx, sp, tts, "include!") { Some(f) => f, None => return DummyResult::expr(sp), }; // The file will be added to the code map by the parser - let mut p = + let p = parse::new_sub_parser_from_file(cx.parse_sess(), cx.cfg(), &res_rel_file(cx, @@ -98,7 +100,28 @@ pub fn expand_include(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree]) true, None, sp); - base::MacExpr::new(p.parse_expr()) + + struct ExpandResult<'a> { + p: parse::parser::Parser<'a>, + } + impl<'a> base::MacResult for ExpandResult<'a> { + fn make_expr(mut self: Box>) -> Option> { + Some(self.p.parse_expr()) + } + fn make_items(mut self: Box>) + -> Option>> { + let mut ret = SmallVector::zero(); + loop { + match self.p.parse_item_with_outer_attributes() { + Some(item) => ret.push(item), + None => break + } + } + Some(ret) + } + } + + box ExpandResult { p: p } } // include_str! : read the given file, insert it as a literal string expr diff --git a/src/test/auxiliary/macro-include-items-expr.rs b/src/test/auxiliary/macro-include-items-expr.rs new file mode 100644 index 0000000000000..aea3c749930b6 --- /dev/null +++ b/src/test/auxiliary/macro-include-items-expr.rs @@ -0,0 +1,13 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-test: this is not a test + +1 diff --git a/src/test/auxiliary/macro-include-items-item.rs b/src/test/auxiliary/macro-include-items-item.rs new file mode 100644 index 0000000000000..da72aaef80591 --- /dev/null +++ b/src/test/auxiliary/macro-include-items-item.rs @@ -0,0 +1,13 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-test: this is not a test + +fn foo() { bar() } diff --git a/src/test/compile-fail/manual-link-bad-form.rs b/src/test/compile-fail/manual-link-bad-form.rs new file mode 100644 index 0000000000000..bd2a3eba0b5b8 --- /dev/null +++ b/src/test/compile-fail/manual-link-bad-form.rs @@ -0,0 +1,17 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags:-l :static +// error-pattern: empty library name given via `-l` + +fn main() { +} + + diff --git a/src/test/compile-fail/manual-link-bad-kind.rs b/src/test/compile-fail/manual-link-bad-kind.rs new file mode 100644 index 0000000000000..4614440ddafd5 --- /dev/null +++ b/src/test/compile-fail/manual-link-bad-kind.rs @@ -0,0 +1,16 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags:-l foo:bar +// error-pattern: unknown library kind `bar`, expected one of dylib, framework, or static + +fn main() { +} + diff --git a/src/test/compile-fail/manual-link-framework.rs b/src/test/compile-fail/manual-link-framework.rs new file mode 100644 index 0000000000000..96cc35049ee40 --- /dev/null +++ b/src/test/compile-fail/manual-link-framework.rs @@ -0,0 +1,17 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-macos +// ignore-ios +// compile-flags:-l foo:framework +// error-pattern: native frameworks are only available on OSX targets + +fn main() { +} diff --git a/src/test/run-make/manual-link/Makefile b/src/test/run-make/manual-link/Makefile new file mode 100644 index 0000000000000..d2a02adc9d4a4 --- /dev/null +++ b/src/test/run-make/manual-link/Makefile @@ -0,0 +1,7 @@ +-include ../tools.mk + +all: $(TMPDIR)/libbar.a + $(RUSTC) foo.rs -lbar:static + $(RUSTC) main.rs + $(call RUN,main) + diff --git a/src/test/run-make/manual-link/bar.c b/src/test/run-make/manual-link/bar.c new file mode 100644 index 0000000000000..e42599986781f --- /dev/null +++ b/src/test/run-make/manual-link/bar.c @@ -0,0 +1 @@ +void bar() {} diff --git a/src/test/run-make/manual-link/foo.c b/src/test/run-make/manual-link/foo.c new file mode 100644 index 0000000000000..e42599986781f --- /dev/null +++ b/src/test/run-make/manual-link/foo.c @@ -0,0 +1 @@ +void bar() {} diff --git a/src/test/run-make/manual-link/foo.rs b/src/test/run-make/manual-link/foo.rs new file mode 100644 index 0000000000000..d67a4057afbfc --- /dev/null +++ b/src/test/run-make/manual-link/foo.rs @@ -0,0 +1,19 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![crate_type = "rlib"] + +extern { + fn bar(); +} + +pub fn foo() { + unsafe { bar(); } +} diff --git a/src/test/run-make/manual-link/main.rs b/src/test/run-make/manual-link/main.rs new file mode 100644 index 0000000000000..756a47f386ae3 --- /dev/null +++ b/src/test/run-make/manual-link/main.rs @@ -0,0 +1,15 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +extern crate foo; + +fn main() { + foo::foo(); +} diff --git a/src/test/run-pass/macro-include-items.rs b/src/test/run-pass/macro-include-items.rs new file mode 100644 index 0000000000000..03eec668edde8 --- /dev/null +++ b/src/test/run-pass/macro-include-items.rs @@ -0,0 +1,18 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn bar() {} + +include!(concat!("", "", "../auxiliary/", "macro-include-items-item.rs")) + +fn main() { + foo(); + assert_eq!(include!(concat!("", "../auxiliary/", "macro-include-items-expr.rs")), 1u); +}