config.h tests

This commit is contained in:
Venelin
2024-04-26 18:50:25 +03:00
parent 6dbe37e07b
commit a8fb5f7d3d
12 changed files with 118 additions and 42 deletions
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include <sys/types.h>
#include <cstdint>
typedef int8_t I8;
typedef uint8_t U8;
typedef int16_t I16;
typedef uint16_t U16;
typedef int32_t I32;
typedef uint32_t U32;
typedef int64_t I64;
typedef uint64_t U64;
typedef int fd_t; // file descriptor
+185
View File
@@ -0,0 +1,185 @@
#include <iostream>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <cstring>
#include "childProcess.hpp"
RetProc::RetProc(Proc origin, int status)
: Proc(origin)
, normal_exit(WIFEXITED(status))
, returned(WEXITSTATUS(status))
{}
RetProc::operator bool()
{
return normal_exit && returned == 0;
}
RetProc wait(Proc p)
{
int status;
waitpid(p.pid, &status, 0);
return RetProc(p, status);
}
/* create a process taking input from pipe childIn */
static Proc createProc(const char* const argv[], const fd_t childIn[2])
{
Proc childproc;
childproc.in = childIn[1];
childproc.out = STDOUT_FILENO;
childproc.pid = fork();
if(childproc.pid == 0) /* child */
{
close(childIn[1]);
dup2(childIn[0], STDIN_FILENO);
exec_or_die(argv);
}
return childproc;
}
/* create a process and redirect it's output to a pipe */
static Proc createRedirProc(const char* const argv[])
{
fd_t childOut[2];
pipe(childOut);
Proc childproc;
childproc.in = STDIN_FILENO;
childproc.out = childOut[0];
childproc.pid = fork();
/* close(STDIN_FILENO); flush?*/
if(childproc.pid != 0) /* parent */
{
close(childOut[1]);
}
else
{
dup2(childOut[1], STDOUT_FILENO);
exec_or_die(argv);
}
return childproc;
}
/* create a process redirecting both in and out */
static Proc createRedirProc(const char* const argv[], const fd_t childIn[2])
{
fd_t childOut[2]; /* todo: add error #include "processTypes.hpp"*/
pipe(childOut);
Proc childproc;
childproc.in = childIn[1];
childproc.out = childOut[0];
childproc.pid = fork();
/* close(STDIN_FILENO); flush?*/
if(childproc.pid != 0) /* parent */
{
close(childOut[1]);
}
else
{
close(childIn[1]);
dup2(childIn[0], STDIN_FILENO);
dup2(childOut[1], STDOUT_FILENO);
exec_or_die(argv);
}
return childproc;
}
Proc createRedirProcess(const char* const argv[], U32 flags) /* todo rename */
{
fd_t childIn[2];
Proc proc;
if(flags & INPUT)
{
pipe(childIn);
if(flags & OUTPUT)
proc = createRedirProc(argv, childIn);
else
proc = createProc(argv, childIn);
}
else if(flags & OUTPUT)
proc = createRedirProc(argv);
else
proc = createProcess(argv);
return proc;
}
Proc createCapProcess(const char* const argv[], fd_t in, fd_t err)
{
fd_t childOut[2];
pipe(childOut);
Proc proc;
proc.in = in;
proc.out = childOut[0];
proc.err = err;
proc.pid = fork();
if(proc.pid == 0) /* child */
{
if(in != STDIN_FILENO)
dup2(in, STDIN_FILENO);
dup2(childOut[1], STDOUT_FILENO);
if(err != STDERR_FILENO)
dup2(err, STDERR_FILENO);
exec_or_die(argv);
}
else
{
close(childOut[1]);
}
return proc;
}
Proc createProcess(const char* const argv[], fd_t in, fd_t out, fd_t err)
{
Proc proc;
proc.in = in;
proc.out = out;
proc.err = err;
proc.pid = fork();
if(proc.pid == 0) /* child */
{
if(in != STDIN_FILENO)
dup2(in, STDIN_FILENO);
if(out != STDOUT_FILENO)
dup2(out, STDOUT_FILENO);
if(err != STDERR_FILENO)
dup2(err, STDERR_FILENO);
exec_or_die(argv);
}
return proc;
}
void exec_or_die(const char* const argv[])
{
execvp(argv[0], (char* const *)argv);
std::cerr << "Can't execute: " << argv[0] << ' ' << strerror(errno) << std::endl;
_exit(1); // _exit since we are a child
}
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include "basicTypes.h"
struct Proc
{
pid_t pid;
fd_t in;
fd_t out;
fd_t err;
};
struct RetProc: public Proc /* returned process */
{
RetProc(Proc, int status); /* status as returned by waitpid */
bool normal_exit;
U8 returned; /* return code */
/* A returned process evaluates to true if it exited normaly and
* returned 0 */
explicit operator bool();
};
// Wait for a running proccess to finish
RetProc wait(Proc);
enum Redirect: U32
{
NOTHING =0,
INPUT = 1 << 0,
OUTPUT = 1 << 1,
ERR = 1 << 2 /* todo */
};
/* For all funcions argv is an array of command parameters
* argv[0] is the command itself, argv needs to end with nullptr
* non of the functions wait for the proccess to finish */
/* Create a proccess and redirect anyting flaged to a new pipe
flags - INPUT, OUTPUT, ERR
return pipe file descriptors*/
Proc createRedirProcess(const char* const argv[], U32 flags);
/* Capture ouput to a pipe, can take alternative input and err FDs */
Proc createCapProcess(const char* const argv[], fd_t in=0, fd_t err=2);
/* Can take FDs as arguments which will be used instead of std */
Proc createProcess(const char* const argv[], fd_t in=0, fd_t out=1, fd_t err=2);
/* execvp the command or exit */
void exec_or_die(const char* const argv[]);
+280
View File
@@ -0,0 +1,280 @@
#include <cstring>
#include <iostream>
#include <assert.h>
#include <limits.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include "commands.hpp"
#include "childProcess.hpp"
enum constants: I32
{
FILE_PERMISIONS = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
};
static fd_t open_or_die(const char* file, I32 flags)
{
fd_t fd = open(file, flags, FILE_PERMISIONS);
if(fd == -1)
{
std::cerr << "Can't open: " << file << ' ' << strerror(errno) << std::endl;
exit(1);
}
return fd;
}
RetProc Cmd::operator()(fd_t in, fd_t out, fd_t err)
{
Proc p = createProcess(argv.data(), in, out, err);
// Close files if such were used
// if(in > 2)
// close(in);
// if(out > 2)
// close(out);
// if(err > 2)
// close(err);
return wait(p);
}
Proc Cmd::detach(fd_t in, fd_t out, fd_t err)
{
return createProcess(argv.data(), in, out, err);
}
void Cmd::append_args(std::initializer_list<const char*> args)
{
argv.reserve(argv.size() + args.size());
argv.back() = *args.begin();
for(auto it = args.begin() + 1; it < args.end(); ++it)
argv.push_back(*it);
argv.push_back(nullptr);
}
Cmd Cmd::operator+(const char* arg)
{
Cmd result(*this);
result.argv.back() = arg;
result.argv.push_back(nullptr);
return result;
}
Cmd& Cmd::operator+=(const char* arg)
{
argv.back() = arg;
argv.push_back(nullptr);
return *this;
}
PendingCmd::PendingCmd(const Cmd& origin, fd_t in, fd_t out, fd_t err)
: cmd(origin)
, in(in)
, out(out)
, err(err)
, execed_(false)
{}
PendingCmd::~PendingCmd()
{
if(!execed_)
operator()();
/* if(in != 0) */
/* close(in); */
/* if(out != 1) */
/* close(out); */
/* if(err != 2) */
/* close(err); */
}
RetProc PendingCmd::operator()()
{
assert(!execed_ && "Executed command twice");
execed_ = true;
return cmd(in, out, err);
}
Proc PendingCmd::detach()
{
assert(!execed_ && "Executed command twice");
execed_ = true;
return cmd.detach(in, out, err);
}
Proc PendingCmd::detachRedirOut()
{
assert(!execed_ && "Executed command twice"); assert(out==1 && "already redirected");
assert(out==1 && "capturing redirected proccess");
execed_ = true;
return createCapProcess(cmd.argv.data(), in, err);
}
void PendingCmd::cancel()
{
execed_ = true;
}
std::string $(const PendingCmd& cmd)
{
Proc p = const_cast<PendingCmd&>(cmd).detachRedirOut();
// check output size (not portable?)
// int pipe_size;
// int rc = ioctl(p.out, FIONREAD, &pipe_size); assert(rc==0);
// ioctl(p.out, FIONREAD, &pipe_size);
// write to the string directly, todo: find a better way
std::string output;
int read_count;
int i = 0;
do
{
output.resize(output.size() + PIPE_BUF); // todo: check if we are overallocating
read_count = read(p.out, &output[i], PIPE_BUF); // todo: check errno
i += read_count;
}
while(read_count > 0);
// p finished?
output.erase(i, output.size() - i);
return output;
}
void exec(const Cmd& cmd)
{
exec_or_die(cmd.argv.data());
}
PendingCmd operator,(const PendingCmd& cfirst, const Cmd& second)
{
auto& first = const_cast<PendingCmd&>(cfirst);
first();
return PendingCmd(second);
}
PendingCmd operator,(RetProc, const Cmd& second)
{
return PendingCmd(second);
}
PendingCmd operator|(const PendingCmd& cfirst, const Cmd& second)
{
auto& first = const_cast<PendingCmd&>(cfirst);
fd_t firstOut = first.detachRedirOut().out;
return PendingCmd(second, firstOut);
}
RetProc operator&&(const PendingCmd& cfirst, const Cmd& csecond)
{
auto& first = const_cast<PendingCmd&>(cfirst);
auto& second = const_cast<Cmd&>(csecond);
RetProc firstProc = first();
if(firstProc)
return second();
return firstProc;
}
RetProc operator&&(RetProc p, const Cmd& ccmd)
{
auto& cmd = const_cast<Cmd&>(ccmd);
if(p)
return cmd();
return p;
}
RetProc operator||(const PendingCmd& cfirst, const Cmd& csecond)
{
auto& first = const_cast<PendingCmd&>(cfirst);
auto& second = const_cast<Cmd&>(csecond);
RetProc firstProc = first();
if(!firstProc)
return second();
return firstProc;
}
RetProc operator||(RetProc p, const Cmd& ccmd)
{
auto& cmd = const_cast<Cmd&>(ccmd);
if(!p)
return cmd();
return p;
}
PendingCmd& operator>(const PendingCmd& cmd, const char* file)
{
fd_t fd = open_or_die(file, O_WRONLY | O_CREAT);
return cmd > fd;
}
PendingCmd& operator>(const PendingCmd& ccmd, fd_t fd)
{
auto& cmd = const_cast<PendingCmd&>(ccmd);
assert(cmd.out == 1 || !"ERROR: Output is already redirected!");
cmd.out = fd;
return cmd;
}
PendingCmd& operator>>(const PendingCmd& cmd, const char* file)
{
fd_t fd = open_or_die(file, O_WRONLY | O_CREAT | O_APPEND);
return cmd > fd;
}
PendingCmd& operator>>(const PendingCmd& cmd, fd_t fd)
{
return cmd > fd;
}
PendingCmd& operator>=(const PendingCmd& cmd, const char* file)
{
fd_t fd = open_or_die(file, O_WRONLY | O_CREAT);
return cmd >= fd;
}
PendingCmd& operator>=(const PendingCmd& ccmd, fd_t fd)
{
auto& cmd = const_cast<PendingCmd&>(ccmd);
assert(cmd.err == 2 || !"ERROR: Error output is already redirected!");
cmd.err = fd;
return cmd;
}
PendingCmd& operator>>=(const PendingCmd& cmd, const char* file)
{
fd_t fd = open_or_die(file, O_WRONLY | O_CREAT | O_APPEND);
return cmd >= fd;
}
PendingCmd& operator>>=(const PendingCmd& cmd, fd_t fd)
{
return cmd >= fd;
}
PendingCmd& operator<(const PendingCmd& cmd, const char* file)
{
fd_t fd = open_or_die(file, O_RDONLY);
return cmd < fd;
}
PendingCmd& operator<(const PendingCmd& ccmd, fd_t fd)
{
auto& cmd = const_cast<PendingCmd&>(ccmd);
assert(cmd.in == 0 || !"ERROR: Input is already redirected!");
cmd.in = fd;
return cmd;
}
PendingCmd operator&(const PendingCmd& cfirst, const Cmd& second)
{
const_cast<PendingCmd&>(cfirst).detach();
return PendingCmd(second);
}
+162
View File
@@ -0,0 +1,162 @@
#pragma once
#include <vector>
#include <string>
#include "childProcess.hpp"
/* All CONST references are used and cast away to allow for taking
both l and r values without using templates or making copies
implicitly convert to it but not to const char* and we want the
implicit conversion for quick scripting
arg pointers are kept and used not copied */
/* A shell command */
class Cmd
{
public:
/* all args must be const char* */
template<typename... Args>
explicit Cmd(Args... args)
: argv({args...})
{
argv.push_back(nullptr);
}
/* Execute the command, if arguments are not given use stdin,out,err
else use the given file desciptors. this can also be used
to redirect err to out by giving err=1 like in shell */
RetProc operator()(fd_t in=0, fd_t out=1, fd_t err=2);
/* Run the command, don't wait to return like shell's & */
Proc detach(fd_t in=0, fd_t out=1, fd_t err=2);
/* Append arguments */
void append_args(std::initializer_list<const char*>);
/* Append an argument and return the new command */
Cmd operator+(const char* arg);
/* Append an argument */
Cmd& operator+=(const char* arg);
std::vector<const char*> argv; /* null terminateded arg list */
};
/* An instance of a shell comand that is pending execution
if the command is not executed during the life of the obj
its executed on destruction */
class PendingCmd
{
public:
/* Can be implicitly created from a command */
PendingCmd(const Cmd&, fd_t in=0, fd_t out=1, fd_t err=2);
/* Execute the command on destruction */
~PendingCmd();
/* No copy we use unnamed return value optimization to return PendingCmd without destruction */
PendingCmd(const PendingCmd&) = delete;
PendingCmd& operator=(const PendingCmd&) = delete;
/* Run the command */
RetProc operator()();
/* Run the command async
shell: cmd &
becomes: cmd.detach() */
Proc detach();
/* Detach but redirect output to a pipe */
Proc detachRedirOut();
/* Prevent a pending command from being executed on destruction */
void cancel();
Cmd cmd;
fd_t in, out, err;
private:
bool execed_;
};
/* Anything that takes PendingCmd can take Cmd aswell */
/* Like shell's exec */
void exec(const Cmd&); /* todo: take Pending? */
/* Run commands in sequence
shell:
ls
cd
becomes:
ls,
cd;
*/
PendingCmd operator,(const PendingCmd&, const Cmd&);
PendingCmd operator,(RetProc, const Cmd&);
/* Forbiden funcion to prevent wrong sequencing such as:
echo, echo && echo
here the 2nd and 3rd echos would be ran before 1st
due to the C++ operator precedence*/
PendingCmd operator,(const PendingCmd&, RetProc) = delete;
/* Shell pipe operator | - execute two commands, second takes input
from first can be chained many times */
PendingCmd operator|(const PendingCmd&, const Cmd&);
/* Shell operator && - run the second command only if first returns 0 (no errors)*/
RetProc operator&&(const PendingCmd&, const Cmd&);
RetProc operator&&(RetProc, const Cmd&);
/* Shell operator || - run second only if first returns != 0
Operator precedence is different from shell, C precendence is && > ||
so mixing || and && may not compile, but shouldn't cause other issues,
use () to resolve these cases */
RetProc operator||(const PendingCmd&, const Cmd&);
RetProc operator||(RetProc, const Cmd&);
/* Shell operator > - redirect output to file
shell: cmd > file 2>&1
becomes: cmd > "file" >=1 */
PendingCmd& operator>(const PendingCmd&, const char* file);
/* A file descriptor can be given istead of a file path in which case
* no truncation occurs */
PendingCmd& operator>(const PendingCmd&, fd_t);
/* Same but append rather then truncate the file */
PendingCmd& operator>>(const PendingCmd&, const char* file);
PendingCmd& operator>>(const PendingCmd&, fd_t);
/* Redirect errors to file */
PendingCmd& operator>=(const PendingCmd&, const char* file);
PendingCmd& operator>=(const PendingCmd&, fd_t);
/* Same but append rather then truncate the file */
PendingCmd& operator>>=(const PendingCmd&, const char* file);
PendingCmd& operator>>=(const PendingCmd&, fd_t);
/* Shell oprator < use file as input */
PendingCmd& operator<(const PendingCmd&, const char* file);
PendingCmd& operator<(const PendingCmd&, fd_t);
/* Shell operator & but only for single processes
e.g.: echo a && echo b & echo c
in shell would detach "echo a && echo b" but here
this is not possible */
PendingCmd operator&(const PendingCmd&, const Cmd&);
// todo
/* string that is implicitly cast to const char* and doesn't deallocate
to allow for simpler synthax, speed and safety
e.g: echo + some_string
rather then: echo + some_string.c_str() */
// class str: public std::basic_string<char> /* todo: allocator */
// {
// public:
// using std::basic_string<char>::basic_string;
// operator const char*() { return c_str();}
// };
/* Execute a command and capture the output
shell:
var=$(ls)
becomes:
auto var = $(ls);
*/
std::string $(const PendingCmd&);
+287
View File
@@ -0,0 +1,287 @@
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <iostream>
#include <fstream>
#include <string_view>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "commands.hpp"
#include "../config.h"
using namespace std;
namespace fs = std::filesystem;
struct MappedFile // todo: move to utils
{
// ~MappedFile()
// {
// munmap(data, len);
// }
char* data;
unsigned len;
};
// process the args until the src file arg is found, return its index
static int parse_args_until_src(int argc, char* argv[]);
// find the cpp to run, if it doesn't exist, exit program
static fs::path find_path_to_cpp(string_view src_file);
// find the path of the cache for the given src_file path
static fs::path get_cache_dir_path(const fs::path& src_file);
// map file in memory with write persmissions
static MappedFile mapfile_for_writing(const fs::path& file);
// preprocess the src file and compare the result to the previous version, return weather it's changed
// remove cached bin if true
static bool preprocess_and_compare();
// only recompile if changes are present
static void compile_src_file();
static void print_usage();
static bool is_src_file(string_view path);
static const char DEBUG_PREFIX[] = "__DBG";
// Context
static bool debug = false;
static fs::path src_file;
static fs::path cache_dir;
static fs::path bin; // cache bins to avoid recompiles
int main(int argc, char* argv[])
{
int src_arg = parse_args_until_src(argc, argv);
// Init context
src_file = find_path_to_cpp( argv[src_arg] );
cache_dir = get_cache_dir_path(src_file);
bin = cache_dir / (debug ? DEBUG_PREFIX : "") += src_file.filename();
// Compile the src
compile_src_file();
// Run the src file without forking
Cmd run;
if(debug) // debug it with gdb
{
run += "gdb";
run += "--args";
}
run += bin.c_str();
for(int i = src_arg+1; i < argc; ++i)
run += argv[i];
exec(run);
}
static int parse_args_until_src(int argc, char* argv[])
{
if(argc < 2)
{
print_usage();
exit(1);
}
int src_arg = 0;
for(int i = 1; i < argc; ++i)
{
string_view arg( argv[i] );
if( arg == "--help" )
{
print_usage();
cout << "Compile and run C++ source CPP_FILE that uses the cppipe library.\n"
"Pass the ARGUMENTS to the compiled binary.\n"
"The binaries are cached and recompiled only if the source or it's headers have changed.\n"
"-g debug the binary, asserts are also enabled\n";
exit(0);
}
else if( arg == "-g" )
debug = true;
else if( is_src_file(arg) )
{
src_arg = i;
break;
}
}
if( !src_arg )
{
print_usage();
exit(1);
}
return src_arg;
}
static fs::path find_path_to_cpp(string_view src_file)
{
if(fs::exists(src_file)) // found relative to CWD
{
return src_file;
}
// search files on CPPIPEPATH
const char* cppipepath_var = getenv("CPPIPEPATH");
if(cppipepath_var) // is set
{
const string_view cppipepath = cppipepath_var;
for(size_t begin = 0, end = cppipepath.find(':');
;
begin = end+1, end = cppipepath.find(':', begin) )
{
const string_view path_entry = cppipepath.substr(begin, end - begin);
if( fs::is_directory(path_entry) )
{
for(const fs::directory_entry& e: fs::directory_iterator(path_entry))
{
if(fs::is_regular_file(e) && e.path().filename() == src_file)
{
return e;
}
}
}
if(end == string::npos)
break;
}
}
// Couln't find the cpp
cerr << "File: " << src_file << " doesn't exist\n";
exit(1);
}
static fs::path get_cache_dir_path(const fs::path& src_file)
{
fs::path cache_dir;
if(char* xdg_cache = getenv("XDG_CACHE_HOME"))
{
cache_dir = xdg_cache;
cache_dir /= "cppipe";
}
else
{
char* home( getenv("HOME") );
cache_dir = home;
cache_dir /= ".cache/cppipe";
}
cache_dir += fs::canonical( src_file ).parent_path();
fs::create_directories(cache_dir);
return cache_dir;
}
static MappedFile mapfile_for_writing(const fs::path& file)
{
fd_t fd = open(file.c_str(), O_RDWR);
MappedFile res;
res.len = lseek(fd, 0, SEEK_END);
res.data = (char*)mmap(nullptr, res.len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
return res;
}
static bool preprocess_and_compare()
{
Cmd preprocess(
CXX,
"-E", // preprocess only
"-P", // don't generate linemarkers in the output to reduce file size
src_file.c_str()
);
if(!debug)
preprocess += "-DNDEBUG";
fs::path old_pp_path = cache_dir / (debug ? DEBUG_PREFIX : "") += src_file.stem() += ".ii";
string new_pp = $(preprocess);
if( fs::exists(old_pp_path) ) // todo: clean up if else blocks
{
if( fs::file_size(old_pp_path) == new_pp.size() )
{
MappedFile old_pp = mapfile_for_writing(old_pp_path);
if( !memcmp(old_pp.data, &new_pp[0], old_pp.len) ) // unchanged
{
return false;
}
else
{
memcpy(old_pp.data, &new_pp[0], old_pp.len);
munmap(old_pp.data, old_pp.len);
return true;
}
// todo
// munmap(data, len);
}
else
{
fs::remove(bin); // rm old bin
ofstream pp_file(old_pp_path);
pp_file << new_pp;
return true;
}
}
else
{
fs::remove(bin); // rm old bin
ofstream pp_file(old_pp_path);
pp_file << new_pp;
return true;
}
}
static void compile_src_file()
{
// Only compile if the source is newer then the bin
if(preprocess_and_compare() || !fs::exists(bin))
{
string preprocessed_file = cache_dir / src_file.stem() += ".ii"; // todo: duplicates with old_pp_path // todo: support C
Cmd compile(
CXX,
preprocessed_file.c_str(),
"-o", bin.c_str(),
CXXFLAGS
);
if(debug)
{
compile.append_args({ DEBUG_FLAGS });
}
else
{
compile.append_args({ RELEASE_FLAGS });
}
if( !compile() ) // if failed to compile
exit(1);
}
}
static void print_usage()
{
cout << "Usage: cppipe [-g] CPP_FILE [ARGUMENTS]...\n";
}
static bool is_src_file(const string_view p)
{
size_t end = p.size() - 1;
if( (p.size() > 4 && p[end-3] == '.' && p[end-2] == 'c' && p[end-1] == 'p' && p[end] == 'p')
|| (p.size() > 2 && p[end-1] == '.' && p[end] == 'c'))
return true;
else
return false;
}