Implement passing arguments to program via compile-run (#1296)

This commit is contained in:
Alexey Kutepov
2024-08-04 00:47:52 +07:00
committed by GitHub
parent 43ea05aad2
commit a5b5f315d1
11 changed files with 267 additions and 65 deletions

View File

@@ -43,6 +43,7 @@ jobs:
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\ls.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\load_world.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\process.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\args.c3 -- foo -bar "baz baz"
..\build\${{ matrix.build_type }}\c3c.exe compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3
@@ -132,6 +133,7 @@ jobs:
../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
../build/c3c compile-run --print-linking examples/load_world.c3
../build/c3c compile-run --print-linking examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
@@ -188,6 +190,7 @@ jobs:
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
../build/c3c compile --no-entry --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
@@ -304,6 +307,7 @@ jobs:
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |
@@ -445,6 +449,7 @@ jobs:
../build/c3c compile-run examples/process.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |
@@ -524,6 +529,7 @@ jobs:
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run -O5 examples/load_world.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
- name: Compile run unit tests
run: |

3
.gitignore vendored
View File

@@ -67,3 +67,6 @@ out/
/cmake-build-debug/
/cmake-build-release/
# Emacs files
TAGS

View File

@@ -252,6 +252,8 @@ add_executable(c3c
src/compiler/codegen_general.c
src/compiler/compiler.c
src/compiler/compiler.h
src/compiler/subprocess.c
src/compiler/subprocess.h
src/compiler/context.c
src/compiler/copying.c
src/compiler/diagnostics.c
@@ -369,7 +371,8 @@ endif()
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/src/" "${CMAKE_SOURCE_DIR}/wrapper/include/")
"${CMAKE_SOURCE_DIR}/src/"
"${CMAKE_SOURCE_DIR}/wrapper/include/")
target_include_directories(c3c_wrappers PRIVATE
@@ -424,7 +427,7 @@ else()
message(STATUS "using gcc/clang warning switches")
target_link_options(c3c PRIVATE -pthread)
if (NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PUBLIC -fno-rtti)
target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
endif()
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)

View File

@@ -0,0 +1,6 @@
import std::io;
fn void main(String[] args)
{
io::printn(args);
}

View File

@@ -8,6 +8,7 @@
#define MAX_LIB_DIRS 1024
#define MAX_FILES 2048
#define MAX_ARGS 2048
#define MAX_INCLUDES 2048
#define MAX_THREADS 0xFFFF
#define DEFAULT_SYMTAB_SIZE (256 * 1024)
@@ -373,6 +374,7 @@ typedef struct BuildOptions_
int build_threads;
const char **libraries_to_fetch;
const char **files;
const char **args;
const char **feature_names;
const char **removed_feature_names;
const char *output_name;
@@ -555,6 +557,7 @@ typedef struct
LinkerType linker_type;
uint32_t symtab_size;
uint32_t switchrange_max_size;
const char **args;
const char *panicfn;
const char *benchfn;
const char *testfn;
@@ -652,6 +655,7 @@ static BuildTarget default_build_target = {
BuildOptions parse_arguments(int argc, const char *argv[]);
ArchOsTarget arch_os_target_from_string(const char *target);
bool command_accepts_files(CompilerCommand command);
bool command_passes_args(CompilerCommand command);
void update_build_target_with_opt_level(BuildTarget *target, OptimizationSetting level);
void create_project(BuildOptions *build_options);
void create_library(BuildOptions *build_options);

View File

@@ -70,7 +70,7 @@ static void usage(void)
PRINTF(" directives [<target>] Generate documentation for the target.");
PRINTF(" bench [<target>] Benchmark a target.");
PRINTF(" clean-run [<target>] Clean, then run the target.");
PRINTF(" compile-run <file1> [<file2> ...] Compile files then immediately run the result.");
PRINTF(" compile-run <file1> [<file2> ...] [-- [<arg1> ...]] Compile files then immediately run the result.");
PRINTF(" compile-only <file1> [<file2> ...] Compile files but do not perform linking.");
PRINTF(" compile-benchmark <file1> [<file2> ...] Compile files into an executable and run benchmarks.");
PRINTF(" compile-test <file1> [<file2> ...] Compile files into an executable and run unit tests.");
@@ -250,6 +250,16 @@ void append_file(BuildOptions *build_options)
vec_add(build_options->files, current_arg);
}
void append_arg(BuildOptions *build_options)
{
if (vec_size(build_options->args) == MAX_ARGS)
{
EOUTPUT("Max %d args may be specified.", MAX_ARGS);
exit_compiler(EXIT_FAILURE);
}
vec_add(build_options->args, current_arg);
}
static bool arg_match(const char *candidate)
{
return strcmp(current_arg, candidate) == 0;
@@ -1189,9 +1199,19 @@ BuildOptions parse_arguments(int argc, const char *argv[])
build_options.severity[i] = DIAG_ERROR;
}
bool collecting_args = false;
for (arg_index = 1; arg_index < arg_count; arg_index++)
{
current_arg = args[arg_index];
if (collecting_args) {
append_arg(&build_options);
continue;
}
if (command_passes_args(build_options.command) && arg_match("--"))
{
collecting_args = true;
continue;
}
if (current_arg[0] == '-')
{
parse_option(&build_options);

View File

@@ -92,6 +92,38 @@ bool command_accepts_files(CompilerCommand command)
UNREACHABLE
}
bool command_passes_args(CompilerCommand command)
{
switch (command)
{
case COMMAND_COMPILE_RUN:
return true;
case COMMAND_COMPILE:
case COMMAND_COMPILE_ONLY:
case COMMAND_DYNAMIC_LIB:
case COMMAND_STATIC_LIB:
case COMMAND_COMPILE_BENCHMARK:
case COMMAND_COMPILE_TEST:
case COMMAND_UNIT_TEST:
case COMMAND_MISSING:
case COMMAND_GENERATE_HEADERS:
case COMMAND_INIT:
case COMMAND_INIT_LIB:
case COMMAND_BUILD:
case COMMAND_RUN:
case COMMAND_CLEAN_RUN:
case COMMAND_CLEAN:
case COMMAND_DIST:
case COMMAND_DOCS:
case COMMAND_BENCH:
case COMMAND_PRINT_SYNTAX:
case COMMAND_BENCHMARK:
case COMMAND_TEST:
case COMMAND_VENDOR_FETCH:
return false;
}
UNREACHABLE
}
void update_build_target_with_opt_level(BuildTarget *target, OptimizationSetting level)
{
@@ -213,6 +245,7 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions *
case COMMAND_CLEAN_RUN:
target->run_after_compile = true;
target->delete_after_run = options->run_once;
target->args = options->args;
break;
case COMMAND_COMPILE_ONLY:
target->type = TARGET_TYPE_OBJECT_FILES;

View File

@@ -9,6 +9,7 @@
#include <sys/wait.h>
#endif
#include "c3_llvm.h"
#include <errno.h>
#define MAX_OUTPUT_FILES 1000000
#define MAX_MODULES 100000
@@ -649,39 +650,20 @@ void compiler_compile(void)
scratch_buffer_append(name);
}
name = scratch_buffer_to_string();
printf("Launching %s...\n", name);
int ret = system(name);
printf("Launching %s", name);
for (uint32_t i = 0; i < vec_size(active_target.args); ++i) {
printf(" %s", active_target.args[i]);
}
printf("\n");
int ret = run_subprocess(name, active_target.args);
if (active_target.delete_after_run)
{
file_delete_file(name);
}
#if PLATFORM_POSIX
if (WIFEXITED(ret))
{
int status = WEXITSTATUS(ret);
printf("Program completed with exit code %d.\n", status);
if (status != 0) exit(status);
}
else if (WIFSIGNALED(ret))
{
printf("Program interrupted by signal %d.\n", WTERMSIG(ret));
exit(EXIT_FAILURE);
}
else if (WIFSTOPPED(ret))
{
printf("Program stopped by signal %d.\n", WSTOPSIG(ret));
}
else
{
printf("Program finished with unexpected code %d.\n", ret);
}
#else
if (ret < 0) exit_compiler(EXIT_FAILURE);
printf("Program completed with exit code %d.\n", ret);
if (ret != 0) exit(ret);
#endif
if (ret != 0) exit_compiler(ret);
}
}
else if (output_static)

