From edae7d98cfac5f55782236d397c831f68ebe11a6 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 9 Nov 2010 14:02:41 +0200 Subject: Implement new compilation architecture Instead of compiling the header directly, g++ is now invoked to compile stdin. At the same time the odb driver pipes the original header to g++. This new approach allows us to add some source code before and/or after the original header. --- doc/makefile | 2 + odb/makefile | 1 + odb/odb.cxx | 498 ++++++++++++++++++++++++++++++++++++++++++++++++-------- odb/options.cli | 6 + odb/plugin.cxx | 92 ++++++++++- 5 files changed, 526 insertions(+), 73 deletions(-) diff --git a/doc/makefile b/doc/makefile index ae60070..2885a9d 100644 --- a/doc/makefile +++ b/doc/makefile @@ -31,6 +31,7 @@ $(out_base)/odb.xhtml: $(src_root)/odb/options.cli \ $(src_base)/odb-prologue.xhtml \ $(src_base)/odb-epilogue.xhtml | $(out_base)/. $(call message,cli-html $<,$(cli) --generate-html --stdout \ +--suppress-undocumented \ --html-prologue $(src_base)/odb-prologue.xhtml \ --html-epilogue $(src_base)/odb-epilogue.xhtml $< >$@) @@ -38,6 +39,7 @@ $(out_base)/odb.1: $(src_root)/odb/options.cli \ $(src_base)/odb-prologue.1 \ $(src_base)/odb-epilogue.1 | $(out_base)/. $(call message,cli-man $<,$(cli) --generate-man --stdout \ +--suppress-undocumented \ --man-prologue $(src_base)/odb-prologue.1 \ --man-epilogue $(src_base)/odb-epilogue.1 $< >$@) diff --git a/odb/makefile b/odb/makefile index 761dcb0..ac11dca 100644 --- a/odb/makefile +++ b/odb/makefile @@ -118,6 +118,7 @@ $(gen): $(cli) $(gen): cli := $(cli) $(gen): cli_options += \ --generate-specifier \ +--suppress-undocumented \ --generate-file-scanner \ --include-with-brackets \ --include-prefix odb \ diff --git a/odb/odb.cxx b/odb/odb.cxx index c94400e..2a947f5 100644 --- a/odb/odb.cxx +++ b/odb/odb.cxx @@ -5,20 +5,32 @@ #include #include // getenv, setenv -#include // strerror -#include // stat, execvp +#include // strerror, memset +#include // stat #include // stat #include // stat -#ifdef _WIN32 -# include // _spawnvp +// Process. +// +#ifndef _WIN32 +# include // execvp, fork, dup2, pipe, {STDIN,STDERR}_FILENO +# include // waitpid +# include // waitpid +#else +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include // CreatePipe, CreateProcess +# include // _open_osfhandle #endif #include #include #include // size_t #include +#include #include +#include #include @@ -33,12 +45,66 @@ using namespace std; using cutl::fs::path; using cutl::fs::invalid_path; +// +// Path manipulation. +// + +// Escape backslashes in the path. +// +static string +escape_path (path const& p); + +// Search the PATH environment variable for the file. +// +static path +path_search (path const&); + +// Driver path with the directory part (search PATH). +// static path driver_path (path const& driver); static path plugin_path (path const& driver); +// +// Process manipulation. +// +struct process_info +{ +#ifndef _WIN32 + pid_t id; +#else + HANDLE id; +#endif + + int fd; +}; + +struct process_failure {}; + +// Start another process using the specified command line. Connect the +// newly created process stdin to our stdout. Issue diagnostics and +// throw process_failure if anything goes wrong. The name argument +// is the name of the current process for diagnostics. +// +static process_info +start_process (char const* args[], char const* name); + +// Close stdout and wait for the process to terminate. Return true if +// the process terminated normally and with the zero exit status. Issue +// diagnostics and throw process_failure if anything goes wrong. The name +// argument is the name of the current process for diagnostics. +// +static bool +wait_process (process_info, char const* name); + +// +// +static string +encode_plugin_option (string const& k, string const& v); + + static char const* const db_macro[] = { "-DODB_DATABASE_MYSQL", @@ -146,6 +212,8 @@ main (int argc, char* argv[]) // Parse driver options. // + bool first_x (true); + for (int i = 1; i < argc; ++i) { string a (argv[i]); @@ -170,15 +238,20 @@ main (int argc, char* argv[]) a = argv[i]; - if (a[0] == '-') - args.push_back (a); - else + if (first_x) { - // This must be the g++ executable name. Update the first - // argument with it. + first_x = false; + + // If it doesn't start with '-', then it must be the g++ + // executable name. Update the first argument with it. // - args[0] = a; + if (a[0] != '-') + args[0] = a; + else + args.push_back (a); } + else + args.push_back (a); } // -I // @@ -345,78 +418,89 @@ main (int argc, char* argv[]) } } - string o ("-fplugin-arg-odb-"); - o += k; - - if (!v.empty ()) - { - o += '='; - - // On Windows we need to protect values with spaces using quotes. - // Since there could be actual quotes in the value, we need to - // escape them. - // -#ifdef _WIN32 - { - string t ("\""); - for (size_t i (0); i < v.size (); ++i) - { - if (v[i] == '"') - t += "\\\""; - else - t += v[i]; - } - t += '"'; - v = t; - } -#endif - o += v; - } - - args.push_back (o); + args.push_back (encode_plugin_option (k, v)); } - // Copy over arguments. + // Reserve space for and remember the position of the --svc-file + // option. // - args.insert (args.end (), plugin_args.begin () + end, plugin_args.end ()); - + size_t svc_file_pos (args.size ()); + args.push_back (""); // Create an execvp-compatible argument array. // - vector exec_args; + typedef vector cstrings; + cstrings exec_args; for (strings::const_iterator i (args.begin ()), end (args.end ()); i != end; ++i) { exec_args.push_back (i->c_str ()); + } + + exec_args.push_back ("-"); // Compile stdin. + exec_args.push_back (0); + + // Iterate over the input files and compile each of them. + // + for (; end < plugin_args.size (); ++end) + { + path input (plugin_args[end]); + + // Set the --svc-file option. + // + args[svc_file_pos] = encode_plugin_option ("svc-file", input.string ()); + exec_args[svc_file_pos] = args[svc_file_pos].c_str (); + + // + // + ifstream ifs (input.string ().c_str (), ios_base::in | ios_base::binary); + + if (!ifs.is_open ()) + { + cerr << input << ": error: unable to open in read mode" << endl; + return 1; + } if (v) - e << *i << ' '; - } + { + e << "Compiling " << input << endl; + for (cstrings::const_iterator i (exec_args.begin ()); + i != exec_args.end (); ++i) + { + if (*i != 0) + e << *i << (*(i + 1) != 0 ? ' ' : '\n'); + } + } - if (v) - e << endl; + process_info pi (start_process (&exec_args[0], argv[0])); - exec_args.push_back (0); + { + __gnu_cxx::stdio_filebuf fb ( + pi.fd, ios_base::out | ios_base::binary); + ostream os (&fb); -#ifdef _WIN32 - intptr_t r (_spawnvp (_P_WAIT, exec_args[0], &exec_args[0])); + // Write the synthesized translation unit to stdout. + // + os << "#line 1 \"" << escape_path (input) << "\"" << endl; - if (r == (intptr_t)(-1)) - { - e << exec_args[0] << ": error: " << strerror (errno) << endl; - return 1; - } + if (!(os << ifs.rdbuf ())) + { + e << input << ": error: io failure" << endl; + wait_process (pi, argv[0]); + return 1; + } + } - return r == 0 ? 0 : 1; -#else - if (execvp (exec_args[0], const_cast (&exec_args[0])) < 0) - { - e << exec_args[0] << ": error: " << strerror (errno) << endl; - return 1; + if (!wait_process (pi, argv[0])) + return 1; } -#endif - + } + catch (process_failure const&) + { + // Diagnostics has already been issued. + // + return 1; } catch (invalid_path const& ex) { @@ -431,20 +515,72 @@ main (int argc, char* argv[]) } +static string +encode_plugin_option (string const& k, string const& cv) +{ + string o ("-fplugin-arg-odb-"), v (cv); + o += k; + + if (!v.empty ()) + { + o += '='; + + // On Windows we need to protect values with spaces using quotes. + // Since there could be actual quotes in the value, we need to + // escape them. + // +#ifdef _WIN32 + { + string t ("\""); + for (size_t i (0); i < v.size (); ++i) + { + if (v[i] == '"') + t += "\\\""; + else + t += v[i]; + } + t += '"'; + v = t; + } +#endif + o += v; + } + + return o; +} + // // Path manipulation. // +static string +escape_path (path const& p) +{ + string r; + string const& s (p.string ()); + + for (size_t i (0); i < s.size (); ++i) + { + if (s[i] == '\\') + r += "\\\\"; + else + r += s[i]; + } + + return r; +} + static path -driver_path (path const& drv) +path_search (path const& f) { typedef path::traits traits; - if (!drv.directory ().empty ()) - return drv; - - // Search the PATH environment variable. + // If there is a directory component in the file, then the PATH + // search does not apply. // + if (!f.directory ().empty ()) + return f; + string paths; // If there is no PATH in environment then the default search @@ -474,10 +610,9 @@ driver_path (path const& drv) if (p.empty ()) p = path ("."); - path dp (p / drv); + path dp (p / f); - // Just check that the file exist without checking for - // permissions, etc. + // Just check that the file exist without checking for permissions, etc. // if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode)) return dp; @@ -504,6 +639,12 @@ driver_path (path const& drv) } static path +driver_path (path const& drv) +{ + return drv.directory ().empty () ? path_search (drv) : drv; +} + +static path plugin_path (path const& drv) { path dp (driver_path (drv)); @@ -539,3 +680,218 @@ plugin_path (path const& drv) return path (); } + +// +// Process manipulation. +// + +#ifndef _WIN32 + +static process_info +start_process (char const* args[], char const* name) +{ + int fd[2]; + + if (pipe (fd) == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + + pid_t pid (fork ()); + + if (pid == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + + if (pid == 0) + { + // Child. Close the write end of the pipe and duplicate the read end + // to stdin. Then close the original read end descriptors. + // + if (close (fd[1]) == -1 || + dup2 (fd[0], STDIN_FILENO) == -1 || + close (fd[0]) == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + + if (execvp (args[0], const_cast (&args[0])) == -1) + { + char const* err (strerror (errno)); + cerr << args[0] << ": error: " << err << endl; + throw process_failure (); + } + } + else + { + // Parent. Close the read end of the pipe and. + // + if (close (fd[0]) == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + } + + process_info r; + r.id = pid; + r.fd = fd[1]; + return r; +} + +static bool +wait_process (process_info pi, char const* name) +{ + int status; + + if (waitpid (pi.id, &status, 0) == -1) + { + char const* err (strerror (errno)); + cerr << name << ": error: " << err << endl; + throw process_failure (); + } + + return WIFEXITED (status) && WEXITSTATUS (status) == 0; +} + +#else // _WIN32 + +static void +print_error (char const* name) +{ + LPTSTR msg; + DWORD e (GetLastError()); + + if (!FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + e, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &msg, + 0, + 0)) + { + cerr << name << ": error: unknown error code " << e << endl; + return; + } + + cerr << name << ": error: " << msg << endl; + LocalFree (msg); +} + +static process_info +start_process (char const* args[], char const* name) +{ + HANDLE in, out; + SECURITY_ATTRIBUTES sa; + + sa.nLength = sizeof (SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = 0; + + if (!CreatePipe (&in, &out, &sa, 0) || + !SetHandleInformation (out, HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + + // Create the process. + // + path file (args[0]); + + // Do PATH search. + // + if (file.directory ().empty ()) + file = path_search (file); + + if (file.empty ()) + { + cerr << args[0] << ": error: file not found" << endl; + throw process_failure (); + } + + // Serialize the arguments to string. + // + string cmd_line; + for (char const** p (args); *p != 0; ++p) + { + if (p != args) + cmd_line += ' '; + + cmd_line += *p; + } + + // Prepare other info. + // + STARTUPINFO si; + PROCESS_INFORMATION pi; + + memset (&si, 0, sizeof (STARTUPINFO)); + memset (&pi, 0, sizeof (PROCESS_INFORMATION)); + + si.cb = sizeof(STARTUPINFO); + si.hStdError = GetStdHandle (STD_ERROR_HANDLE); + si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE); + si.hStdInput = in; + si.dwFlags |= STARTF_USESTDHANDLES; + + if (!CreateProcess ( + file.string ().c_str (), + const_cast (cmd_line.c_str ()), + 0, // Process security attributes. + 0, // Primary thread security attributes. + true, // Inherit handles. + 0, // Creation flags. + 0, // Use our environment. + 0, // Use our current directory. + &si, + &pi)) + { + print_error (name); + throw process_failure (); + } + + CloseHandle (pi.hThread); + + int fd (_open_osfhandle ((intptr_t) (out), 0)); + + if (fd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + + process_info r; + r.id = pi.hProcess; + r.fd = fd; + return r; +} + +static bool +wait_process (process_info pi, char const* name) +{ + DWORD status; + + if (WaitForSingleObject (pi.id, INFINITE) != WAIT_OBJECT_0 || + !GetExitCodeProcess (pi.id, &status)) + { + print_error (name); + throw process_failure (); + } + + CloseHandle (pi.id); + return status == 0; +} + +#endif // _WIN32 diff --git a/odb/options.cli b/odb/options.cli index 4abfc84..5d24f8a 100644 --- a/odb/options.cli +++ b/odb/options.cli @@ -288,4 +288,10 @@ class options database-default engine, pass \cb{default} as the value for this option." }; + + // + // Undocumented options that are used to pass service information + // between the frontend and the plugin. + // + std::string --svc-file; }; diff --git a/odb/plugin.cxx b/odb/plugin.cxx index 1947c6e..d5134d9 100644 --- a/odb/plugin.cxx +++ b/odb/plugin.cxx @@ -8,6 +8,7 @@ #include // std::auto_ptr #include #include +#include // std::strcpy #include #include @@ -24,6 +25,63 @@ using namespace semantics; int plugin_is_GPL_compatible; auto_ptr options_; +// A prefix of the _cpp_file struct. This struct is not part of the +// public interface so we have to resort to this technique (based on +// libcpp/files.c). +// +struct cpp_file_prefix +{ + char const* name; + char const* path; + char const* pchname; + char const* dir_name; +}; + +extern "C" void +start_unit_callback (void*, void*) +{ + // Set the directory of the main file (stdin) to that of the orginal + // file. + // + cpp_buffer* b (cpp_get_buffer (parse_in)); + _cpp_file* f (cpp_get_file (b)); + char const* p (cpp_get_path (f)); + cpp_file_prefix* fp (reinterpret_cast (f)); + + // Perform sanity checks. + // + if (p != 0 && *p == '\0' // The path should be empty (stdin). + && cpp_get_prev (b) == 0 // This is the only buffer (main file). + && fp->path == p // Our prefix corresponds to the actual type. + && fp->dir_name == 0) // The directory part hasn't been initialized. + { + path p (options_->svc_file ()); + path d (p.directory ()); + char* s; + + if (d.empty ()) + { + s = XNEWVEC (char, 1); + *s = '\0'; + } + else + { + size_t n (d.string ().size ()); + s = XNEWVEC (char, n + 2); + strcpy (s, d.string ().c_str ()); + s[n] = path::traits::directory_separator; + s[n + 1] = '\0'; + } + + fp->dir_name = s; + } + else + { + cerr << "ice: unable to initialize main file directory" << endl; + exit (1); + } +} + extern "C" void gate_callback (void*, void*) { @@ -37,8 +95,31 @@ gate_callback (void*, void*) try { + // Find the actual main file name that was specified with the + // #line directive. + // + path file; + for (size_t i (0); i < line_table->used; ++i) + { + line_map const* m (line_table->maps + i); + + if (MAIN_FILE_P (m) && m->reason == LC_RENAME) + { + string f (m->to_file); + + if (f != "" && + f != "" && + f != "") + { + file = path (f); + break; + } + } + } + + // + // parser p (*options_, loc_pragmas_, decl_pragmas_); - path file (main_input_filename); auto_ptr u (p.parse (global_namespace, file)); // @@ -73,6 +154,8 @@ gate_callback (void*, void*) static char const* const odb_version = ODB_COMPILER_VERSION_STR; +typedef vector strings; + extern "C" int plugin_init (plugin_name_args* plugin_info, plugin_gcc_version*) { @@ -84,7 +167,7 @@ plugin_init (plugin_name_args* plugin_info, plugin_gcc_version*) // Parse options. // { - vector argv_str; + strings argv_str; vector argv; argv_str.push_back (plugin_info->base_name); @@ -129,6 +212,11 @@ plugin_init (plugin_name_args* plugin_info, plugin_gcc_version*) 0); register_callback (plugin_info->base_name, + PLUGIN_START_UNIT, + start_unit_callback, + 0); + + register_callback (plugin_info->base_name, PLUGIN_OVERRIDE_GATE, &gate_callback, 0); -- cgit v1.1