Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional compilation based on crate_type #20267

Open
BenTheElder opened this issue Dec 28, 2014 · 19 comments
Open

Conditional compilation based on crate_type #20267

BenTheElder opened this issue Dec 28, 2014 · 19 comments
Labels
A-attributes Area: Attributes (`#[…]`, `#![…]`) A-driver Area: rustc_driver that ties everything together into the `rustc` compiler C-feature-request Category: A feature request, i.e: not implemented / a PR. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@BenTheElder
Copy link

Ideally we should be able to do something like this:
Cargo.toml:

.....
[lib]
path = "src/lib.rs"
crate-type = ["rlib","dylib"]
.....

src/lib.rs:

....
#[no_mangle]
#[cfg(crate_type="dylib")]
pub unsafe extern "C" fn .....

To allow only exporting the c api in the dylib or perhaps in the dylib and static lib and not in the rust code.
This would be consistent with being able to build multiple library formats but allow the unsafe c methods to not be exported to rust code.

I checked against: http://doc.rust-lang.org/reference.html#conditional-compilation
and did some experimentation myself, with no luck.

If anyone has any better suggestions, I'd love to hear them.

I'm looking to generate very similar rust and c libraries with similar apis where the c library is just a c style wrapper around the rust api, also written in rust. Ideally i'd keep this in a single source and conditionally define the c api's in the dylib for loading from various languages with c ffi's and exclude the c api from the rust lib.

@BenTheElder
Copy link
Author

I've since rewritten all of the wrapper functions I intended to exclude from the rlib to be totally safe and documented them with their c equivalent definitions. I plan to just ensure the documentation notes that the wrapper functions are not intended to be used from rust and that the implementation defined mangled methods should be preferred.

I'm leaving this open for now, but feel free to close it. It might still be useful to have, but I've given up on it for now rather than complicate my project structure further (E.G. having a rust lib and a seperate wrapper lib), in favor of better documentation and safer code.

@kmcallister kmcallister added the A-driver Area: rustc_driver that ties everything together into the `rustc` compiler label Jan 16, 2015
@Stebalien
Copy link
Contributor

Triage: I believe this is fixed (see https://doc.rust-lang.org/reference.html#linkage).

@steveklabnik
Copy link
Member

I believe so as well, and given that @BenTheElder found another way in the meantime, and nobody else has commented in a year, let's give it a close.

@ccll
Copy link

ccll commented Feb 22, 2017

This is not working, at least on these versions as I tried:
rustc 1.17.0-nightly (a17e5e2 2017-02-20)
rustc 1.15.1 (021bd29 2017-02-08)
rustc 1.15.0 (10893a9 2017-01-19)
rustc 1.14.0 (e8a0123 2016-12-16)

Possibly a regression?

Details
I'm working on a Windows project, where one crate needs to be an DLL ("cdylib"), and the function named "DllMain" should be exported as a C function to do some initialization when the DLL is loaded. In the meanwhile this crate contains some type definitions used by other high-level crates, so this crate should be built to both "rlib" and "cdylib", and the "rlib" version should NOT export "DllMain" as the other high-level crates may themselves be an "cdylib" and has their own version of "DllMain" exported.
But as I added the conditional compilation as stated above, the function "DllMain" just disappears from the DLL's exported symbol table.

here is an shorter version of the snippet:

// lib.rs
#[no_mangle]
#[cfg(crate_type="cdylib")]
pub extern "stdcall" fn DllMain() {

}
# Cargo.toml
...
[lib]
crate_type = ["rlib", "cdylib"]
...

@tmccombs
Copy link
Contributor

I don't see anything about this in the link to the documentation.

@nbigaouette-eai
Copy link

I can't find anything about this in the reference either.

Using #[cfg(crate_type="cdylib")] to include something only when cdylib crate is being built (with crate_type = ["rlib", "cdylib"] in Cargo.toml) does not work for me either.

I would like to do this to not have to expose an unsage API meant to be consumed by FFI/Python.

@dtolnay
Copy link
Member

dtolnay commented Dec 29, 2018

Reopening because I was looking for this today and it doesn't seem like it was ever implemented.

I don't think an RFC would be required for this.

@dtolnay dtolnay reopened this Dec 29, 2018
@pheki
Copy link
Contributor

pheki commented Jul 27, 2019

I'm working on this! Can I be assigned?

@pheki
Copy link
Contributor

pheki commented Aug 2, 2019

I did implement this, but I'm not sure it is possible to be intuitive as rustc allows multiple crate types in the same session.

When you compile a crate with crate-type = ["rlib","cdylib"], cargo generates a single rustc call with both --crate-type rlib and --crate-type cdylib, which would make both #[cfg(crate_type="rlib")] and #[cfg(crate_type="cdylib")] compile...

I can create a PR anyone's interested.

@jonas-schievink jonas-schievink added A-attributes Area: Attributes (`#[…]`, `#![…]`) C-feature-request Category: A feature request, i.e: not implemented / a PR. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Aug 13, 2019
@osa1
Copy link
Contributor

osa1 commented Dec 23, 2020

A use case for this is when I have a no_std static library (i.e. staticlib crate type, I build it to .a and link it with external code), but I also want to use it in another Rust executable create for testing and want to use std in the testing crate. So I generate both staticlib and rlib.

To be able to generate .a (staticlib) I need to define a panic_handler but if I do that I can't build the crate to rlib (or maybe I can but you can't use it in my test crate as I'll have multiple panic_handlers).

With this feature I could do

#[cfg(crate_type = "staticlib")]
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
    ...
}

