Skip to content

Commit c7be36d

Browse files
author
Dmitry Grigoriev
committed
Fixed issue #1.
1 parent 841b534 commit c7be36d

File tree

2 files changed

+34
-24
lines changed

2 files changed

+34
-24
lines changed

README.md

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,62 @@
22

33
## Concept
44

5+
### In short (for experienced linux users)
6+
7+
- Create C++ source file, prepend it with shebang line `#!/path/to/build-n-run`, make it executable `chmod +x script.cpp` and run: `./script.cpp`.
8+
9+
- `build-n-run` compiles `script.cpp` to `~/bin/build-n-run.compiled/` **only if source file is newer than compiled binary**, and then runs compiled binary.
10+
11+
### In detail
12+
513
They say compiled languages are inconvenient for scripting because you have to recompile your script everytime you edit it. A little program `build-n-run.cpp` presented in this repo solves the problem.
614

715
In bash scripts, first line always starts with "[shebang](https://en.wikipedia.org/wiki/Shebang_(Unix))" followed by name of a bash interpreter. When you run bash script, effectively bash interpreter is run and this script is passed to it as first argument.
816

917
#!/bin/bash
1018
echo "Hello world!"
1119

12-
Same approach is used for Python scripts, Scala scripts, etc. So I did the same: file `hello.cpp` in this repo is normal C++ program prepended with shebang line:
20+
Same approach is used for Python scripts, Scala scripts, etc. So I did the same: example script `hello.cpp` in this repo is normal C++ program prepended with shebang line:
1321

1422
#!/usr/local/bin/build-n-run
1523
#include <stdio.h>
1624
int main() {
1725
puts("Hello world!");
1826
}
1927

20-
`build-n-run` automatically recompiles script *if it was edited* (by comparing source and binary modification times) and then runs it:
28+
`build-n-run` automatically recompiles script **if it is new or edited** (i.e. if compiled binary does not exist or if source file's modification time is newer than binary's) and then runs it:
2129

2230
$ hello.cpp
23-
Recompiling... <--- first time you run your script, it's recompiled
31+
Recompiling... <--- First time you run your script, it's recompiled.
2432
Hello world!
2533

2634
$ hello.cpp
27-
Hello world!
35+
Hello world! <--- No recompilation on next run if script is unchanged.
2836

2937
## Installation and use
3038

3139
1. Download `build-n-run.cpp` from this repo.
3240

33-
2. [Optional] Check its source code: update GCC options used to compile your scripts; remove "Recompiling..." message if it messes with our scripts' output.
41+
2. (Optional) Check its source code: you might want to update GCC options used to compile your scripts, or remove "Recompiling..." message if it messes with our scripts' output.
3442

35-
3. Compile and install it. I install to `/usr/local/bin`, so I run this command as root. You can install it anywhere you want, just don't forget to edit path in `hello.cpp`'s shebang line. I use gcc 7.3.0, so I explicitly link `libstdc++fs.a` which implements `std::experimental::filesystem`.
43+
3. Compile and install it. I install to `/usr/local/bin`, so I run this command as root; you can install it anywhere you want, just don't forget to update path in scripts' shebang line. I use gcc 7.3.0, so I explicitly link `libstdc++fs.a` which implements `std::experimental::filesystem`.
3644

3745
# g++ build-n-run.cpp -lstdc++fs -o /usr/local/bin/build-n-run
3846

39-
4. Create directory for compiled scripts. Its full path must be the same as installed program's plus `.compiled` suffix. If that directory is shared among multiple users, grant global access to it.
40-
41-
# mkdir /usr/local/bin/build-n-run.compiled
42-
# chmod a+rwx /usr/local/bin/build-n-run.compiled
43-
44-
That's all. Now write C++ program with shebang at first line, *chmod it to be executable*, and run:
47+
That's all. Now write C++ program with shebang line, **chmod it to be executable**, and run:
4548

4649
$ chmod +x hello.cpp
4750
$ ./hello.cpp
4851
Recompiling...
4952
Hello world!
5053

51-
Now let's take a look at `build-n-run.compiled` directory:
54+
Let's take a look at `~/bin/build-n-run.compiled` directory:
5255

53-
$ ls /usr/local/bin/build-n-run.compiled
56+
$ ls ~/bin/build-n-run.compiled
5457
home--me--tmp--hello
5558

5659
I downloaded `hello.cpp` to `/home/me/tmp`, and compiled binary's name reflects source's full path, slashes replaced with `"--"`.
60+
61+
## Security concerns
62+
63+
Compiled cache is inside user's home, so scripts compiled by one user cannot be run by other users (see issue #1).

build-n-run.cpp

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// How to build with gcc-7.3.0:
22
// # g++ build-n-run.cpp -lstdc++fs -o /usr/local/bin/build-n-run
3-
// # mkdir /usr/local/bin/build-n-run.compiled; chmod a+rwx /usr/local/bin/build-n-run.compiled
43

54
#include <stdio.h>
65
#include <stdlib.h>
@@ -25,7 +24,7 @@ string replaceAll(string s, const string& search, const string& replace) {
2524
}
2625

2726

28-
// I call gcc via `man 3 system` which starts shell. Slow. OK for compilation, but not for compiled script.
27+
// I call gcc via `system(3)` which starts shell. Slow. OK for compilation, but not for running compiled script.
2928
int fastExec(const char* cmd, char** argv) {
3029
auto pid = fork();
3130
if (pid == -1) {
@@ -49,33 +48,37 @@ int fastExec(const char* cmd, char** argv) {
4948

5049

5150
int main(int argc, char** argv) {
52-
path me(argv[0]);
51+
path me(*argv++);
5352
if (argc < 2) {
5453
fprintf(stderr, "ERROR: Usage: %s SCRIPT.cpp [ARG...]\n", me.filename().c_str());
5554
exit(1);
5655
}
5756

58-
path source(argv[1]);
57+
path source(*argv);
5958
if (source.extension() != ".cpp") {
6059
fputs("ERROR: Given script must have .cpp extension", stderr);
6160
exit(1);
6261
}
6362

64-
path target(me.string() + ".compiled/" + replaceAll(canonical(source), "/", "--").substr(2));
63+
char* home = getenv("HOME");
64+
if (home == nullptr || *home == '\0') {
65+
fputs("ERROR: HOME environment variable is not set", stderr);
66+
exit(1);
67+
}
68+
path target(string(home) + "/bin/" + me.filename().string() + ".compiled/" + replaceAll(canonical(source), "/", "--").substr(2));
6569
target.replace_extension();
6670

6771
if (!exists(target) || last_write_time(source) > last_write_time(target)) {
6872
puts("Recompiling...");
69-
70-
// Commented out: permission denied if executed by restricted user. Create manually with mode=777.
71-
// create_directories(target.parent_path());
73+
74+
create_directories(target.parent_path());
7275

7376
// https://stackoverflow.com/a/1003654/4247442
74-
string cmd = "tail -n +2 \"" + source.string() + "\" | g++ -o \"" + target.string() + "\" -xc++ -lstdc++fs -";
77+
string cmd = "tail -n +2 \"" + source.string() + "\" | g++ -o \"" + target.string() + "\" -xc++ -Wall -lstdc++fs -";
7578
if (system(cmd.c_str()) != 0) {
7679
exit(1);
7780
}
7881
}
7982

80-
return fastExec(target.c_str(), argv + 1);
83+
return fastExec(target.c_str(), argv);
8184
}

0 commit comments

Comments
 (0)