How can I pass flags to rustc from a build script if they contain a space? - rust

I am writing a Rust program with some C integration so I'm using a custom build script. In this script, I pass -L <path to library> to rustc, but this works only if <path to library> does not contain a space.
The exact line in the build.rs:
println!(r"cargo:rustc-flags= -L {}/target/sdsl/sdsl_install/lib -l sdsl -l divsufsort -l divsufsort64 -l stdc++", current_dir);
If current_dir contains a space I get this error
error: Only `-l` and `-L` flags are allowed in build script of `top_tree_compression v0.1.0 (file:///home/jan/Uni/Bachelorarbeit/Programme/Top_Tree%20Compression)`: `-L /home/jan/Uni/Bachelorarbeit/Programme/Top_Tree Compression/target/sdsl/sdsl_install/lib -l sdsl -l divsufsort -l divsufsort64 -l stdc++`
I tried to write a \ before the space to escape it but it gives me the same error. Then I tried to replace the space with %20 because in the error message the space was replaced with this, but then I get a linking error because the path is not correct.

It appears you cannot as of Rust 1.29. The source code for the current master of Cargo:
let mut flags_iter = value
.split(|c: char| c.is_whitespace())
.filter(|w| w.chars().any(|c| !c.is_whitespace()));
This naively splits the argument on any whitespace, regardless of where it occurs. This seems to be a bug or limitation of Cargo and you should look for an already-filed issue or file one yourself.
That being said, if you use the more fit-for-purpose rustc-link-lib and rustc-link-search parameters, spaces work fine:
println!(r#"cargo:rustc-link-search={}/target/sdsl/sdsl_install/lib"#, "some thing");
$ cargo run --verbose
Compiling xx v0.1.0 (file:///private/tmp/xx)
[...snip...]
Running `rustc [...snip...] -L 'some thing/target/sdsl/sdsl_install/lib'`

Related

Standard error file when there is no error

I'm new to Linux & shell and I'm struggling with checking if the compilation is successful.
g++ code.cpp -o code.o 2>error.txt
if [ ! -e error.txt ]
then
do something
else
echo "Failed to compile"
I guess an error file is created even if the compilation is successful. What is the content of the error file when there is no error? I need to change the if condition to check if the compilation is successful.
It's just the order of things. What happens when the shell parses the string g++ code.cpp -o code.o 2>error.txt is:
The shell creates error.txt, truncating the file if that name already exists.
g++ is called with its error output redirected to the new file.
If g++ does not write any data, then the file remains as it was (empty) at the end of step 1.
You probably aren't so much interested in the error file as you are the return value. You probably ought to just do:
if g++ code.cpp -o code; then : do something; done
or even just:
g++ code .cpp -o code && : do something
but if really want to do something else with the errors, you can do:
if g++ code.cpp -o code.o 2> error.txt; then
rm error.txt
: do something
else
echo >&2 Failed to compile code.cpp.\ See "$(pwd)"/error.txt for details.
fi
Make sure you escape at least one of the spaces after the . so that you get 2 spaces after the period (or just quote the whole argument to echo). Although it's become fashionable lately to claim that you only need one space, all of those arguments rely on the use of variable width fonts and any command line tool worth using will be used most often in an environment where fixed width fonts are still dominant. This last point is totally unrelated to your question, but is worth remembering.

How can I pass version number as a variable into rpmbuilder?

Building an RPM is part of our CI-flow, so naturally I want the version number passed as a parameter into rpmbuild, but I can't get it to work. I tried the solution from this question, but it says I can't put '$' in define-strings:
[mangolorax#localhost build_artifacts]$ ./package_release.sh 1.3.3.7
+ BUILD_VERSION_STRING=1.3.3.7
+ BUILD_DIR=/home/builder/build
+ exec rpmdev-setuptree
+ cd /home/mangolorax/rpmbuild/SPECS/
+ ln -sf /home/builder/build/mvpn.spec
+ rpmbuild --target x86_64 --define 'version ${BUILD_VERSION_STRING}' -bb mvpn.spec -vv
Building target platforms: x86_64
Building for target x86_64
error: line 2: Illegal char '$' in: Version: ${BUILD_VERSION_STRING}
I also found this question, but it seems to me to be a ridiculously convoluted solution to the problem. Surely there must be a simpler way of doing this? Or have I fundamentally misunderstood this problem?
The entire problem was that I used single quotes around the --define string. In bash that means that everything inside the string is passed literally without expanding any variables. If I call rpmbuild like this instead, it works as expected:
rpmbuild --target x86_64 --define "version ${BUILD_VERSION_STRING}" -bb mvpn.spec -vv

Rust not printing to terminal

rustc is not outputting anything to terminal when using println!.
Code:
fn main() {
println!("Hello, world!");
}
Running it:
me#mclaptop:~
> rustc helloworld.rs
me#mclaptop:~
>
Why does it not print anything?
rustc is the compiler of the Rust language, it just produces an executable to be run. If you want to actually see the output you must run the ./helloworld command.
You can read about it here.
rustc only compiles your code. You need to call the output binary to get it working.
Try ./helloworld or whatever the name of the output file is.
On Linux and Mac:
rustrc helloworld.rs && ./helloworld
You can simply use:
cargo run
This will compile current project and run it just with one command.

How to execute Rust code directly on Unix systems? (using the shebang)

From reading this thread, it looks like its possible to use the shebang to run Rust *.
#!/usr/bin/env rustc
fn main() {
println!("Hello World!");
}
Making this executable and running does compile, but not run the code.
chmod +x hello_world.rs
./hello_world.rs
However this only compiles the code into hello_world.
Can *.rs files be executed directly, similar to a shell script?
* This references rustx, I looked into this, but its a bash script which compiles the script every time (without caching) and never removes the file from the temp directory, although this could be improved. Also it has the significant limitation that it can't use crates.
There's cargo-script. That also lets you use dependencies.
After installing cargo-script via cargo install cargo-script, you can create your script file (hello.rs) like this:
#!/usr/bin/env run-cargo-script
fn main() {
println!("Hello World!");
}
To execute it, you need to:
$ chmod +x hello.rs
$ ./hello.rs
Compiling hello v0.1.0 (file://~/.cargo/.cargo/script-cache/file-hello-d746fc676c0590b)
Finished release [optimized] target(s) in 0.80 secs
Hello World!
To use crates from crates.io, please see the tutorial in the README linked above.
This seems to work:
#!/bin/sh
//usr/bin/env rustc $0 -o a.out && ./a.out && rm ./a.out ; exit
fn main() {
println!("Hello World!");
}
I have written a tool just for that: Scriptisto. It is a fully language agnostic tool and it works with other compiled languages or languages that have expensive validation steps (Python with mypy).
For Rust it can also fetch dependencies behind the scenes or build entirely in Docker without having a Rust compiler installed. scriptisto embeds those templates into the binary so you can bootstrap easily:
$ scriptisto new rust > ./script.rs
$ chmod +x ./script.rs
$ ./script.rs
Instead of new rust you can do new docker-rust and the build will not require Rust compiler on your host system.
#!/bin/sh
#![allow()] /*
exec cargo-play --cached --release $0 -- "$#"
*/
Needs cargo-play. You can see a solution that doesn't need anything here:
#!/bin/sh
#![allow()] /*
# rust self-compiler by Mahmoud Al-Qudsi, Copyright NeoSmart Technologies 2020
# See <https://neosmart.net/blog/self-compiling-rust-code/> for info & updates.
#
# This code is freely released to the public domain. In case a public domain
# license is insufficient for your legal department, this code is also licensed
# under the MIT license.
# Get an output path that is derived from the complete path to this self script.
# - `realpath` makes sure if you have two separate `script.rs` files in two
# different directories, they get mapped to different binaries.
# - `which` makes that work even if you store this script in $PATH and execute
# it by its filename alone.
# - `cut` is used to print only the hash and not the filename, which `md5sum`
# always includes in its output.
OUT=/tmp/$(printf "%s" $(realpath $(which "$0")) | md5sum | cut -d' ' -f1)
# Calculate hash of the current contents of the script, so we can avoid
# recompiling if it hasn't changed.
MD5=$(md5sum "$0" | cut -d' ' -f1)
# Check if we have a previously compiled output for this exact source code.
if !(test -f "${OUT}.md5" && test "${MD5}" = "$(cat ${OUT}.md5)"); then
# The script has been modified or is otherwise not cached.
# Check if the script already contains an `fn main()` entry point.
if grep -Eq '^\s*(\[.*?\])*\s*fn\s*main\b*' "$0"; then
# Compile the input script as-is to the previously determined location.
rustc "$0" -o ${OUT}
# Save rustc's exit code so we can compare against it later.
RUSTC_STATUS=$?
else
# The script does not contain an `fn main()` entry point, so add one.
# We don't use `printf 'fn main() { %s }' because the shebang must
# come at the beginning of the line, and we don't use `tail` to skip
# it because that would result in incorrect line numbers in any errors
# reported by rustc, instead we just comment out the shebang but leave
# it on the same line as `fn main() {`.
printf "fn main() {//%s\n}" "$(cat $0)" | rustc - -o ${OUT}
# Save rustc's exit code so we can compare against it later.
RUSTC_STATUS=$?
fi
# Check if we compiled the script OK, or exit bubbling up the return code.
if test "${RUSTC_STATUS}" -ne 0; then
exit ${RUSTC_STATUS}
fi
# Save the MD5 of the current version of the script so we can compare
# against it next time.
printf "%s" ${MD5} > ${OUT}.md5
fi
# Execute the compiled output. This also ends execution of the shell script,
# as it actually replaces its process with ours; see exec(3) for more on this.
exec ${OUT} $#
# At this point, it's OK to write raw rust code as the shell interpreter
# never gets this far. But we're actually still in the rust comment we opened
# on line 2, so close that: */

scons surrounds option with double quotes

I use scons (V1.1.0) for a project that contains a build step that involves the flex tool.
The definition for the flex command in the scons default rules is:
env["LEX"] = env.Detect("flex") or "lex"
env["LEXFLAGS"] = SCons.Util.CLVar("")
env["LEXCOM"] = "$LEX $LEXFLAGS -t $SOURCES > $TARGET"
which I don't want to change.
However, since -t causes #line directives to be created in the output file that refer to the file "<stdout>", this confuses the subsequent gcov processing.
As a solution, I found that -o can be used to override the file name flex produces into the #line directives (it still produces its output on stdout due to the -t option which apparently has precedence).
To achieve that, I added this in the project's SConscript file:
env.AppendUnique(LEXFLAGS = ['-o $TARGET','-c'],delete_existing=1)
I added the -c option (which does nothing) only to show the difference between how it is treated compared to -o.
An according debug print in the SConscript file results in the following (as expected):
repr(env["LEXFLAGS"]) = ['-o $TARGET', '-c']
This results in the following command line, according to the scons log:
flex "-o build/myfile.cpp" -c -t src/myfile.ll > build/myfile.cpp
So the -c option gets into the command line as desired, but the -o option and its filename parameter has double quotes around it, that must have been created by scons when expanding the LEXFLAGS variable.
When I use this definition for LEXFLAGS instead:
env.AppendUnique(LEXFLAGS = ['--outfile=$TARGET','-c'],delete_existing=1)
the resulting command line works as desired:
flex --outfile=build/myfile.cpp -c -t src/myfile.ll > build/myfile.cpp
So one could speculate that the blank in the -o case caused the double quotes to be used, maybe in an attempt to bind the content together into one logical parameter for the command.
So while my immediate problem is solved by using --outfile, my question is still is it possible to rid of the double quotes in the -o case?
Thanks,
Andy
SCons 1.1.0 is extremely old at this point. I'd recommend trying 2.3.0. But your analysis is correct; if an option (a single option, that is) has a space in it, SCons will quote it so it stays a single option. But you don't have a single option; you really have two, '-o' and '$TARGET'. Just break it up like that and it'll work.

Resources