which would make it possible to build the crate to both rlib and staticlib.

@guilhermewerner
Copy link

Another use case is the creation of plugins, where an application can dynamically load the plugins via an extern "C" function, but also be able to use the plugins statically, like a normal rust library.

That way we could disable the dynamic loading function in the plugin, to be able to statically link.

A quick way to solve this is to create two crates, one rlib, with the api in rust, and another cdylib, containing the function to load the plugin. Although this method is not very ergonomic, it would be much better to be able to use conditional compilation to control whether or not the extern function is defined.

This has already been mentioned in: eclipse-zenoh/zenoh#89 (comment)

@dabretin
Copy link

It seems that using #![crate_type="..."] in top of different source files could solve the problem...

static_lib.rs:

#![allow(unused_attributes)]
#![crate_type="lib"]
...

dynamic_lib.rs:

#![allow(unused_attributes)]
#![crate_type="cdylib"]
...

lib.rs:

mod static_lib;
mod dynamic_lib;
...

@guilhermewerner
Copy link

guilhermewerner commented Aug 25, 2021

Unfortunately this doesn't work with cargo.

But I think rust-lang/cargo#8628 can solve this and other problems.

@js2xxx
Copy link

js2xxx commented Feb 7, 2022

@dabretin 's method seems not working for implementing conditional #[panic_handler] at least in my case. I also tried using unstable #[linkage = "weak"] but not working either. I wonder if there are some other alternatives because I don't want to duplicate dozens of interface functions in different crates...

@cr1901
Copy link
Contributor

cr1901 commented Nov 13, 2022

I ended up needing something like this for crates that's intended to be used from C, but can also in principle be used in a Rust binary crate:

All but one of the crates define an extern "C" { static mut GLOBAL: Foo } if compiled as an rlib. The remaining rlib and each crate compiled as a staticlib define #[no_mangle] static mut GLOBAL: Foo = Foo::new(). This works for staticlibs because ELF allows global definitions to be preempted by default (embedded crate, possibly unsound with linkers that don't allow preemption?).

Right now, I have all crates do extern "C" { static mut GLOBAL: Foo } and tell the user "they need to define a GLOBAL in the main app". But the user not having to do that would be better ergonomics IMO.

@tgross35
Copy link
Contributor

@pheki did you ever make the PR, or would you be able to? Even if there are merge conflicts or it doesn't work, it might be a good starting point for somebody else.

Bit of an annoying issue when you stumble upon this. You could workaround using features instead, if you pass them manually while compiling.

Gist to a playground example of what likely should work: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=18fcde81a38b7e8cb6f3f62fd5df851a

@pheki
Copy link
Contributor

pheki commented Jan 20, 2023

@tgross35 I think I left the code in my rust clone and eventually cleaned it up. My bad, I should have just made the PR.

I ended up just using a feature for my use case.

@PonasKovas
Copy link
Contributor

PonasKovas commented Jan 27, 2023

As @pheki has already meantioned earlier, currently cargo always builds all crate_types at the same time, as you can see if you make a ["cdylib", "lib"] crate and build it, both cdylib and lib will be generated without recompiling the code 2 times, and you can't tell cargo to build only one of them, or separately. So #[cfg(crate_type = "type")] would not be practical, as it would always compile if the type is in Cargo.toml.

The best solution at the moment seems to be using features or separate crates.

My proposal

My proposal

I am not sure if the way crate_type is treated is optimal though. How often do you actually need all of those crate_types at the same time? As far as I can imagine, you almost always need either one or the other. I am not deeply familiar with cargo or rustc's inner workings so take my words with a grain of salt, but here's what I think:

