txt file not found when importing the crate - rust

I created a library that in order to work needs to parse a txt file every time you call the main method. The problem is that when importing into another project the txt file can not be found because I'm using env::current_dir(), when I call the method from the library folder current folder is the crate's root, and I can access root/src/my_file.txt. When importing and using the library the root is different and there isn't any my_file.txt.
How can resolve this? Here is the Crate
Here is how I access the file.
fn parse(&mut self, name_to_find: &str) -> () {
let p = env::current_dir().unwrap();
println!("{}", p.display());
let file = File::open(format!("{}/src/nam_dict.txt", p.display())).unwrap();
let lines = BufReader::new(file).lines();
...

Rust is a compiled language, so one can't assume access to the sourcecode and adjacent files at runtime.
So your options are
Include it at compile time with the include_str! macro. This means changes to the file won't be picked up until the library and its dependents are rebuilt
Locate the file at runtime, e.g. through a specified location (such as somewhere in the config hierarchy in the user's home directory), an environment variable, a commandline option or from the current directory. This way the file can be changed without recompiling the program but the user has to know that he must provide it.
More complicated approaches such as including a default configuration and letting the user override it are also possible.

Related

Is it possible to have example-specific build.rs file?

In my library I have few examples -- (normally) each one is represented by a single x<N>.rs file living in examples directory.
One example uses a .proto file -- this file needs to be compiled during build (of said example) and it's generated output is used by example itself.
I've tried this in my Cargo.toml:
[[example]]
name = "x1"
path = "examples/x1/main.rs"
build = "examples/x1/build.rs"
but build key gets ignored when I run cargo build --example x1
Is it possible to have example-specific build.rs file?
If not -- what is the correct way to deal with this situation?
Edit: I ended up processing that .proto file in crate's build.rs (even though it is not required to build that crate) and using artefacts in the example like this:
pub mod my_proto {
include!(concat!(env!("OUT_DIR"), "/my_proto.rs"));
}
This is not possible. This issue explains why, but in a nutshell build scripts are used for whole crate. So you could move your example into separate crate.

Can not create rust library for python

I am trying to make a python module in rust. I am continuing to fail to get the files that I need to generate. I followed this tutorial almost exactly.
https://mycognosist.github.io/tutorial-rust-python-lib.html
Here is my toml file.
name = "pylib"
version = "0.1.0"
authors = ["Atops"]
edition = "2018"
[lib]
name = "status"
crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies.cpython]
version = "0.5"
features = ["extension-module"]
Here is the code for lib.rs
extern crate cpython;
use cpython::{PyResult, Python, py_fn, py_module_initializer};
pub extern fn hello(_py: Python, val: String) -> PyResult<String> {
match &*val {
"hello" => Ok("world".to_string()),
_ => Ok("afdfs".to_string()),
}
}
py_module_initializer!(status, initstatus, Pyinit_status, |py, m|{
m.add(py, "__doc__", "asdhgdfs")?;
m.add(py, "hello", py_fn!(py, hello(val: String)))?;
Ok(())
});
I navigate to the appropriate folder with my cargo.toml file and use cargo build --release, as specified in the tutorial. It finishes with a few warnings about certain things being "not FFI-safe". When it finishes a new folder is created called "target". I go through there to where the files for the library should be, but the appropriate files don't seem to have been generated. I have these.
target\release
These don't seem to be usable for me. I am not sure what I have done wrong.
I copied every single one of these files to another folder and tried importing them in python. This did not work. I tried changing them to .so files, this did not work. It seems that windows is supposed to output a dll here, but the dll file did not work when attempting to import. It did not work as a dll file or a so file. I am not sure what to do here. Also, it seems that when these files are generated in every other tutorial or guide I see, there is a "lib" prefix on the name of each. I get no such prefix.
A dll file contains shared object code for COFF systems (like Windows) while a so file generally contains shared object code for ELF (and other) systems like Linux.
Renaming them will not work under any circumstance. If you have one and need the other, you need to rebuild them on the correct, matching, operating system.
I think you might be following instructions for Linux (due to the lib prefixes) on a Windows machine and then expecting some of the instructions to work. They might work, if you can identify all the "changes" between the platform and modify the Linux instructions to match the Windows platform; but until you know how to translate one environment's instructions to the other, it might be easier if you just find a set of Windows instructions that only refer to dll files and don't mention so files.

How can I import a nested module without creating a mod.rs? [duplicate]

I have module files generated from protobuf definitions. There are a lot of files and they will grow with time. This is the structure:
proto_rust/src/lib.rs
proto_rust/src/protos/{lots of auto generated .rs files}
proto_rust/src/protos/mod.rs
Since there are lots of files in proto_rust/src/protos/, it does not make sense for me to manually put them in mod.rs. Is there a way to expose all them to lib.rs? Something like pub mod *.
Use dtolnay's automod crate.
automod::dir!("path/to/directory");
This macro expands to one or more mod items, one for each source file
in the specified directory.
The path is given relative to the directory containing Cargo.toml.

Falling back to alternative value if include_bytes!(…) target is missing

My package has a binary target that uses include_bytes!(…) to bundle a copy of some precomputed values into the compiled binary. This is an optimization, but isn't strictly necessary: the program is capable of calculating these values at run time if the bundled data slice .is_empty().
The program needs to be able to build without this data. However, include_bytes!("data/computed.bin") causes a build error if the target file does not exist.
error: couldn't read src/data/computed.bin: No such file or directory (os error 2)
Currently, I have a Bash build script that uses touch data/computed.bin to ensure the file exists before building. However, I don't want to depend on platform-specific solutions like Bash; I want to be able to build this project on any supported platform using cargo build.
How can my Rust program include_bytes!(…) or include_str!(…) from a file if it exits, but gracefully fall back to an alternative value or behaviour if the file doesn't exist, while only using the standard Cargo build tools?
We can use a build script to ensure that the included file exists before out package tries to include it. However, build scripts can only write to the current build's unique output directory, so we can't just create the missing input files in the source directory directly.
error: failed to verify package tarball
Caused by:
Source directory was modified by build.rs during cargo publish. Build scripts should not modify anything outside of OUT_DIR.
Instead, our build script can create the file-to-include in the build directory, copying the source data if it exists, and we can update our package code to include this data from the build directory instead of from the source directory. The build path will be available in the OUT_DIR environment variable during the build, so we can access it from std::env::var("OUT_DIR") in our build script and from env!("OUT_DIR") in the rest of our package.
//! build.rs
use std::{fs, io};
fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
fs::create_dir_all(&format!("{}/src/data", out_dir))
.expect("unable to create data directory");
let path = format!("src/data/computed.bin", name);
let out_path = format!("{}/{}", out_dir, path);
let mut out_file = fs::OpenOptions::new()
.append(true)
.create(true)
.open(&out_path)
.expect("unable to open/create data file");
if let Ok(mut source_file) = fs::File::open(&path) {
io::copy(&mut source_file, &mut out_file).expect("failed to copy data after opening");
}
}
//! src/foo.rs
fn precomputed_data() -> Option<&'static [u8]> {
let data = include_bytes!(concat!(env!("OUT_DIR"), "/src/data/computed.bin")).as_ref();
if !data.is_empty() {
Some(data)
} else {
None
}
}
While using a build script (like in this answer) would work, I am not a fan of that solution:
The build script copies the file – depending on the file size, that might be prohibitively expensive. Though one could probably solve this problem using hardlinks instead.
An empty file might be perfectly fine data – the solution would misdetect an empty file as missing. However, depending on the use case, an empty file might actually be perfectly valid.
It is very verbose – this turns a simple include_bytes! into a build script of approximately 20 lines and additionally a few more lines when including to handle the data.is_empty() case.
It is hard to grasp what is happening here for a casual reader – why is this script including something from $OUT_DIR? It would probably take a moment for the reader to get the idea a build script might be involved here.
It does not scale well – most of those problems would get even worse if there were multiple files that needed to be included optionally.
I therefore decided to write the procedural macro crate include_optional to solve this problem (currently only works on nightly Rust because it depends on some unstable features).
With this, the solution to this problem is a one liner:
use include_optional::include_bytes_optional;
fn precomputed_data() -> Option<&'static [u8]> {
include_bytes_optional!("./computed.bin")
}
There are also macros wrapping include_str! and include!.

