From 957d3e96e25d3c0b8a12939513ce24e30b50a88f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 31 Oct 2016 16:40:13 -0700 Subject: [PATCH] rustc: Implement #[link(cfg(..))] and crt-static This commit is an implementation of [RFC 1721] which adds a new target feature to the compiler, `crt-static`, which can be used to select how the C runtime for a target is linked. Most targets dynamically linke the C runtime by default with the notable exception of some of the musl targets. [RFC 1721]: https://github.com/rust-lang/rfcs/blob/master/text/1721-crt-static.md This commit first adds the new target-feature, `crt-static`. If enabled, then the `cfg(target_feature = "crt-static")` will be available. Targets like musl will have this enabled by default. This feature can be controlled through the standard target-feature interface, `-C target-feature=+crt-static` or `-C target-feature=-crt-static`. Next this adds an gated and unstable `#[link(cfg(..))]` feature to enable the `crt-static` semantics we want with libc. The exact behavior of this attribute is a little squishy, but it's intended to be a forever-unstable implementation detail of the liblibc crate. Specifically the `#[link(cfg(..))]` annotation means that the `#[link]` directive is only active in a compilation unit if that `cfg` value is satisfied. For example when compiling an rlib, these directives are just encoded and ignored for dylibs, and all staticlibs are continued to be put into the rlib as usual. When placing that rlib into a staticlib, executable, or dylib, however, the `cfg` is evaluated *as if it were defined in the final artifact* and the library is decided to be linked or not. Essentially, what'll happen is: * On MSVC with `-C target-feature=-crt-static`, the `msvcrt.lib` library will be linked to. * On MSVC with `-C target-feature=+crt-static`, the `libcmt.lib` library will be linked to. * On musl with `-C target-feature=-crt-static`, the object files in liblibc.rlib are removed and `-lc` is passed instead. * On musl with `-C target-feature=+crt-static`, the object files in liblibc.rlib are used and `-lc` is not passed. This commit does **not** include an update to the liblibc module to implement these changes. I plan to do that just after the 1.14.0 beta release is cut to ensure we get ample time to test this feature. cc #37406 --- src/liblibc | 2 +- src/librustc/middle/cstore.rs | 17 +- src/librustc_back/target/linux_musl_base.rs | 4 +- src/librustc_back/target/mod.rs | 6 + src/librustc_driver/target_features.rs | 29 ++++ src/librustc_metadata/creader.rs | 49 ++++-- src/librustc_metadata/cstore.rs | 12 +- src/librustc_metadata/cstore_impl.rs | 6 +- src/librustc_metadata/decoder.rs | 4 +- src/librustc_metadata/encoder.rs | 11 +- src/librustc_metadata/schema.rs | 4 +- src/librustc_trans/back/archive.rs | 27 ++- src/librustc_trans/back/link.rs | 154 +++++++++++++----- src/librustc_trans/back/write.rs | 11 +- src/libsyntax/feature_gate.rs | 3 + src/test/compile-fail/crt-static-gated.rs | 14 ++ src/test/compile-fail/link-cfg-gated.rs | 15 ++ src/test/run-make/link-cfg/Makefile | 22 +++ .../run-make/link-cfg/dep-with-staticlib.rs | 18 ++ src/test/run-make/link-cfg/dep.rs | 18 ++ src/test/run-make/link-cfg/no-deps.rs | 30 ++++ src/test/run-make/link-cfg/return1.c | 16 ++ src/test/run-make/link-cfg/return2.c | 16 ++ src/test/run-make/link-cfg/return3.c | 16 ++ src/test/run-make/link-cfg/with-deps.rs | 24 +++ .../run-make/link-cfg/with-staticlib-deps.rs | 24 +++ .../link-cfg-works-transitive-dylib.rs | 14 ++ .../link-cfg-works-transitive-rlib.rs | 17 ++ src/test/run-pass/crt-static-off-works.rs | 17 ++ src/test/run-pass/crt-static-on-works.rs | 16 ++ src/test/run-pass/link-cfg-works.rs | 23 +++ 31 files changed, 547 insertions(+), 92 deletions(-) create mode 100644 src/test/compile-fail/crt-static-gated.rs create mode 100644 src/test/compile-fail/link-cfg-gated.rs create mode 100644 src/test/run-make/link-cfg/Makefile create mode 100644 src/test/run-make/link-cfg/dep-with-staticlib.rs create mode 100644 src/test/run-make/link-cfg/dep.rs create mode 100644 src/test/run-make/link-cfg/no-deps.rs create mode 100644 src/test/run-make/link-cfg/return1.c create mode 100644 src/test/run-make/link-cfg/return2.c create mode 100644 src/test/run-make/link-cfg/return3.c create mode 100644 src/test/run-make/link-cfg/with-deps.rs create mode 100644 src/test/run-make/link-cfg/with-staticlib-deps.rs create mode 100644 src/test/run-pass/auxiliary/link-cfg-works-transitive-dylib.rs create mode 100644 src/test/run-pass/auxiliary/link-cfg-works-transitive-rlib.rs create mode 100644 src/test/run-pass/crt-static-off-works.rs create mode 100644 src/test/run-pass/crt-static-on-works.rs create mode 100644 src/test/run-pass/link-cfg-works.rs diff --git a/src/liblibc b/src/liblibc index 7d9b71f0971f8..6e8c1b490ccbe 160000 --- a/src/liblibc +++ b/src/liblibc @@ -1 +1 @@ -Subproject commit 7d9b71f0971f8fa196d864d7071f216a59036d6e +Subproject commit 6e8c1b490ccbe5e84d248bab883515bc85394b5f diff --git a/src/librustc/middle/cstore.rs b/src/librustc/middle/cstore.rs index 3583ccdb97bad..f61978271e7f6 100644 --- a/src/librustc/middle/cstore.rs +++ b/src/librustc/middle/cstore.rs @@ -89,6 +89,13 @@ pub enum NativeLibraryKind { NativeUnknown, // default way to specify a dynamic library } +#[derive(Clone, Hash, RustcEncodable, RustcDecodable)] +pub struct NativeLibrary { + pub kind: NativeLibraryKind, + pub name: String, + pub cfg: Option>, +} + /// The data we save and restore about an inlined item or method. This is not /// part of the AST that we parse from a file, but it becomes part of the tree /// that we trans. @@ -204,7 +211,7 @@ pub trait CrateStore<'tcx> { fn crate_hash(&self, cnum: CrateNum) -> Svh; fn crate_disambiguator(&self, cnum: CrateNum) -> InternedString; fn plugin_registrar_fn(&self, cnum: CrateNum) -> Option; - fn native_libraries(&self, cnum: CrateNum) -> Vec<(NativeLibraryKind, String)>; + fn native_libraries(&self, cnum: CrateNum) -> Vec; fn reachable_ids(&self, cnum: CrateNum) -> Vec; fn is_no_builtins(&self, cnum: CrateNum) -> bool; @@ -231,7 +238,7 @@ pub trait CrateStore<'tcx> { // This is basically a 1-based range of ints, which is a little // silly - I may fix that. fn crates(&self) -> Vec; - fn used_libraries(&self) -> Vec<(String, NativeLibraryKind)>; + fn used_libraries(&self) -> Vec; fn used_link_args(&self) -> Vec; // utility functions @@ -377,7 +384,7 @@ impl<'tcx> CrateStore<'tcx> for DummyCrateStore { -> InternedString { bug!("crate_disambiguator") } fn plugin_registrar_fn(&self, cnum: CrateNum) -> Option { bug!("plugin_registrar_fn") } - fn native_libraries(&self, cnum: CrateNum) -> Vec<(NativeLibraryKind, String)> + fn native_libraries(&self, cnum: CrateNum) -> Vec { bug!("native_libraries") } fn reachable_ids(&self, cnum: CrateNum) -> Vec { bug!("reachable_ids") } fn is_no_builtins(&self, cnum: CrateNum) -> bool { bug!("is_no_builtins") } @@ -412,7 +419,9 @@ impl<'tcx> CrateStore<'tcx> for DummyCrateStore { // This is basically a 1-based range of ints, which is a little // silly - I may fix that. fn crates(&self) -> Vec { vec![] } - fn used_libraries(&self) -> Vec<(String, NativeLibraryKind)> { vec![] } + fn used_libraries(&self) -> Vec { + vec![] + } fn used_link_args(&self) -> Vec { vec![] } // utility functions diff --git a/src/librustc_back/target/linux_musl_base.rs b/src/librustc_back/target/linux_musl_base.rs index d55907aeedfbc..18cca425a32c8 100644 --- a/src/librustc_back/target/linux_musl_base.rs +++ b/src/librustc_back/target/linux_musl_base.rs @@ -16,7 +16,6 @@ pub fn opts() -> TargetOptions { // Make sure that the linker/gcc really don't pull in anything, including // default objects, libs, etc. base.pre_link_args.push("-nostdlib".to_string()); - base.pre_link_args.push("-static".to_string()); // At least when this was tested, the linker would not add the // `GNU_EH_FRAME` program header to executables generated, which is required @@ -67,5 +66,8 @@ pub fn opts() -> TargetOptions { base.has_rpath = false; base.position_independent_executables = false; + // These targets statically link libc by default + base.crt_static_default = true; + base } diff --git a/src/librustc_back/target/mod.rs b/src/librustc_back/target/mod.rs index cc04582b19e28..f195ccb3f4292 100644 --- a/src/librustc_back/target/mod.rs +++ b/src/librustc_back/target/mod.rs @@ -373,6 +373,9 @@ pub struct TargetOptions { /// A blacklist of ABIs unsupported by the current target. Note that generic /// ABIs are considered to be supported on all platforms and cannot be blacklisted. pub abi_blacklist: Vec, + + /// Whether or not the CRT is statically linked by default. + pub crt_static_default: bool, } impl Default for TargetOptions { @@ -425,6 +428,7 @@ impl Default for TargetOptions { max_atomic_width: None, panic_strategy: PanicStrategy::Unwind, abi_blacklist: vec![], + crt_static_default: false, } } } @@ -585,6 +589,7 @@ impl Target { key!(no_integrated_as, bool); key!(max_atomic_width, Option); try!(key!(panic_strategy, PanicStrategy)); + key!(crt_static_default, bool); if let Some(array) = obj.find("abi-blacklist").and_then(Json::as_array) { for name in array.iter().filter_map(|abi| abi.as_string()) { @@ -745,6 +750,7 @@ impl ToJson for Target { target_option_val!(no_integrated_as); target_option_val!(max_atomic_width); target_option_val!(panic_strategy); + target_option_val!(crt_static_default); if default.abi_blacklist != self.options.abi_blacklist { d.insert("abi-blacklist".to_string(), self.options.abi_blacklist.iter() diff --git a/src/librustc_driver/target_features.rs b/src/librustc_driver/target_features.rs index ba51947a33301..d0ed81bcffcbc 100644 --- a/src/librustc_driver/target_features.rs +++ b/src/librustc_driver/target_features.rs @@ -12,6 +12,7 @@ use syntax::{ast, attr}; use llvm::LLVMRustHasFeature; use rustc::session::Session; use rustc_trans::back::write::create_target_machine; +use syntax::feature_gate::UnstableFeatures; use syntax::parse::token::InternedString; use syntax::parse::token::intern_and_get_ident as intern; use libc::c_char; @@ -47,4 +48,32 @@ pub fn add_configuration(cfg: &mut ast::CrateConfig, sess: &Session) { cfg.push(attr::mk_name_value_item_str(tf.clone(), intern(&feat[..feat.len() - 1]))) } } + + let mut requested_features = sess.opts.cg.target_feature.split(','); + let unstable_options = sess.opts.debugging_opts.unstable_options; + let is_nightly = UnstableFeatures::from_environment().is_nightly_build(); + let found_negative = requested_features.any(|r| r == "-crt-static"); + let found_positive = requested_features.any(|r| r == "+crt-static"); + + // If the target we're compiling for requests a static crt by default, + // then see if the `-crt-static` feature was passed to disable that. + // Otherwise if we don't have a static crt by default then see if the + // `+crt-static` feature was passed. + let crt_static = if sess.target.target.options.crt_static_default { + found_negative + } else { + found_positive + }; + + // If we switched from the default then that's only allowed on nightly, so + // gate that here. + if (found_positive || found_negative) && (!is_nightly || !unstable_options) { + sess.fatal("specifying the `crt-static` target feature is only allowed \ + on the nightly channel with `-Z unstable-options` passed \ + as well"); + } + + if crt_static { + cfg.push(attr::mk_name_value_item_str(tf.clone(), intern("crt-static"))); + } } diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs index 75944122f5c10..4298bb47fea62 100644 --- a/src/librustc_metadata/creader.rs +++ b/src/librustc_metadata/creader.rs @@ -23,6 +23,7 @@ use rustc::session::search_paths::PathKind; use rustc::middle; use rustc::middle::cstore::{CrateStore, validate_crate_name, ExternCrate}; use rustc::util::nodemap::{FxHashMap, FxHashSet}; +use rustc::middle::cstore::NativeLibrary; use rustc::hir::map::Definitions; use std::cell::{RefCell, Cell}; @@ -35,6 +36,7 @@ use syntax::ast; use syntax::abi::Abi; use syntax::attr; use syntax::ext::base::SyntaxExtension; +use syntax::feature_gate::{self, GateIssue}; use syntax::parse::token::{InternedString, intern}; use syntax_pos::{Span, DUMMY_SP}; use log; @@ -77,9 +79,8 @@ struct ExternCrateInfo { fn register_native_lib(sess: &Session, cstore: &CStore, span: Option, - name: String, - kind: cstore::NativeLibraryKind) { - if name.is_empty() { + lib: NativeLibrary) { + if lib.name.is_empty() { match span { Some(span) => { struct_span_err!(sess, span, E0454, @@ -94,17 +95,21 @@ fn register_native_lib(sess: &Session, return } let is_osx = sess.target.target.options.is_like_osx; - if kind == cstore::NativeFramework && !is_osx { + if lib.kind == cstore::NativeFramework && !is_osx { let msg = "native frameworks are only available on OSX targets"; match span { - Some(span) => { - span_err!(sess, span, E0455, - "{}", msg) - } + Some(span) => span_err!(sess, span, E0455, "{}", msg), None => sess.err(msg), } } - cstore.add_used_library(name, kind); + if lib.cfg.is_some() && !sess.features.borrow().link_cfg { + feature_gate::emit_feature_err(&sess.parse_sess, + "link_cfg", + span.unwrap(), + GateIssue::Language, + "is feature gated"); + } + cstore.add_used_library(lib); } // Extra info about a crate loaded for plugins or exported macros. @@ -635,9 +640,9 @@ impl<'a> CrateLoader<'a> { fn register_statically_included_foreign_items(&mut self) { let libs = self.cstore.get_used_libraries(); - for (lib, list) in self.foreign_item_map.iter() { - let is_static = libs.borrow().iter().any(|&(ref name, kind)| { - lib == name && kind == cstore::NativeStatic + for (foreign_lib, list) in self.foreign_item_map.iter() { + let is_static = libs.borrow().iter().any(|lib| { + *foreign_lib == lib.name && lib.kind == cstore::NativeStatic }); if is_static { for id in list { @@ -898,7 +903,18 @@ impl<'a> CrateLoader<'a> { InternedString::new("foo") } }; - register_native_lib(self.sess, self.cstore, Some(m.span), n.to_string(), kind); + let cfg = items.iter().find(|k| { + k.check_name("cfg") + }).and_then(|a| a.meta_item_list()); + let cfg = cfg.map(|list| { + list[0].meta_item().unwrap().clone() + }); + let lib = NativeLibrary { + name: n.to_string(), + kind: kind, + cfg: cfg, + }; + register_native_lib(self.sess, self.cstore, Some(m.span), lib); } // Finally, process the #[linked_from = "..."] attribute @@ -924,7 +940,12 @@ impl<'a> middle::cstore::CrateLoader for CrateLoader<'a> { } for &(ref name, kind) in &self.sess.opts.libs { - register_native_lib(self.sess, self.cstore, None, name.clone(), kind); + let lib = NativeLibrary { + name: name.clone(), + kind: kind, + cfg: None, + }; + register_native_lib(self.sess, self.cstore, None, lib); } self.register_statically_included_foreign_items(); } diff --git a/src/librustc_metadata/cstore.rs b/src/librustc_metadata/cstore.rs index 8c95e4aec0a04..37853b7473a65 100644 --- a/src/librustc_metadata/cstore.rs +++ b/src/librustc_metadata/cstore.rs @@ -31,7 +31,7 @@ use syntax::{ast, attr}; use syntax::ext::base::SyntaxExtension; use syntax_pos; -pub use rustc::middle::cstore::{NativeLibraryKind, LinkagePreference}; +pub use rustc::middle::cstore::{NativeLibrary, LinkagePreference}; pub use rustc::middle::cstore::{NativeStatic, NativeFramework, NativeUnknown}; pub use rustc::middle::cstore::{CrateSource, LinkMeta}; @@ -97,7 +97,7 @@ pub struct CStore { metas: RefCell>>, /// Map from NodeId's of local extern crate statements to crate numbers extern_mod_crate_map: RefCell>, - used_libraries: RefCell>, + used_libraries: RefCell>, used_link_args: RefCell>, statically_included_foreign_items: RefCell, pub inlined_item_cache: RefCell>>, @@ -212,12 +212,12 @@ impl CStore { libs } - pub fn add_used_library(&self, lib: String, kind: NativeLibraryKind) { - assert!(!lib.is_empty()); - self.used_libraries.borrow_mut().push((lib, kind)); + pub fn add_used_library(&self, lib: NativeLibrary) { + assert!(!lib.name.is_empty()); + self.used_libraries.borrow_mut().push(lib); } - pub fn get_used_libraries<'a>(&'a self) -> &'a RefCell> { + pub fn get_used_libraries(&self) -> &RefCell> { &self.used_libraries } diff --git a/src/librustc_metadata/cstore_impl.rs b/src/librustc_metadata/cstore_impl.rs index 83de8acdb6056..5419f9955e44e 100644 --- a/src/librustc_metadata/cstore_impl.rs +++ b/src/librustc_metadata/cstore_impl.rs @@ -14,7 +14,7 @@ use locator; use schema; use rustc::middle::cstore::{InlinedItem, CrateStore, CrateSource, DepKind, ExternCrate}; -use rustc::middle::cstore::{NativeLibraryKind, LinkMeta, LinkagePreference, LoadedMacro}; +use rustc::middle::cstore::{NativeLibrary, LinkMeta, LinkagePreference, LoadedMacro}; use rustc::hir::def::{self, Def}; use rustc::middle::lang_items; use rustc::session::Session; @@ -295,7 +295,7 @@ impl<'tcx> CrateStore<'tcx> for cstore::CStore { }) } - fn native_libraries(&self, cnum: CrateNum) -> Vec<(NativeLibraryKind, String)> + fn native_libraries(&self, cnum: CrateNum) -> Vec { self.get_crate_data(cnum).get_native_libraries() } @@ -524,7 +524,7 @@ impl<'tcx> CrateStore<'tcx> for cstore::CStore { result } - fn used_libraries(&self) -> Vec<(String, NativeLibraryKind)> + fn used_libraries(&self) -> Vec { self.get_used_libraries().borrow().clone() } diff --git a/src/librustc_metadata/decoder.rs b/src/librustc_metadata/decoder.rs index ba85544326f8f..6ffe53345332f 100644 --- a/src/librustc_metadata/decoder.rs +++ b/src/librustc_metadata/decoder.rs @@ -11,7 +11,7 @@ // Decoding metadata from a single crate's metadata use astencode::decode_inlined_item; -use cstore::{self, CrateMetadata, MetadataBlob, NativeLibraryKind}; +use cstore::{self, CrateMetadata, MetadataBlob, NativeLibrary}; use index::Index; use schema::*; @@ -980,7 +980,7 @@ impl<'a, 'tcx> CrateMetadata { } - pub fn get_native_libraries(&self) -> Vec<(NativeLibraryKind, String)> { + pub fn get_native_libraries(&self) -> Vec { self.root.native_libraries.decode(self).collect() } diff --git a/src/librustc_metadata/encoder.rs b/src/librustc_metadata/encoder.rs index 778a9d185e145..823c50e1e5fd6 100644 --- a/src/librustc_metadata/encoder.rs +++ b/src/librustc_metadata/encoder.rs @@ -13,7 +13,7 @@ use index::Index; use schema::*; use rustc::middle::cstore::{InlinedItemRef, LinkMeta}; -use rustc::middle::cstore::{LinkagePreference, NativeLibraryKind}; +use rustc::middle::cstore::{LinkagePreference, NativeLibrary}; use rustc::hir::def; use rustc::hir::def_id::{CrateNum, CRATE_DEF_INDEX, DefIndex, DefId}; use rustc::middle::dependency_format::Linkage; @@ -1134,14 +1134,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.lazy_seq_ref(&tcx.lang_items.missing)) } - fn encode_native_libraries(&mut self) -> LazySeq<(NativeLibraryKind, String)> { + fn encode_native_libraries(&mut self) -> LazySeq { let used_libraries = self.tcx.sess.cstore.used_libraries(); - self.lazy_seq(used_libraries.into_iter().filter_map(|(lib, kind)| { - match kind { - cstore::NativeStatic => None, // these libraries are not propagated - cstore::NativeFramework | cstore::NativeUnknown => Some((kind, lib)), - } - })) + self.lazy_seq(used_libraries) } fn encode_codemap(&mut self) -> LazySeq { diff --git a/src/librustc_metadata/schema.rs b/src/librustc_metadata/schema.rs index d7a5f7ad71544..5292a4a7c3768 100644 --- a/src/librustc_metadata/schema.rs +++ b/src/librustc_metadata/schema.rs @@ -14,7 +14,7 @@ use index; use rustc::hir; use rustc::hir::def::{self, CtorKind}; use rustc::hir::def_id::{DefIndex, DefId}; -use rustc::middle::cstore::{DepKind, LinkagePreference, NativeLibraryKind}; +use rustc::middle::cstore::{DepKind, LinkagePreference, NativeLibrary}; use rustc::middle::lang_items; use rustc::mir; use rustc::ty::{self, Ty}; @@ -175,7 +175,7 @@ pub struct CrateRoot { pub dylib_dependency_formats: LazySeq>, pub lang_items: LazySeq<(DefIndex, usize)>, pub lang_items_missing: LazySeq, - pub native_libraries: LazySeq<(NativeLibraryKind, String)>, + pub native_libraries: LazySeq, pub codemap: LazySeq, pub impls: LazySeq, pub reachable_ids: LazySeq, diff --git a/src/librustc_trans/back/archive.rs b/src/librustc_trans/back/archive.rs index e063209799f16..df8dd7750ae0c 100644 --- a/src/librustc_trans/back/archive.rs +++ b/src/librustc_trans/back/archive.rs @@ -145,8 +145,11 @@ impl<'a> ArchiveBuilder<'a> { /// /// This ignores adding the bytecode from the rlib, and if LTO is enabled /// then the object file also isn't added. - pub fn add_rlib(&mut self, rlib: &Path, name: &str, lto: bool) - -> io::Result<()> { + pub fn add_rlib(&mut self, + rlib: &Path, + name: &str, + lto: bool, + skip_objects: bool) -> io::Result<()> { // Ignoring obj file starting with the crate name // as simple comparison is not enough - there // might be also an extra name suffix @@ -159,9 +162,23 @@ impl<'a> ArchiveBuilder<'a> { self.config.sess.cstore.metadata_filename().to_owned(); self.add_archive(rlib, move |fname: &str| { - let skip_obj = lto && fname.starts_with(&obj_start) - && fname.ends_with(".o"); - skip_obj || fname.ends_with(bc_ext) || fname == metadata_filename + if fname.ends_with(bc_ext) || fname == metadata_filename { + return true + } + + // Don't include Rust objects if LTO is enabled + if lto && fname.starts_with(&obj_start) && fname.ends_with(".o") { + return true + } + + // Otherwise if this is *not* a rust object and we're skipping + // objects then skip this file + if skip_objects && (!fname.starts_with(&obj_start) || !fname.ends_with(".o")) { + return true + } + + // ok, don't skip this + return false }) } diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index ad8e0c1ee59f6..95d63311ee6e4 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -19,7 +19,7 @@ use session::config::{OutputFilenames, Input, OutputType}; use session::filesearch; use session::search_paths::PathKind; use session::Session; -use middle::cstore::{self, LinkMeta}; +use middle::cstore::{self, LinkMeta, NativeLibrary}; use middle::cstore::{LinkagePreference, NativeLibraryKind}; use middle::dependency_format::Linkage; use CrateTranslation; @@ -43,6 +43,7 @@ use std::process::Command; use std::str; use flate; use syntax::ast; +use syntax::attr; use syntax_pos::Span; // RLIB LLVM-BYTECODE OBJECT LAYOUT @@ -406,12 +407,29 @@ fn link_rlib<'a>(sess: &'a Session, ab.add_file(obj); } - for (l, kind) in sess.cstore.used_libraries() { - match kind { - NativeLibraryKind::NativeStatic => ab.add_native_library(&l), + // Note that in this loop we are ignoring the value of `lib.cfg`. That is, + // we may not be configured to actually include a static library if we're + // adding it here. That's because later when we consume this rlib we'll + // decide whether we actually needed the static library or not. + // + // To do this "correctly" we'd need to keep track of which libraries added + // which object files to the archive. We don't do that here, however. The + // #[link(cfg(..))] feature is unstable, though, and only intended to get + // liblibc working. In that sense the check below just indicates that if + // there are any libraries we want to omit object files for at link time we + // just exclude all custom object files. + // + // Eventually if we want to stabilize or flesh out the #[link(cfg(..))] + // feature then we'll need to figure out how to record what objects were + // loaded from the libraries found here and then encode that into the + // metadata of the rlib we're generating somehow. + for lib in sess.cstore.used_libraries() { + match lib.kind { + NativeLibraryKind::NativeStatic => {} NativeLibraryKind::NativeFramework | - NativeLibraryKind::NativeUnknown => {} + NativeLibraryKind::NativeUnknown => continue, } + ab.add_native_library(&lib.name); } // After adding all files to the archive, we need to update the @@ -578,10 +596,28 @@ fn link_staticlib(sess: &Session, objects: &[PathBuf], out_filename: &Path, each_linked_rlib(sess, &mut |cnum, path| { let name = sess.cstore.crate_name(cnum); - ab.add_rlib(path, &name, sess.lto()).unwrap(); - let native_libs = sess.cstore.native_libraries(cnum); - all_native_libs.extend(native_libs); + + // Here when we include the rlib into our staticlib we need to make a + // decision whether to include the extra object files along the way. + // These extra object files come from statically included native + // libraries, but they may be cfg'd away with #[link(cfg(..))]. + // + // This unstable feature, though, only needs liblibc to work. The only + // use case there is where musl is statically included in liblibc.rlib, + // so if we don't want the included version we just need to skip it. As + // a result the logic here is that if *any* linked library is cfg'd away + // we just skip all object files. + // + // Clearly this is not sufficient for a general purpose feature, and + // we'd want to read from the library's metadata to determine which + // object files come from where and selectively skip them. + let skip_object_files = native_libs.iter().any(|lib| { + lib.kind == NativeLibraryKind::NativeStatic && !relevant_lib(sess, lib) + }); + ab.add_rlib(path, &name, sess.lto(), skip_object_files).unwrap(); + + all_native_libs.extend(sess.cstore.native_libraries(cnum)); }); ab.update_symbols(); @@ -594,13 +630,14 @@ fn link_staticlib(sess: &Session, objects: &[PathBuf], out_filename: &Path, platforms, and so may need to be preserved"); } - for &(kind, ref lib) in &all_native_libs { - let name = match kind { - NativeLibraryKind::NativeStatic => "static library", + for lib in all_native_libs.iter().filter(|l| relevant_lib(sess, l)) { + let name = match lib.kind { NativeLibraryKind::NativeUnknown => "library", NativeLibraryKind::NativeFramework => "framework", + // These are included, no need to print them + NativeLibraryKind::NativeStatic => continue, }; - sess.note_without_error(&format!("{}: {}", name, *lib)); + sess.note_without_error(&format!("{}: {}", name, lib.name)); } } @@ -876,14 +913,12 @@ fn add_local_native_libraries(cmd: &mut Linker, sess: &Session) { } }); - let libs = sess.cstore.used_libraries(); - - let staticlibs = libs.iter().filter_map(|&(ref l, kind)| { - if kind == NativeLibraryKind::NativeStatic {Some(l)} else {None} - }); - let others = libs.iter().filter(|&&(_, kind)| { - kind != NativeLibraryKind::NativeStatic + let pair = sess.cstore.used_libraries().into_iter().filter(|l| { + relevant_lib(sess, l) + }).partition(|lib| { + lib.kind == NativeLibraryKind::NativeStatic }); + let (staticlibs, others): (Vec<_>, Vec<_>) = pair; // Some platforms take hints about whether a library is static or dynamic. // For those that support this, we ensure we pass the option if the library @@ -899,15 +934,15 @@ fn add_local_native_libraries(cmd: &mut Linker, sess: &Session) { // don't otherwise explicitly reference them. This can occur for // libraries which are just providing bindings, libraries with generic // functions, etc. - cmd.link_whole_staticlib(l, &search_path); + cmd.link_whole_staticlib(&l.name, &search_path); } cmd.hint_dynamic(); - for &(ref l, kind) in others { - match kind { - NativeLibraryKind::NativeUnknown => cmd.link_dylib(l), - NativeLibraryKind::NativeFramework => cmd.link_framework(l), + for lib in others { + match lib.kind { + NativeLibraryKind::NativeUnknown => cmd.link_dylib(&lib.name), + NativeLibraryKind::NativeFramework => cmd.link_framework(&lib.name), NativeLibraryKind::NativeStatic => bug!(), } } @@ -1017,7 +1052,16 @@ fn add_upstream_rust_crates(cmd: &mut Linker, cnum: CrateNum) { let src = sess.cstore.used_crate_source(cnum); let cratepath = &src.rlib.unwrap().0; - if !sess.lto() && crate_type != config::CrateTypeDylib { + + // See the comment above in `link_staticlib` and `link_rlib` for why if + // there's a static library that's not relevant we skip all object + // files. + let native_libs = sess.cstore.native_libraries(cnum); + let skip_native = native_libs.iter().any(|lib| { + lib.kind == NativeLibraryKind::NativeStatic && !relevant_lib(sess, lib) + }); + + if !sess.lto() && crate_type != config::CrateTypeDylib && !skip_native { cmd.link_rlib(&fix_windows_verbatim_for_gcc(cratepath)); return } @@ -1029,33 +1073,42 @@ fn add_upstream_rust_crates(cmd: &mut Linker, time(sess.time_passes(), &format!("altering {}.rlib", name), || { let cfg = archive_config(sess, &dst, Some(cratepath)); let mut archive = ArchiveBuilder::new(cfg); - archive.remove_file(sess.cstore.metadata_filename()); archive.update_symbols(); let mut any_objects = false; for f in archive.src_files() { - if f.ends_with("bytecode.deflate") { + if f.ends_with("bytecode.deflate") || + f == sess.cstore.metadata_filename() { archive.remove_file(&f); continue } + let canonical = f.replace("-", "_"); let canonical_name = name.replace("-", "_"); + let is_rust_object = + canonical.starts_with(&canonical_name) && { + let num = &f[name.len()..f.len() - 2]; + num.len() > 0 && num[1..].parse::().is_ok() + }; + + // If we've been requested to skip all native object files + // (those not generated by the rust compiler) then we can skip + // this file. See above for why we may want to do this. + let skip_because_cfg_say_so = skip_native && !is_rust_object; + // If we're performing LTO and this is a rust-generated object // file, then we don't need the object file as it's part of the // LTO module. Note that `#![no_builtins]` is excluded from LTO, // though, so we let that object file slide. - if sess.lto() && - !sess.cstore.is_no_builtins(cnum) && - canonical.starts_with(&canonical_name) && - canonical.ends_with(".o") { - let num = &f[name.len()..f.len() - 2]; - if num.len() > 0 && num[1..].parse::().is_ok() { - archive.remove_file(&f); - continue - } + let skip_because_lto = sess.lto() && is_rust_object && + !sess.cstore.is_no_builtins(cnum); + + if skip_because_cfg_say_so || skip_because_lto { + archive.remove_file(&f); + } else { + any_objects = true; } - any_objects = true; } if !any_objects { @@ -1127,15 +1180,26 @@ fn add_upstream_native_libraries(cmd: &mut Linker, sess: &Session) { // the paths. let crates = sess.cstore.used_crates(LinkagePreference::RequireStatic); for (cnum, _) in crates { - let libs = sess.cstore.native_libraries(cnum); - for &(kind, ref lib) in &libs { - match kind { - NativeLibraryKind::NativeUnknown => cmd.link_dylib(lib), - NativeLibraryKind::NativeFramework => cmd.link_framework(lib), - NativeLibraryKind::NativeStatic => { - bug!("statics shouldn't be propagated"); - } + for lib in sess.cstore.native_libraries(cnum) { + if !relevant_lib(sess, &lib) { + continue + } + match lib.kind { + NativeLibraryKind::NativeUnknown => cmd.link_dylib(&lib.name), + NativeLibraryKind::NativeFramework => cmd.link_framework(&lib.name), + + // ignore statically included native libraries here as we've + // already included them when we included the rust library + // previously + NativeLibraryKind::NativeStatic => {} } } } } + +fn relevant_lib(sess: &Session, lib: &NativeLibrary) -> bool { + match lib.cfg { + Some(ref cfg) => attr::cfg_matches(cfg, &sess.parse_sess, None), + None => true, + } +} diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index 9012914deeb09..01eea08c50bc5 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -147,7 +147,16 @@ impl Emitter for SharedEmitter { // arise as some of intrinsics are converted into function calls // and nobody provides implementations those functions fn target_feature(sess: &Session) -> String { - format!("{},{}", sess.target.target.options.features, sess.opts.cg.target_feature) + let rustc_features = [ + "crt-static", + ]; + let requested_features = sess.opts.cg.target_feature.split(','); + let llvm_features = requested_features.filter(|f| { + !rustc_features.iter().any(|s| f.contains(s)) + }); + format!("{},{}", + sess.target.target.options.features, + llvm_features.collect::>().join(",")) } fn get_llvm_opt_level(optimize: config::OptLevel) -> llvm::CodeGenOptLevel { diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 9116b392f1784..27f720b760998 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -311,6 +311,9 @@ declare_features! ( // Allows using `Self` and associated types in struct expressions and patterns. (active, more_struct_aliases, "1.14.0", Some(37544)), + + // Allows #[link(..., cfg(..))] + (active, link_cfg, "1.14.0", Some(37406)), ); declare_features! ( diff --git a/src/test/compile-fail/crt-static-gated.rs b/src/test/compile-fail/crt-static-gated.rs new file mode 100644 index 0000000000000..6c7c60b653a25 --- /dev/null +++ b/src/test/compile-fail/crt-static-gated.rs @@ -0,0 +1,14 @@ +// Copyright 2016 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:-C target-feature=+crt-static +// error-pattern: specifying the `crt-static` target feature is only allowed + +fn main() {} diff --git a/src/test/compile-fail/link-cfg-gated.rs b/src/test/compile-fail/link-cfg-gated.rs new file mode 100644 index 0000000000000..27918a27caf50 --- /dev/null +++ b/src/test/compile-fail/link-cfg-gated.rs @@ -0,0 +1,15 @@ +// Copyright 2016 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. + +#[link(name = "foo", cfg(foo))] +//~^ ERROR: is feature gated +extern {} + +fn main() {} diff --git a/src/test/run-make/link-cfg/Makefile b/src/test/run-make/link-cfg/Makefile new file mode 100644 index 0000000000000..4abc0caa69864 --- /dev/null +++ b/src/test/run-make/link-cfg/Makefile @@ -0,0 +1,22 @@ +-include ../tools.mk + +all: $(call DYLIB,return1) $(call DYLIB,return2) $(call NATIVE_STATICLIB,return3) + ls $(TMPDIR) + $(RUSTC) --print cfg --target x86_64-unknown-linux-musl | grep crt-static + + $(RUSTC) no-deps.rs --cfg foo + $(call RUN,no-deps) + $(RUSTC) no-deps.rs --cfg bar + $(call RUN,no-deps) + + $(RUSTC) dep.rs + $(RUSTC) with-deps.rs --cfg foo + $(call RUN,with-deps) + $(RUSTC) with-deps.rs --cfg bar + $(call RUN,with-deps) + + $(RUSTC) dep-with-staticlib.rs + $(RUSTC) with-staticlib-deps.rs --cfg foo + $(call RUN,with-staticlib-deps) + $(RUSTC) with-staticlib-deps.rs --cfg bar + $(call RUN,with-staticlib-deps) diff --git a/src/test/run-make/link-cfg/dep-with-staticlib.rs b/src/test/run-make/link-cfg/dep-with-staticlib.rs new file mode 100644 index 0000000000000..ecc2365ddb06d --- /dev/null +++ b/src/test/run-make/link-cfg/dep-with-staticlib.rs @@ -0,0 +1,18 @@ +// Copyright 2016 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. + +#![feature(link_cfg)] +#![crate_type = "rlib"] + +#[link(name = "return1", cfg(foo))] +#[link(name = "return3", kind = "static", cfg(bar))] +extern { + pub fn my_function() -> i32; +} diff --git a/src/test/run-make/link-cfg/dep.rs b/src/test/run-make/link-cfg/dep.rs new file mode 100644 index 0000000000000..7da879c2bfa2b --- /dev/null +++ b/src/test/run-make/link-cfg/dep.rs @@ -0,0 +1,18 @@ +// Copyright 2016 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. + +#![feature(link_cfg)] +#![crate_type = "rlib"] + +#[link(name = "return1", cfg(foo))] +#[link(name = "return2", cfg(bar))] +extern { + pub fn my_function() -> i32; +} diff --git a/src/test/run-make/link-cfg/no-deps.rs b/src/test/run-make/link-cfg/no-deps.rs new file mode 100644 index 0000000000000..6b1141067440b --- /dev/null +++ b/src/test/run-make/link-cfg/no-deps.rs @@ -0,0 +1,30 @@ +// Copyright 2016 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. + +#![feature(link_cfg)] + +#[link(name = "return1", cfg(foo))] +#[link(name = "return2", cfg(bar))] +extern { + fn my_function() -> i32; +} + +fn main() { + unsafe { + let v = my_function(); + if cfg!(foo) { + assert_eq!(v, 1); + } else if cfg!(bar) { + assert_eq!(v, 2); + } else { + panic!("unknown"); + } + } +} diff --git a/src/test/run-make/link-cfg/return1.c b/src/test/run-make/link-cfg/return1.c new file mode 100644 index 0000000000000..a2a3d051dd14f --- /dev/null +++ b/src/test/run-make/link-cfg/return1.c @@ -0,0 +1,16 @@ +// Copyright 2016 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. + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int my_function() { + return 1; +} diff --git a/src/test/run-make/link-cfg/return2.c b/src/test/run-make/link-cfg/return2.c new file mode 100644 index 0000000000000..d6ddcccf2fb7b --- /dev/null +++ b/src/test/run-make/link-cfg/return2.c @@ -0,0 +1,16 @@ +// Copyright 2016 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. + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int my_function() { + return 2; +} diff --git a/src/test/run-make/link-cfg/return3.c b/src/test/run-make/link-cfg/return3.c new file mode 100644 index 0000000000000..6a3b695f20811 --- /dev/null +++ b/src/test/run-make/link-cfg/return3.c @@ -0,0 +1,16 @@ +// Copyright 2016 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. + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int my_function() { + return 3; +} diff --git a/src/test/run-make/link-cfg/with-deps.rs b/src/test/run-make/link-cfg/with-deps.rs new file mode 100644 index 0000000000000..799555c500a16 --- /dev/null +++ b/src/test/run-make/link-cfg/with-deps.rs @@ -0,0 +1,24 @@ +// Copyright 2016 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 dep; + +fn main() { + unsafe { + let v = dep::my_function(); + if cfg!(foo) { + assert_eq!(v, 1); + } else if cfg!(bar) { + assert_eq!(v, 2); + } else { + panic!("unknown"); + } + } +} diff --git a/src/test/run-make/link-cfg/with-staticlib-deps.rs b/src/test/run-make/link-cfg/with-staticlib-deps.rs new file mode 100644 index 0000000000000..33a9c7720e268 --- /dev/null +++ b/src/test/run-make/link-cfg/with-staticlib-deps.rs @@ -0,0 +1,24 @@ +// Copyright 2016 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 dep_with_staticlib; + +fn main() { + unsafe { + let v = dep_with_staticlib::my_function(); + if cfg!(foo) { + assert_eq!(v, 1); + } else if cfg!(bar) { + assert_eq!(v, 3); + } else { + panic!("unknown"); + } + } +} diff --git a/src/test/run-pass/auxiliary/link-cfg-works-transitive-dylib.rs b/src/test/run-pass/auxiliary/link-cfg-works-transitive-dylib.rs new file mode 100644 index 0000000000000..d41fd490f58e2 --- /dev/null +++ b/src/test/run-pass/auxiliary/link-cfg-works-transitive-dylib.rs @@ -0,0 +1,14 @@ +// Copyright 2016 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. + +#![feature(link_cfg)] + +#[link(name = "foo", cfg(foo))] +extern {} diff --git a/src/test/run-pass/auxiliary/link-cfg-works-transitive-rlib.rs b/src/test/run-pass/auxiliary/link-cfg-works-transitive-rlib.rs new file mode 100644 index 0000000000000..9f096c351fbe3 --- /dev/null +++ b/src/test/run-pass/auxiliary/link-cfg-works-transitive-rlib.rs @@ -0,0 +1,17 @@ +// Copyright 2016 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. + +// no-prefer-dynamic + +#![feature(link_cfg)] +#![crate_type = "rlib"] + +#[link(name = "foo", cfg(foo))] +extern {} diff --git a/src/test/run-pass/crt-static-off-works.rs b/src/test/run-pass/crt-static-off-works.rs new file mode 100644 index 0000000000000..c94c877c12c6d --- /dev/null +++ b/src/test/run-pass/crt-static-off-works.rs @@ -0,0 +1,17 @@ +// Copyright 2016 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:-C target-feature=-crt-static -Z unstable-options +// ignore-musl - requires changing the linker which is hard + +#![feature(cfg_target_feature)] + +#[cfg(not(target_feature = "crt-static"))] +fn main() {} diff --git a/src/test/run-pass/crt-static-on-works.rs b/src/test/run-pass/crt-static-on-works.rs new file mode 100644 index 0000000000000..ae8e5f6297048 --- /dev/null +++ b/src/test/run-pass/crt-static-on-works.rs @@ -0,0 +1,16 @@ +// Copyright 2016 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:-C target-feature=+crt-static -Z unstable-options + +#![feature(cfg_target_feature)] + +#[cfg(target_feature = "crt-static")] +fn main() {} diff --git a/src/test/run-pass/link-cfg-works.rs b/src/test/run-pass/link-cfg-works.rs new file mode 100644 index 0000000000000..7db948c7daa9f --- /dev/null +++ b/src/test/run-pass/link-cfg-works.rs @@ -0,0 +1,23 @@ +// Copyright 2016 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. + +// aux-build:link-cfg-works-transitive-rlib.rs +// aux-build:link-cfg-works-transitive-dylib.rs + +#![feature(link_cfg)] + +extern crate link_cfg_works_transitive_rlib; +extern crate link_cfg_works_transitive_dylib; + +#[link(name = "foo", cfg(foo))] +extern {} + +fn main() {} +