Declare that a crate is not Windows compatible

Is there a way to declare, via the Cargo.toml file that a particular crate cannot be used on Windows. By used, I mean cannot be compiled on Windows.
Of course, I could put an #[cfg(not(windows))] around the main function (it is a binary crate), making it impossible to compile it on Windows, but is there a way to do it from the crate manifest?

No, I don't think so. You can do #[cfg(windows)] compile_error!("This program isn't compatible with windows."); though. Also is it just windows it isn't compatible with, or is it only compatible with for example unix systems. In that case you should probably use #[cfg(unix)]/#[cfg(not(unix))] instead.

2 Likes

I thought that was the default :slight_smile:

No seriously, I gave up building anything in any language for Windows a long time ago.

Luckily Win 10 brought us the Linux Subsystem for Windows so I can still continue to develop on Windows.

https://doc.rust-lang.org/cargo/reference/cargo-targets.html#configuring-a-target

I did not test but maybe you can use the "required-features" setting?

If you're developing something very unix-specific, then add categories = ["os::unix-apis"] to crate's metadata. It won't automatically stop Windows users from getting it, but at least it's a clear classification and hopefully users will notice it when browsing crates.io or lib.rs.

4 Likes

It's not that bad with Rust, since the language is automatically compatible, and lots of crates do have proper support for Windows.

I even got cargo-deb run on Windows, because it was only a matter of changing a couple of lines of code. I find it hilarious — you can make Windows-only Debian packages! (now somebody needs to RIIR dpkg).

5 Likes

The convention would be #![cfg(not(windows))] at the top level, so that the crate compiles but is empty in windows, which makes things a lot simpler in several situations.

But yeah, generally just making clear that it's Unix only or whatever in the readme/tags is all: don't let people download it and then get disappointed.

Of course, it's quite easy to make most code cross platform without any special effort unless the whole point is to expose a platform specific behavior, but I ain't your boss, so whatever.

(also, I would appreciate it if people kept their platform bashing to themselves, I've got a pretty long list of complaints for all three major desktop OSs - but airing them is petty pointless)

8 Likes

Well, I was initially making it Windows-compatible, but I then ran into the following issue: I am storing a path by splitting it into its parent and file-name. On Unix, / has None for its parent and None for its file-name, so its clear when I am storing the root directory. But on Windows, C:\, D:\ ... all of them have None for both parent and file-name - so how do I know what path I am referring to.
There is probably a way to solve this - possibly store the drive name somewhere, but I don't anticipate my application to be used on Windows (there are much better alternatives on Windows). So I decided that it was simpler to just rule out Windows use rather than worry about this.

Challenge accepted :slight_smile:
(not sure if I'm really up to this, due to the size of the task, but it indeed sounds interesting)

1 Like

(off the beaten path here, but why not) Not really clear from that description what the issue is: if you are trying to get a list of strings for each path component, then surely .iter() or .components() is exactly what you want: That gives "/", "foo", "bar" on unix, and "C:", "\", "foo", "bar" on windows, as either OsStr or std::path::Components. If you specifically want to split into (parent, file_name), then "C:\foo" splits into ("C:", "foo"), which is fine, and "/foo" splits into ("/", and "foo") which is fine. So basically, the only "weird" case is when .parent() is None, that you should just store the .to(_os)_str() instead of .file_name().

maybe the best choice is

#[cfg(windows)]
fn func_use_unix_api(...){
    unimplemented!("Currently this crate does not support windows")
}

this may provide the capability that allow windows users modifying your code and make PR for you.

4 Likes

I don't understand why you use parent() like that. Maybe there are other Path methods that you could use instead? e.g. is_absolute or ancestors?

If you need to store paths as portable strings without C:\ showing up, there's:

Well, I have a situation like this - I need to store the "filesystem", starting at some directory as a tree. So, for an instance, I might want to store the directory "/home/deep/stuff" or "C:\Users\deep\stuff" as a tree. For paths like this, it is not a problem. The problem occurs for the directory "C:". Calling parent() on this returns None. So, this way I am losing the drive info. Why do I need to store the parent? For the root node. I am representing the tree as:

struct Tree {
    root_parent: PathBuf,
    root: Node,
}

struct Node {
     name: OsString,
     info: Info,
     children: HashMap<OsString, Node>,
}

Would something like cap-std work for you, or do you specifically need paths rather than files?

Unfortunately, I'd need paths. On the other hand, you have presented a really interesting crate - so thank you for that!

parent() is not for splitting the path into components, but literally for finding a parent directory. C:\ doesn't have a parent directory — you can't be in C:\ and cd .. to go to C.

You need path.components(). It's lossless and gives you C as a separate component.

It's a reversible iterator, so you can use path.components().rev() as a lossless alternative to ancestors() and calling .parent() in a loop.

path.components().rev().next() is like parent(), but will yield C too.

2 Likes

Ah I see, that's brilliant!