Attaching an icon resource to a Rust application

How do you attach an icon resource to a Rust application? I've seen how it's done in C but I'm not clear on how it works in Rust. This would be on Windows. I know that Linux and OS X work differently. If anyone has any tips on this for OS X that would be great too.
Windows
An easy way to set the icon for your .exe file is with the winres crate. First, add winres as a build dependency in your Cargo.toml:
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
Then, add a build script (a file named build.rs next to your Cargo.toml):
use std::io;
#[cfg(windows)] use winres::WindowsResource;
fn main() -> io::Result<()> {
#[cfg(windows)] {
WindowsResource::new()
// This path can be absolute, or relative to your crate root.
.set_icon("assets/icon.ico")
.compile()?;
}
Ok(())
}
Note that this does not update the icon shown in the taskbar or title bar. Setting that must be done via your GUI framework, e.g. iced recently added a way to configure this.
macOS
To set the icon on macOS, you need to bundle the executable into an .app. An .app is actually a directory, not a file. It looks something like this:
My App.app
Contents
Info.plist — This XML file includes information about your app, including the name of the binary and the location of the icon file:
<key>CFBundleExecutable</key>
<string>myapp</string>
<key>CFBundleIconFile</key>
<string>AppIcon.icns</string>
MacOS
myapp — The binary from target/release
Resources
AppIcon.icns
macOS apps are typically distributed as .dmg files. A release script could build the binary, bundle it into an .app, and then bundle that into a .dmg, along with a symlink to /Applications to make it easier for the user to “install” the app by moving it there.
Here are sample .dmg contents, and the corresponding release script.
Rust has no notion of icon files for windows, so you would do it the same way as you do in C, albeit via the Rust foreign function interface (FFI). There exist FFI wrappers for windows APIs, notably winapi.
Here is an example that shows how to associate an icon with an executable (by way of an .rc file).

Resources