View File

@@ -9,6 +9,7 @@
#include "enums.h"
#include "target.h"
#include "utils/malloc.h"
#include "subprocess.h"
#include <float.h>
typedef double Real;

141
src/compiler/subprocess.c Normal file
View File

@@ -0,0 +1,141 @@
#include "../utils/common.h"
#include "../utils/lib.h"
#include <errno.h>
#include "subprocess.h"
#if PLATFORM_POSIX
#include <sys/wait.h>
#endif
#if PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
int run_subprocess(const char *name, const char **args)
{
#if PLATFORM_WINDOWS
// https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
STARTUPINFOA siStartInfo;
ZeroMemory(&siStartInfo, sizeof(siStartInfo));
siStartInfo.cb = sizeof(STARTUPINFO);
// NOTE: theoretically setting NULL to std handles should not be a problem
// https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior
// TODO: check for errors in GetStdHandle
siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
PROCESS_INFORMATION piProcInfo;
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
scratch_buffer_clear();
scratch_buffer_printf("%s", name);
for (uint32_t i = 0; i < vec_size(args); i++) {
scratch_buffer_append_char(' ');
bool need_quoting = strpbrk(args[i], "\t\v ") != NULL || args[i][0] == 0;
if (need_quoting) scratch_buffer_append_char('"');
for (int j = 0; '\0' != args[i][j]; j++) {
switch (args[i][j]) {
default:
break;
case '\\':
if (args[i][j + 1] == '"') {
scratch_buffer_append_char('\\');
}
break;
case '"':
scratch_buffer_append_char('\\');
break;
}
scratch_buffer_append_char(args[i][j]);
}
if (need_quoting) scratch_buffer_append_char('"');
}
BOOL bSuccess = CreateProcessA(
NULL,
scratch_buffer_to_string(),
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&siStartInfo,
&piProcInfo);
if (!bSuccess) {
eprintf("Could not create child process: %lu", GetLastError());
return -1;
}
CloseHandle(piProcInfo.hThread);
DWORD result = WaitForSingleObject(
piProcInfo.hProcess, // HANDLE hHandle,
INFINITE // DWORD dwMilliseconds
);
if (result == WAIT_FAILED) {
eprintf("Could not wait on child process: %lu", GetLastError());
return -1;
}
DWORD exit_status;
if (!GetExitCodeProcess(piProcInfo.hProcess, &exit_status)) {
eprintf("Could not get process exit code: %lu", GetLastError());
return -1;
}
CloseHandle(piProcInfo.hProcess);
return exit_status;
#else
pid_t cpid = fork();
if (cpid < 0) {
eprintf("Could not fork child process %s: %s", name, strerror(errno));
return -1;
}
if (cpid == 0) {
const char **args_null = NULL;
vec_add(args_null, name);
for (uint32_t i = 0; i < vec_size(args); ++i) {
vec_add(args_null, args[i]);
}
vec_add(args_null, NULL);
if (execvp(name, (char * const*)args_null) < 0) {
eprintf("Could not exec child process %s: %s", name, strerror(errno));
exit(1);
}
UNREACHABLE
}
for (;;) {
int wstatus = 0;
if (waitpid(cpid, &wstatus, 0) < 0) {
eprintf("Could not wait on %s (pid %d): %s", name, cpid, strerror(errno));
return -1;
}
if (WIFEXITED(wstatus)) {
return WEXITSTATUS(wstatus);
}
if (WIFSIGNALED(wstatus)) {
eprintf("Program interrupted by signal %d.\n", WTERMSIG(wstatus));
return -1;
}
}
return cpid;
#endif
}

View File

@@ -0,0 +1,3 @@
#pragma once
int run_subprocess(const char *name, const char **args);