Note: I am not mentioning bin or proc-macro types purposely in this proposal since they are mutually exclusive with all the other types already and are not affected in any way.

crate_type should define "allowed" types, and if only 1 type is given, it should work the same way it works now.
However, if there are multiple types given, it should compile all of them separately (allowing conditional compilation based on the crate_type), and only the types that will be needed (decided by context).

So, if you were to cargo build such a crate, it would build all of them by default, but you could specify which type you want with a command line argument like --crate-type <which type to generate>. (A related idea that I haven't thought out very well but still want to mention: something like default-crate-type key in Cargo.toml which, if set, would make cargo build only build the given type instead of all of them by default (still can be overriden by --crate-type argument of course))

And if your crate was used as a dependency in another crate, it should only build rlib or dylib.
If both rlib and dylib are "allowed" in crate_type, then cargo should require you to explicitly specify which one you want to use in the dependency definition (for example some_lib = { version = "1.0", crate-type = "dylib" }, where some_lib has crate-type = ["rlib", "dylib"] in it's Cargo.toml). (Or use the default-crate-type key I suggested earlier)
If neither of them is "allowed", it should be a hard error (it's already a warning with a notice that it might become a hard error in the future).

the warning

What this would solve

  • The vagueness of which crate_type is actually used if a dependency has crate_type = ["rlib", "dylib"]. (Currently it seems to just use rlib, generating the dylib but not actually using it)
  • It would make conditional compilation based on crate_type that is being generated easier and more intuitive.
  • Make it easier to use crate_type correctly: not allowing crates to depend on cdylib and staticlib crates in Cargo.toml (build.rs should be used).

Potential problems/downsides

  • Compiling all crate_types separately will require to compile all dependencies too. This shouldn't affect the majority of existing crates, since most of them only have one crate_type. But for the small subset of crates that do have multiple crate_types and actually need all of them compiled at the same time/place it may increase compilation time significantly. Of course this could be fixed by making cargo keep the cache of builds for all crate_types.
  • This would be a breaking change. I am not sure what's the exact Cargo take on breaking changes, but glancing at it's Cargo.toml I see that it's still pre-1.0 and should be possible.

Example use-case

Implementing this proposal would allow one to, for example, make a cdylib and define it's public API for Rust in the form of rlib very easily in the same crate without using features.

// Dynamic library implementation
// If a different Rust crate adds this crate as a dependency, only `rlib` will be compiled
// and the following won't be included.
// However, if specifically building the `cdylib` type with `cargo build`, it will be included
#[cfg(crate_type = "cdylib")]
#[no_mangle]
extern "C" fn example() -> interface::MyGuard {
    interface::MyGuard {
        inner: 123,
    }
}

// The public API of the dynamic library, that is shared both by the dynamic library itself,
// and binary that uses it. This makes it as simple as adding a dependency to your Cargo.toml
// to get access to the API, without separating the dynamic library into two crates "impl" and
// "interface" or using features.
pub mod interface {
    #[repr(C)]
    pub struct MyGuard {
        pub(crate) inner: usize,
    }
    
    impl Drop for MyGuard {
        fn drop(&mut self) {
            println!("guard dropped: {}", self.inner);
        }
    }
}

Looking forward to your comments and thoughts on this proposal.
Hopefully I am not making a clown out of myself again 🙂

@guilhermewerner
Copy link

(A related idea that I haven't thought out very well but still want to mention: something like default-crate-type key in Cargo.toml which, if set, would make cargo build only build the given type instead of all of them by default (still can be overriden by --crate-type argument of course))

It is possible to achieve this behavior if you set the crate-type to rlib in Cargo.toml and use the following command:

cargo rustc --package <package_name> --crate-type cdylib

It will only compile the crate for the crate-types specified in the command, this is possible after rust-lang/rfcs#3180

This is a way around the problem of #[cfg(crate_type = "type")], where all crates in the project are rlib only, to be used as a dependency on other crates, and when I need to create a cdylib I compile it separately.

However, it would be very interesting not to compile twice to get this result.

tantaman added a commit to vlcn-io/cr-sqlite that referenced this issue Sep 15, 2023
tantaman added a commit to vlcn-io/cr-sqlite that referenced this issue Sep 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-attributes Area: Attributes (`#[…]`, `#![…]`) A-driver Area: rustc_driver that ties everything together into the `rustc` compiler C-feature-request Category: A feature request, i.e: not implemented / a PR. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests