From e6c09f1e0e8a7c2ca0e455db7f0199cb55f18125 Mon Sep 17 00:00:00 2001 From: Alberto Gonzalez Date: Sun, 15 Mar 2020 09:16:04 +0000 Subject: Add `exec` command to run shell commands. --- passes/cmds/Makefile.inc | 1 + passes/cmds/exec.cc | 156 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 passes/cmds/exec.cc (limited to 'passes') diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 20b38bf8e..60f20fa6d 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -1,4 +1,5 @@ +OBJS += passes/cmds/exec.o OBJS += passes/cmds/add.o OBJS += passes/cmds/delete.o OBJS += passes/cmds/design.o diff --git a/passes/cmds/exec.cc b/passes/cmds/exec.cc new file mode 100644 index 000000000..9b9f136dc --- /dev/null +++ b/passes/cmds/exec.cc @@ -0,0 +1,156 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/register.h" +#include "kernel/log.h" +#include +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct ExecPass : public Pass { + ExecPass() : Pass("exec", "execute commands in the operating system shell") { } + void help() YS_OVERRIDE + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" exec [options] -- [command]\n"); + log("\n"); + log("Execute a command in the operating system shell. All supplied arguments are\n"); + log("concatenated and passed as a command to popen(3). Whitespace is not guaranteed\n"); + log("to be preserved, even if quoted. stdin is not connected, while stdout is\n"); + log("logged and stderr is logged as a warning.\n"); + log("\n"); + log("\n"); + log(" -q\n"); + log(" suppress stdout and stderr from subprocess\n"); + log("\n"); + log(" -expected-return \n"); + log(" generates an error if popen() does not return specified value.\n"); + log("\n"); + log(" -expected-stdout \n"); + log(" generates an error if specified regex does not match any line\n"); + log(" in subprocess stdout.\n"); + log("\n"); + log("\n"); + log(" Example: exec -q -expected-return 0 -- echo \"bananapie\" | grep \"nana\"\n"); + log("\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE + { + if(args.size() == 0) + log_cmd_error("No command provided.\n"); + + std::string cmd = ""; + char buf[4096] = {}; + std::string linebuf = ""; + bool flag_cmd = false; + bool flag_quiet = false; + bool flag_expected_return = false; + bool flag_expected_stdout = false; + int expected_return_value = 0; + YS_REGEX_TYPE expected_stdout_re; + std::string expected_stdout_re_str; + bool expected_stdout_re_matched = false; + + for(size_t argidx = 1; argidx < args.size(); ++argidx) { + if (flag_cmd) { + cmd += args[argidx] + (argidx != (args.size() - 1)? " " : ""); + } else { + if (args[argidx] == "--") + flag_cmd = true; + else if (args[argidx] == "-q") + flag_quiet = true; + else if (args[argidx] == "-expected-return") { + flag_expected_return = true; + ++argidx; + if (argidx >= args.size()) + log_cmd_error("No expected return value specified.\n"); + + expected_return_value = atoi(args[argidx].c_str()); + } else if (args[argidx] == "-expected-stdout") { + flag_expected_stdout = true; + ++argidx; + if (argidx >= args.size()) + log_cmd_error("No expected regular expression to find in stdout specified.\n"); + + try{ + expected_stdout_re_str = args[argidx]; + expected_stdout_re = YS_REGEX_COMPILE(args[argidx]); + } catch (const YS_REGEX_NS::regex_error& e) { + log_cmd_error("Error in regex expression '%s' !\n", expected_stdout_re_str.c_str()); + } + + } else + log_cmd_error("Unknown option \"%s\" or \"--\" doesn\'t precede command.", args[argidx].c_str()); + } + } + + log_header(design, "Executing command \"%s\".\n", cmd.c_str()); + log_push(); + + fflush(stdout); + bool keep_reading = true; + auto *f = popen(cmd.c_str(), "r"); + if (f == nullptr) + log_cmd_error("errno %d after popen() returned NULL.\n", errno); + while (keep_reading) { + keep_reading = (fgets(buf, sizeof(buf), f) != nullptr); + linebuf += buf; + memset(buf, 0, sizeof(buf)); + + auto pos = linebuf.find('\n'); + while (pos != std::string::npos) { + std::string line = linebuf.substr(0, pos); + linebuf.erase(0, pos + 1); + if (!flag_quiet) + log("%s\n", line.c_str()); + + if(YS_REGEX_NS::regex_search(line, expected_stdout_re)) + expected_stdout_re_matched = true; + + pos = linebuf.find('\n'); + } + } + int status = pclose(f); + int retval = -1; + + if(WIFEXITED(status)) { + retval = WEXITSTATUS(status); + } + else if(WIFSIGNALED(status)) { + retval = WTERMSIG(status); + } + else if(WIFSTOPPED(status)) { + retval = WSTOPSIG(status); + } + + if (flag_expected_return && retval != expected_return_value) + log_cmd_error("Return value %d did not match expected return value %d.\n", retval, expected_return_value); + + if (flag_expected_stdout && !expected_stdout_re_matched) + log_cmd_error("Command stdout did not have a line matching given regex \"%s\".\n", expected_stdout_re_str.c_str()); + + log_pop(); + } +} ExecPass; + +PRIVATE_NAMESPACE_END -- cgit v1.2.3 From 8ba49a8462141db4ad715ec7b9e84b0d0d487cd6 Mon Sep 17 00:00:00 2001 From: Alberto Gonzalez Date: Mon, 16 Mar 2020 06:02:14 +0000 Subject: Allow specifying multiple regexes to match in `exec` command output, and also to specify regexes that must _not_ match. --- passes/cmds/exec.cc | 100 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 33 deletions(-) (limited to 'passes') diff --git a/passes/cmds/exec.cc b/passes/cmds/exec.cc index 9b9f136dc..ec7b5b377 100644 --- a/passes/cmds/exec.cc +++ b/passes/cmds/exec.cc @@ -35,41 +35,54 @@ struct ExecPass : public Pass { log("\n"); log("Execute a command in the operating system shell. All supplied arguments are\n"); log("concatenated and passed as a command to popen(3). Whitespace is not guaranteed\n"); - log("to be preserved, even if quoted. stdin is not connected, while stdout is\n"); - log("logged and stderr is logged as a warning.\n"); + log("to be preserved, even if quoted. stdin and stderr are not connected, while stdout is\n"); + log("logged unless the \"-q\" option is specified.\n"); log("\n"); log("\n"); log(" -q\n"); - log(" suppress stdout and stderr from subprocess\n"); + log(" Suppress stdout and stderr from subprocess\n"); log("\n"); - log(" -expected-return \n"); - log(" generates an error if popen() does not return specified value.\n"); + log(" -expect-return \n"); + log(" Generate an error if popen() does not return specified value.\n"); + log(" May only be specified once; the final specified value is controlling\n"); + log(" if specified multiple times.\n"); log("\n"); - log(" -expected-stdout \n"); - log(" generates an error if specified regex does not match any line\n"); - log(" in subprocess stdout.\n"); + log(" -expect-stdout \n"); + log(" Generate an error if the specified regex does not match any line\n"); + log(" in subprocess's stdout. May be specified multiple times.\n"); log("\n"); + log(" -not-expect-stdout \n"); + log(" Generate an error if the specified regex matches any line\n"); + log(" in subprocess's stdout. May be specified multiple times.\n"); log("\n"); - log(" Example: exec -q -expected-return 0 -- echo \"bananapie\" | grep \"nana\"\n"); + log("\n"); + log(" Example: exec -q -expect-return 0 -- echo \"bananapie\" | grep \"nana\"\n"); log("\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE { - if(args.size() == 0) - log_cmd_error("No command provided.\n"); - std::string cmd = ""; char buf[4096] = {}; std::string linebuf = ""; bool flag_cmd = false; bool flag_quiet = false; - bool flag_expected_return = false; - bool flag_expected_stdout = false; - int expected_return_value = 0; - YS_REGEX_TYPE expected_stdout_re; - std::string expected_stdout_re_str; - bool expected_stdout_re_matched = false; + bool flag_expect_return = false; + int expect_return_value = 0; + bool flag_expect_stdout = false; + struct expect_stdout_elem { + bool matched; + bool polarity; //true: this regex must match at least one line + //false: this regex must not match any line + std::string str; + YS_REGEX_TYPE re; + + expect_stdout_elem() : matched(false), polarity(true), str(), re(){}; + }; + std::vector expect_stdout; + + if(args.size() == 0) + log_cmd_error("No command provided.\n"); for(size_t argidx = 1; argidx < args.size(); ++argidx) { if (flag_cmd) { @@ -79,24 +92,41 @@ struct ExecPass : public Pass { flag_cmd = true; else if (args[argidx] == "-q") flag_quiet = true; - else if (args[argidx] == "-expected-return") { - flag_expected_return = true; + else if (args[argidx] == "-expect-return") { + flag_expect_return = true; ++argidx; if (argidx >= args.size()) log_cmd_error("No expected return value specified.\n"); - expected_return_value = atoi(args[argidx].c_str()); - } else if (args[argidx] == "-expected-stdout") { - flag_expected_stdout = true; + expect_return_value = atoi(args[argidx].c_str()); + } else if (args[argidx] == "-expect-stdout") { + flag_expect_stdout = true; + ++argidx; + if (argidx >= args.size()) + log_cmd_error("No expected regular expression specified.\n"); + + try{ + expect_stdout_elem x; + x.str = args[argidx]; + x.re = YS_REGEX_COMPILE(args[argidx]); + expect_stdout.push_back(x); + } catch (const YS_REGEX_NS::regex_error& e) { + log_cmd_error("Error in regex expression '%s' !\n", args[argidx].c_str()); + } + } else if (args[argidx] == "-not-expect-stdout") { + flag_expect_stdout = true; ++argidx; if (argidx >= args.size()) - log_cmd_error("No expected regular expression to find in stdout specified.\n"); + log_cmd_error("No expected regular expression specified.\n"); try{ - expected_stdout_re_str = args[argidx]; - expected_stdout_re = YS_REGEX_COMPILE(args[argidx]); + expect_stdout_elem x; + x.str = args[argidx]; + x.re = YS_REGEX_COMPILE(args[argidx]); + x.polarity = false; + expect_stdout.push_back(x); } catch (const YS_REGEX_NS::regex_error& e) { - log_cmd_error("Error in regex expression '%s' !\n", expected_stdout_re_str.c_str()); + log_cmd_error("Error in regex expression '%s' !\n", args[argidx].c_str()); } } else @@ -124,8 +154,10 @@ struct ExecPass : public Pass { if (!flag_quiet) log("%s\n", line.c_str()); - if(YS_REGEX_NS::regex_search(line, expected_stdout_re)) - expected_stdout_re_matched = true; + if (flag_expect_stdout) + for(auto &x : expect_stdout) + if (YS_REGEX_NS::regex_search(line, x.re)) + x.matched = true; pos = linebuf.find('\n'); } @@ -143,11 +175,13 @@ struct ExecPass : public Pass { retval = WSTOPSIG(status); } - if (flag_expected_return && retval != expected_return_value) - log_cmd_error("Return value %d did not match expected return value %d.\n", retval, expected_return_value); + if (flag_expect_return && retval != expect_return_value) + log_cmd_error("Return value %d did not match expected return value %d.\n", retval, expect_return_value); - if (flag_expected_stdout && !expected_stdout_re_matched) - log_cmd_error("Command stdout did not have a line matching given regex \"%s\".\n", expected_stdout_re_str.c_str()); + if (flag_expect_stdout) + for (auto &x : expect_stdout) + if (x.polarity ^ x.matched) + log_cmd_error("Command stdout did%s have a line matching given regex \"%s\".\n", (x.polarity? " not" : ""), x.str.c_str()); log_pop(); } -- cgit v1.2.3 From cbc5664d370230803ac25705485ef1c6ed5f8416 Mon Sep 17 00:00:00 2001 From: Alberto Gonzalez Date: Mon, 16 Mar 2020 16:42:04 +0000 Subject: Clean up `exec` code according to review. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Miodrag Milanović --- passes/cmds/exec.cc | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'passes') diff --git a/passes/cmds/exec.cc b/passes/cmds/exec.cc index ec7b5b377..7b5428f16 100644 --- a/passes/cmds/exec.cc +++ b/passes/cmds/exec.cc @@ -20,7 +20,16 @@ #include "kernel/register.h" #include "kernel/log.h" #include -#include + +#if defined(_WIN32) +# define WIFEXITED(x) 1 +# define WIFSIGNALED(x) 0 +# define WIFSTOPPED(x) 0 +# define WEXITSTATUS(x) ((x) & 0xff) +# define WTERMSIG(x) SIGTERM +#else +# include +#endif USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -63,7 +72,7 @@ struct ExecPass : public Pass { void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE { std::string cmd = ""; - char buf[4096] = {}; + char buf[1024] = {}; std::string linebuf = ""; bool flag_cmd = false; bool flag_quiet = false; @@ -139,7 +148,11 @@ struct ExecPass : public Pass { fflush(stdout); bool keep_reading = true; - auto *f = popen(cmd.c_str(), "r"); + int status = 0; + int retval = 0; + +#ifndef EMSCRIPTEN + FILE *f = popen(cmd.c_str(), "r"); if (f == nullptr) log_cmd_error("errno %d after popen() returned NULL.\n", errno); while (keep_reading) { @@ -162,8 +175,8 @@ struct ExecPass : public Pass { pos = linebuf.find('\n'); } } - int status = pclose(f); - int retval = -1; + status = pclose(f); +#endif if(WIFEXITED(status)) { retval = WEXITSTATUS(status); -- cgit v1.2.3 From 7ea7fb700b4e7450b679d8be8a3380ecba721b68 Mon Sep 17 00:00:00 2001 From: Alberto Gonzalez Date: Wed, 18 Mar 2020 09:14:22 +0000 Subject: Update copyright and license header. I hereby assign to Claire Wolf the copyright for all work I did on `passes/cmds/exec.cc`. In the event that this copyright assignment is not legally valid, I offer this work under the ISC license. --- passes/cmds/exec.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'passes') diff --git a/passes/cmds/exec.cc b/passes/cmds/exec.cc index 7b5428f16..399cb0ebb 100644 --- a/passes/cmds/exec.cc +++ b/passes/cmds/exec.cc @@ -1,7 +1,7 @@ /* * yosys -- Yosys Open SYnthesis Suite * - * Copyright (C) 2012 Clifford Wolf + * Copyright (C) 2012 - 2020 Claire Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above -- cgit v1.2.3