summaryrefslogtreecommitdiff
path: root/client/arg.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'client/arg.cpp')
-rw-r--r--client/arg.cpp419
1 files changed, 419 insertions, 0 deletions
diff --git a/client/arg.cpp b/client/arg.cpp
new file mode 100644
index 0000000..db16ee9
--- /dev/null
+++ b/client/arg.cpp
@@ -0,0 +1,419 @@
+/* -*- c-file-style: "java"; indent-tabs-mode: nil; fill-column: 78 -*-
+ *
+ * distcc -- A simple distributed compiler system
+ *
+ * Copyright (C) 2002, 2003 by Martin Pool <mbp@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "client.h"
+
+using namespace std;
+
+#define CLIENT_DEBUG 0
+
+#if CLIENT_DEBUG
+static string concat_args( const list<string> &args )
+{
+ size_t len = args.size() - 1;
+ string result = "\"";
+ for ( list<string>::const_iterator it = args.begin();
+ it != args.end(); ++it, len-- ) {
+ result += *it;
+ if ( len )
+ result += ", ";
+ }
+ result += "\"";
+ return result;
+}
+#endif
+
+#define str_equal(a, b) (!strcmp((a), (b)))
+
+inline int str_startswith(const char *head, const char *worm)
+{
+ return !strncmp(head, worm, strlen(head));
+}
+
+static bool analyze_program(const char* name, CompileJob& job)
+{
+ string compiler_name = find_basename( name );
+
+ string::size_type pos = compiler_name.rfind('/');
+ if (pos != string::npos)
+ compiler_name = compiler_name.substr(pos);
+
+ job.setCompilerName( compiler_name );
+
+ string suffix = compiler_name;
+ if ( compiler_name.size() > 2)
+ suffix = compiler_name.substr(compiler_name.size()-2);
+
+ if (suffix == "++" || suffix == "CC")
+ job.setLanguage (CompileJob::Lang_CXX);
+ else if (suffix == "cc")
+ job.setLanguage (CompileJob::Lang_C);
+ else {
+ job.setLanguage( CompileJob::Lang_Custom );
+ job.setCompilerName( name ); // keep path
+ return true;
+ }
+
+ return false;
+}
+
+bool analyse_argv( const char * const *argv,
+ CompileJob &job, bool icerun )
+{
+ ArgumentsList args;
+ string ofile;
+
+#if CLIENT_DEBUG > 1
+ trace() << "scanning arguments ";
+ for ( int index = 0; argv[index]; index++ )
+ trace() << argv[index] << " ";
+ trace() << endl;
+#endif
+
+ bool had_cc = (job.compilerName().size() > 0);
+ bool always_local = analyze_program(had_cc ? job.compilerName().c_str() : argv[0], job);
+ bool seen_c = false;
+ bool seen_s = false;
+ bool seen_mf = false;
+ bool seen_md = false;
+ if( icerun ) {
+ always_local = true;
+ job.setLanguage( CompileJob::Lang_Custom );
+ }
+
+ for (int i = had_cc ? 2 : 1; argv[i]; i++) {
+ const char *a = argv[i];
+
+ if (icerun) {
+ args.append(a, Arg_Local);
+ } else if (a[0] == '-') {
+ if (!strcmp(a, "-E") || !strncmp(a, "-fdump", 6) || !strcmp(a, "-combine")) {
+ always_local = true;
+ args.append(a, Arg_Local);
+ } else if (!strcmp(a, "-MD") || !strcmp(a, "-MMD")) {
+ seen_md = true;
+ args.append(a, Arg_Local);
+ /* These two generate dependencies as a side effect. They
+ * should work with the way we call cpp. */
+ } else if (!strcmp(a, "-MG") || !strcmp(a, "-MP")) {
+ args.append(a, Arg_Local);
+ /* These just modify the behaviour of other -M* options and do
+ * nothing by themselves. */
+ } else if (!strcmp(a, "-MF")) {
+ seen_mf = true;
+ args.append(a, Arg_Local);
+ args.append( argv[++i], Arg_Local );
+ /* as above but with extra argument */
+ } else if (!strcmp(a, "-MT") || !strcmp(a, "-MQ")) {
+ args.append(a, Arg_Local);
+ args.append( argv[++i], Arg_Local );
+ /* as above but with extra argument */
+ } else if (a[1] == 'M') {
+ /* -M(anything else) causes the preprocessor to
+ produce a list of make-style dependencies on
+ header files, either to stdout or to a local file.
+ It implies -E, so only the preprocessor is run,
+ not the compiler. There would be no point trying
+ to distribute it even if we could. */
+ always_local = true;
+ args.append(a, Arg_Local);
+ } else if ( str_equal( "--param", a ) ) {
+ args.append( a, Arg_Remote );
+ /* skip next word, being option argument */
+ if (argv[i+1])
+ args.append( argv[++i], Arg_Remote );
+ } else if ( a[1] == 'B' ) {
+ /* -B overwrites the path where the compiler finds the assembler.
+ As we don't use that, better force local job.
+ */
+ always_local = true;
+ args.append( a, Arg_Local );
+ if ( str_equal( a, "-B" ) ) {
+ /* skip next word, being option argument */
+ if (argv[i+1])
+ args.append( argv[++i], Arg_Local );
+ }
+ } else if (str_startswith("-Wa,", a)) {
+ /* Options passed through to the assembler. The only one we
+ * need to handle so far is -al=output, which directs the
+ * listing to the named file and cannot be remote. There are
+ * some other options which also refer to local files,
+ * but most of them make no sense when called via the compiler,
+ * hence we only look for -a[a-z]*= and localize the job if we
+ * find it. */
+ const char *pos = a;
+ bool local = false;
+ while ((pos = strstr(pos+1, "-a"))) {
+ pos += 2;
+ while (*pos >= 'a' && *pos <= 'z')
+ pos++;
+ if (*pos == '=') {
+ local = true;
+ break;
+ }
+ if (!*pos)
+ break;
+ }
+ if (local) {
+ always_local = true;
+ args.append(a, Arg_Local);
+ } else
+ args.append(a, Arg_Remote);
+ } else if (!strcmp(a, "-S")) {
+ seen_s = true;
+ } else if (!strcmp(a, "-fprofile-arcs")
+ || !strcmp(a, "-ftest-coverage")
+ || !strcmp( a, "-frepo" )
+ || !strcmp( a, "-fprofile-generate" )
+ || !strcmp( a, "-fprofile-use" )
+ || !strcmp( a, "-save-temps")
+ || !strcmp( a, "-fbranch-probabilities") ) {
+#if CLIENT_DEBUG
+ log_info() << "compiler will emit profile info; must be local" << endl;
+#endif
+ always_local = true;
+ args.append(a, Arg_Local);
+ } else if (!strcmp(a, "-x")) {
+#if CLIENT_DEBUG
+ log_info() << "gcc's -x handling is complex; running locally" << endl;
+#endif
+ always_local = true;
+ args.append(a, Arg_Local);
+ } else if (!strcmp(a, "-march=native") || !strcmp(a, "-mcpu=native") || !strcmp(a, "-mtune=native")) {
+#if CLIENT_DEBUG
+ log_info() << "-{march,mpcu,mtune}=native optimizes for local machine; must be local" << endl;
+#endif
+ always_local = true;
+ args.append(a, Arg_Local);
+ } else if (!strcmp(a, "-c")) {
+ seen_c = true;
+ } else if (str_startswith("-o", a)) {
+ if (!strcmp(a, "-o")) {
+ /* Whatever follows must be the output */
+ if ( argv[i+1] )
+ ofile = argv[++i];
+ } else {
+ a += 2;
+ ofile = a;
+ }
+ if (ofile == "-" ) {
+ /* Different compilers may treat "-o -" as either "write to
+ * stdout", or "write to a file called '-'". We can't know,
+ * so we just always run it locally. Hopefully this is a
+ * pretty rare case. */
+#if CLIENT_DEBUG
+ log_info() << "output to stdout? running locally" << endl;
+#endif
+ always_local = true;
+ }
+ } else if (str_equal("-include", a)) {
+ /* This has a duplicate meaning. it can either include a file
+ for preprocessing or a precompiled header. decide which one. */
+ if (argv[i+1]) {
+ ++i;
+ std::string p = argv[i];
+ if (access(p.c_str(), R_OK) && access((p + ".gch").c_str(), R_OK))
+ always_local = true; /* can't decide which one, let the compiler figure it
+ out. */
+ args.append(a, Arg_Local);
+ args.append(argv[i], Arg_Local);
+ }
+ } else if (str_equal("-D", a)
+ || str_equal("-I", a)
+ || str_equal("-U", a)
+ || str_equal("-L", a)
+ || str_equal("-l", a)
+ || str_equal("-MF", a)
+ || str_equal("-MT", a)
+ || str_equal("-MQ", a)
+ || str_equal("-imacros", a)
+ || str_equal("-iprefix", a)
+ || str_equal("-iwithprefix", a)
+ || str_equal("-isystem", a)
+ || str_equal("-iquote", a)
+ || str_equal("-imultilib", a)
+ || str_equal("-isysroot", a)
+ || str_equal("-include", a)
+ || str_equal("-iwithprefixbefore", a)
+ || str_equal("-idirafter", a) ) {
+ args.append(a, Arg_Local);
+ /* skip next word, being option argument */
+ if (argv[i+1]) {
+ ++i;
+ if (str_startswith("-O", argv[i]))
+ always_local = true;
+ args.append( argv[i], Arg_Local );
+ }
+ } else if (str_startswith("-Wp,", a)
+ || str_startswith("-D", a)
+ || str_startswith("-U", a)
+ || str_startswith("-I", a)
+ || str_startswith("-l", a)
+ || str_startswith("-L", a)) {
+ args.append(a, Arg_Local);
+ } else if (str_equal("-undef", a)
+ || str_equal("-nostdinc", a)
+ || str_equal("-nostdinc++", a)
+ || str_equal("-MD", a)
+ || str_equal("-MMD", a)
+ || str_equal("-MG", a)
+ || str_equal("-MP", a)) {
+ args.append(a, Arg_Local);
+ } else
+ args.append( a, Arg_Rest );
+ } else {
+ args.append( a, Arg_Rest );
+ }
+ }
+
+ if (!seen_c && !seen_s)
+ always_local = true;
+ else if ( seen_s ) {
+ if ( seen_c )
+ log_info() << "can't have both -c and -S, ignoring -c" << endl;
+ args.append( "-S", Arg_Remote );
+ } else {
+ args.append( "-c", Arg_Remote );
+ }
+
+ if ( !always_local ) {
+
+ ArgumentsList backup = args;
+
+ /* TODO: ccache has the heuristic of ignoring arguments that are not
+ * extant files when looking for the input file; that's possibly
+ * worthwile. Of course we can't do that on the server. */
+ string ifile;
+ for ( ArgumentsList::iterator it = args.begin();
+ it != args.end(); ) {
+ if ( it->first == "-") {
+ always_local = true;
+ break;
+ }
+ if ( it->second != Arg_Rest || it->first.at( 0 ) == '-' )
+ ++it;
+ else if ( ifile.empty() ) {
+#if CLIENT_DEBUG
+ log_info() << "input file: " << it->first << endl;
+#endif
+ job.setInputFile( it->first );
+ ifile = it->first;
+ it = args.erase( it );
+ } else {
+ log_info() << "found another non option on command line. Two input files? " << it->first << endl;
+ always_local = true;
+ args = backup;
+ job.setInputFile( string() );
+ break;
+ }
+ }
+
+ if ( ifile.find( '.' ) != string::npos ) {
+ string::size_type dot_index = ifile.find_last_of( '.' );
+ string ext = ifile.substr( dot_index + 1 );
+
+ if (ext == "cc"
+ || ext == "cpp" || ext == "cxx"
+ || ext == "cp" || ext == "c++"
+ || ext == "C" || ext == "ii") {
+#if CLIENT_DEBUG
+ if ( job.language() != CompileJob::Lang_CXX )
+ log_info() << "switching to C++ for " << ifile << endl;
+#endif
+ job.setLanguage( CompileJob::Lang_CXX );
+ } else if(ext == "mi" || ext == "m"
+ || ext == "mii" || ext == "mm"
+ || ext == "M" ) {
+ job.setLanguage( CompileJob::Lang_OBJC );
+ } else if ( ext == "s" || ext == "S" || // assembler
+ ext == "ads" || ext == "adb" || // ada
+ ext == "f" || ext == "for" || // fortran
+ ext == "FOR" || ext == "F" ||
+ ext == "fpp" || ext == "FPP" ||
+ ext == "r" ) {
+ always_local = true;
+ } else if ( ext != "c" && ext != "i" ) { // C is special, it depends on arg[0] name
+ log_warning() << "unknown extension " << ext << endl;
+ always_local = true;
+ }
+
+ if ( !always_local && ofile.empty() ) {
+ ofile = ifile.substr( 0, dot_index );
+ if ( seen_s )
+ ofile += ".s";
+ else
+ ofile += ".o";
+ string::size_type slash = ofile.find_last_of( '/' );
+ if ( slash != string::npos )
+ ofile = ofile.substr( slash + 1 );
+ }
+
+ if ( !always_local && seen_md && !seen_mf) {
+ string dfile = ofile.substr( 0, ofile.find_last_of( '.' ) ) + ".d";
+
+#if CLIENT_DEBUG
+ log_info() << "dep file: " << dfile << endl;
+#endif
+
+ args.append("-MF", Arg_Local);
+ args.append(dfile, Arg_Local);
+ }
+ }
+
+ } else {
+ job.setInputFile( string() );
+ }
+
+ struct stat st;
+ if ( ofile.empty() || (!stat( ofile.c_str(), &st ) && !S_ISREG( st.st_mode )))
+ always_local = true;
+
+ job.setFlags( args );
+ job.setOutputFile( ofile );
+
+#if CLIENT_DEBUG
+ trace() << "scanned result: local args=" << concat_args( job.localFlags() )
+ << ", remote args=" << concat_args( job.remoteFlags() )
+ << ", rest=" << concat_args( job.restFlags() )
+ << ", local=" << always_local
+ << ", compiler=" << job.compilerName()
+ << ", lang="
+ << (job.language() != CompileJob::Lang_Custom ?
+ (job.language() == CompileJob::Lang_CXX ? "C++" : "C" ) : "<custom>")
+ << endl;
+#endif
+
+ return always_local;
+}