int work_it( CompileJob &j, unsigned int job_stat[], MsgChannel* client, CompileResultMsg& rmsg, const string &outfilename, unsigned long int mem_limit, int client_fd, int /*job_in_fd*/ ) { rmsg.out.erase(rmsg.out.begin(), rmsg.out.end()); rmsg.out.erase(rmsg.out.begin(), rmsg.out.end()); std::list<string> list = j.remoteFlags(); appendList( list, j.restFlags() ); int sock_err[2]; int sock_out[2]; int sock_in[2]; int main_sock[2]; char buffer[4096]; if ( pipe( sock_err ) ) return EXIT_DISTCC_FAILED; if ( pipe( sock_out ) ) return EXIT_DISTCC_FAILED; if ( pipe( main_sock ) ) return EXIT_DISTCC_FAILED; if ( pipe( death_pipe ) ) return EXIT_DISTCC_FAILED; // We use a socket pair instead of a pipe to get a "slightly" bigger // output buffer. This saves context switches and latencies. if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_in) < 0) return EXIT_DISTCC_FAILED; int maxsize = 2*1024*2024; #ifdef SO_SNDBUFFORCE if (setsockopt(sock_in[1], SOL_SOCKET, SO_SNDBUFFORCE, &maxsize, sizeof(maxsize)) < 0) #endif { setsockopt(sock_in[1], SOL_SOCKET, SO_SNDBUF, &maxsize, sizeof(maxsize)); } if ( fcntl( sock_in[1], F_SETFL, O_NONBLOCK ) ) return EXIT_DISTCC_FAILED; /* Testing */ struct sigaction act; sigemptyset( &act.sa_mask ); act.sa_handler = SIG_IGN; act.sa_flags = 0; sigaction( SIGPIPE, &act, 0L ); act.sa_handler = theSigCHLDHandler; act.sa_flags = SA_NOCLDSTOP; sigaction( SIGCHLD, &act, 0 ); sigaddset( &act.sa_mask, SIGCHLD ); // Make sure we don't block this signal. gdb tends to do that :-( sigprocmask( SIG_UNBLOCK, &act.sa_mask, 0 ); flush_debug(); pid_t pid = fork(); if ( pid == -1 ) { return EXIT_OUT_OF_MEMORY; } else if ( pid == 0 ) { setenv( "PATH", "usr/bin", 1 ); // Safety check if (getuid() == 0 || getgid() == 0) { error_client( client, "UID is 0 - aborting." ); _exit(142); } #ifdef RLIMIT_AS struct rlimit rlim; if ( getrlimit( RLIMIT_AS, &rlim ) ) { error_client( client, "getrlimit failed." ); log_perror( "getrlimit" ); } rlim.rlim_cur = mem_limit*1024*1024; rlim.rlim_max = mem_limit*1024*1024; if ( setrlimit( RLIMIT_AS, &rlim ) ) { error_client( client, "setrlimit failed." ); log_perror( "setrlimit" ); } #endif int argc = list.size(); argc++; // the program argc += 6; // -x c - -o file.o -fpreprocessed argc += 4; // gpc parameters argc += 1; // -pipe char **argv = new char*[argc + 1]; int i = 0; if (j.language() == CompileJob::Lang_C) argv[i++] = strdup( "usr/bin/gcc" ); else if (j.language() == CompileJob::Lang_CXX) argv[i++] = strdup( "usr/bin/g++" ); else assert(0); bool hasPipe = false; for ( std::list<string>::const_iterator it = list.begin(); it != list.end(); ++it) { if(*it == "-pipe") hasPipe = true; argv[i++] = strdup( it->c_str() ); } argv[i++] = strdup("-fpreprocessed"); if(!hasPipe) argv[i++] = strdup("-pipe"); argv[i++] = strdup("-x"); argv[i++] = strdup((j.language() == CompileJob::Lang_CXX) ? "c++" : "c"); argv[i++] = strdup( "-" ); argv[i++] = strdup( "-o" ); argv[i++] = strdup(outfilename.c_str()); argv[i++] = strdup( "--param" ); sprintf( buffer, "ggc-min-expand=%d", ggc_min_expand_heuristic( mem_limit ) ); argv[i++] = strdup( buffer ); argv[i++] = strdup( "--param" ); sprintf( buffer, "ggc-min-heapsize=%d", ggc_min_heapsize_heuristic( mem_limit ) ); argv[i++] = strdup( buffer ); // before you add new args, check above for argc argv[i] = 0; assert(i <= argc); close_debug(); close( sock_out[0] ); dup2 (sock_out[1], STDOUT_FILENO ); close(sock_out[1]); close(sock_err[0]); dup2( sock_err[1], STDERR_FILENO ); close(sock_err[1]); close( sock_in[1] ); dup2( sock_in[0], STDIN_FILENO); close (sock_in[0]); close( main_sock[0] ); fcntl(main_sock[1], F_SETFD, FD_CLOEXEC); close( death_pipe[0] ); close( death_pipe[1] ); #ifdef ICECC_DEBUG for(int f = STDERR_FILENO+1; f < 4096; ++f) { long flags; assert((flags = fcntl(f, F_GETFD, 0)) < 0 || (flags & FD_CLOEXEC)); } #endif execv( argv[0], const_cast<char *const*>( argv ) ); // no return perror( "ICECC: execv" ); char resultByte = 1; write(main_sock[1], &resultByte, 1); _exit(-1); } close( sock_in[0] ); close( sock_out[1] ); close( sock_err[1] ); // idea borrowed from kprocess. // check whether the compiler could be run at all. close( main_sock[1] ); for(;;) { char resultByte; ssize_t n = ::read(main_sock[0], &resultByte, 1); if (n == -1 && errno == EINTR) continue; // Ignore if (n == 1) { rmsg.status = resultByte; error_client( client, "compiler did not start" ); return EXIT_COMPILER_MISSING; } break; // != EINTR } close( main_sock[0] ); struct timeval starttv; gettimeofday(&starttv, 0 ); int return_value = 0; // Got EOF for preprocessed input. stdout send may be still pending. bool input_complete = false; // Pending data to send to stdin FileChunkMsg *fcmsg = 0; size_t off = 0; log_block parent_wait("parent, waiting"); for(;;) { if ( client_fd >= 0 && !fcmsg ) { if (Msg *msg = client->get_msg(0)) { if (input_complete) { rmsg.err.append( "client cancelled\n" ); return_value = EXIT_CLIENT_KILLED; client_fd = -1; kill(pid, SIGTERM); delete fcmsg; fcmsg = 0; delete msg; } else { if ( msg->type == M_END ) { input_complete = true; if (!fcmsg) { close( sock_in[1] ); sock_in[1] = -1; } delete msg; } else if ( msg->type == M_FILE_CHUNK ) { fcmsg = static_cast<FileChunkMsg*>( msg ); off = 0; job_stat[JobStatistics::in_uncompressed] += fcmsg->len; job_stat[JobStatistics::in_compressed] += fcmsg->compressed; } else { log_error() << "protocol error while reading preprocessed file\n"; return_value = EXIT_IO_ERROR; client_fd = -1; kill(pid, SIGTERM); delete fcmsg; fcmsg = 0; delete msg; } } } else if (client->at_eof()) { log_error() << "unexpected EOF while reading preprocessed file\n"; return_value = EXIT_IO_ERROR; client_fd = -1; kill(pid, SIGTERM); delete fcmsg; fcmsg = 0; } } fd_set rfds; FD_ZERO( &rfds ); if (sock_out[0] >= 0) FD_SET( sock_out[0], &rfds ); if (sock_err[0] >= 0) FD_SET( sock_err[0], &rfds ); int max_fd = std::max( sock_out[0], sock_err[0] ); if ( client_fd >= 0 && !fcmsg ) { FD_SET( client_fd, &rfds ); if ( client_fd > max_fd ) max_fd = client_fd; // Note that we don't actually query the status of this fd - // we poll it in every iteration. } FD_SET( death_pipe[0], &rfds ); if ( death_pipe[0] > max_fd ) max_fd = death_pipe[0]; fd_set wfds, *wfdsp = 0; FD_ZERO( &wfds ); if (fcmsg) { FD_SET( sock_in[1], &wfds ); wfdsp = &wfds; if ( sock_in[1] > max_fd ) max_fd = sock_in[1]; } struct timeval tv, *tvp = 0; if (!input_complete) { tv.tv_sec = 60; tv.tv_usec = 0; tvp = &tv; } switch( select( max_fd+1, &rfds, wfdsp, 0, tvp ) ) { case 0: if (!input_complete) { log_error() << "timeout while reading preprocessed file\n"; kill(pid, SIGTERM); // Won't need it any more ... return_value = EXIT_IO_ERROR; client_fd = -1; input_complete = true; delete fcmsg; fcmsg = 0; continue; } // this should never happen assert( false ); return EXIT_DISTCC_FAILED; case -1: if (errno == EINTR) continue; // this should never happen assert( false ); return EXIT_DISTCC_FAILED; default: if ( fcmsg && FD_ISSET(sock_in[1], &wfds) ) { ssize_t bytes = write( sock_in[1], fcmsg->buffer + off, fcmsg->len - off ); if ( bytes < 0 ) { if (errno == EINTR) continue; kill(pid, SIGTERM); // Most likely crashed anyway ... return_value = EXIT_COMPILER_CRASHED; continue; } // The fd is -1 anyway //write(job_in_fd, fcmsg->buffer + off, bytes); off += bytes; if (off == fcmsg->len) { delete fcmsg; fcmsg = 0; if (input_complete) { close( sock_in[1] ); sock_in[1] = -1; } } } if ( sock_out[0] >= 0 && FD_ISSET(sock_out[0], &rfds) ) { ssize_t bytes = read( sock_out[0], buffer, sizeof(buffer)-1 ); if ( bytes > 0 ) { buffer[bytes] = 0; rmsg.out.append( buffer ); } else if (bytes == 0) { close(sock_out[0]); sock_out[0] = -1; } } if ( sock_err[0] >= 0 && FD_ISSET(sock_err[0], &rfds) ) { ssize_t bytes = read( sock_err[0], buffer, sizeof(buffer)-1 ); if ( bytes > 0 ) { buffer[bytes] = 0; rmsg.err.append( buffer ); } else if (bytes == 0) { close(sock_err[0]); sock_err[0] = -1; } } if ( FD_ISSET(death_pipe[0], &rfds) ) { // Note that we have already read any remaining stdout/stderr: // the sigpipe is delivered after everything was written, // and the notification is multiplexed into the select above. struct rusage ru; int status; if (wait4(pid, &status, 0, &ru) != pid) { // this should never happen assert( false ); return EXIT_DISTCC_FAILED; } if ( !WIFEXITED(status) || WEXITSTATUS(status) ) { unsigned long int mem_used = ( ru.ru_minflt + ru.ru_majflt ) * getpagesize() / 1024; rmsg.status = EXIT_OUT_OF_MEMORY; if ( mem_used * 100 > 85 * mem_limit * 1024 || rmsg.err.find( "memory exhausted" ) != string::npos ) { // the relation between ulimit and memory used is pretty thin ;( return EXIT_OUT_OF_MEMORY; } } if ( WIFEXITED(status) ) { struct timeval endtv; gettimeofday(&endtv, 0 ); rmsg.status = WEXITSTATUS(status); job_stat[JobStatistics::exit_code] = WEXITSTATUS(status); job_stat[JobStatistics::real_msec] = (endtv.tv_sec - starttv.tv_sec) * 1000 + (long(endtv.tv_usec) - long(starttv.tv_usec)) / 1000; job_stat[JobStatistics::user_msec] = ru.ru_utime.tv_sec * 1000 + ru.ru_utime.tv_usec / 1000; job_stat[JobStatistics::sys_msec] = ru.ru_stime.tv_sec * 1000 + ru.ru_stime.tv_usec / 1000; job_stat[JobStatistics::sys_pfaults] = ru.ru_majflt + ru.ru_nswap + ru.ru_minflt; } return return_value; } } } }
int build_remote(CompileJob &job, MsgChannel *local_daemon, const Environments &_envs, int permill ) { srand( time( 0 ) + getpid() ); int torepeat = 1; // older compilers do not support the options we need to make it reproducible #if defined(__GNUC__) && ( ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 3) ) || (__GNUC__ >=4) ) if (!compiler_is_clang(job)) { if ( rand() % 1000 < permill) torepeat = 3; } #endif trace() << job.inputFile() << " compiled " << torepeat << " times on " << job.targetPlatform() << "\n"; map<string, string> versionfile_map, version_map; Environments envs = rip_out_paths( _envs, version_map, versionfile_map ); if (!envs.size()) { log_error() << "$ICECC_VERSION needs to point to .tar files\n"; throw(22); } const char *preferred_host = getenv("ICECC_PREFERRED_HOST"); if ( torepeat == 1 ) { string fake_filename; list<string> args = job.remoteFlags(); for ( list<string>::const_iterator it = args.begin(); it != args.end(); ++it ) fake_filename += "/" + *it; args = job.restFlags(); for ( list<string>::const_iterator it = args.begin(); it != args.end(); ++it ) fake_filename += "/" + *it; fake_filename += get_absfilename( job.inputFile() ); GetCSMsg getcs (envs, fake_filename, job.language(), torepeat, job.targetPlatform(), job.argumentFlags(), preferred_host ? preferred_host : string(), ignore_unverified()); if (!local_daemon->send_msg (getcs)) { log_warning() << "asked for CS\n"; throw( 24 ); } UseCSMsg *usecs = get_server( local_daemon ); int ret; if (!maybe_build_local (local_daemon, usecs, job, ret)) ret = build_remote_int( job, usecs, local_daemon, version_map[usecs->host_platform], versionfile_map[usecs->host_platform], 0, true ); delete usecs; return ret; } else { char *preproc = 0; dcc_make_tmpnam( "icecc", ".ix", &preproc, 0 ); const CharBufferDeleter preproc_holder(preproc); int cpp_fd = open(preproc, O_WRONLY ); /* When call_cpp returns normally (for the parent) it will have closed the write fd, i.e. cpp_fd. */ pid_t cpp_pid = call_cpp(job, cpp_fd ); if ( cpp_pid == -1 ) { ::unlink( preproc ); throw( 10 ); } int status = 255; waitpid( cpp_pid, &status, 0); if ( shell_exit_status(status) ) { // failure ::unlink( preproc ); return shell_exit_status( status ); } char rand_seed[400]; // "designed to be oversized" (Levi's) sprintf( rand_seed, "-frandom-seed=%d", rand() ); job.appendFlag( rand_seed, Arg_Remote ); GetCSMsg getcs (envs, get_absfilename( job.inputFile() ), job.language(), torepeat, job.targetPlatform(), job.argumentFlags(), preferred_host ? preferred_host : string(), ignore_unverified()); if (!local_daemon->send_msg (getcs)) { log_warning() << "asked for CS\n"; throw( 0 ); } map<pid_t, int> jobmap; CompileJob *jobs = new CompileJob[torepeat]; UseCSMsg **umsgs = new UseCSMsg*[torepeat]; bool misc_error = false; int *exit_codes = new int[torepeat]; for ( int i = 0; i < torepeat; i++ ) // init exit_codes[i] = 42; for ( int i = 0; i < torepeat; i++ ) { jobs[i] = job; char *buffer = 0; if ( i ) { dcc_make_tmpnam( "icecc", ".o", &buffer, 0 ); jobs[i].setOutputFile( buffer ); } else buffer = strdup( job.outputFile().c_str() ); const CharBufferDeleter buffer_holder( buffer ); umsgs[i] = get_server( local_daemon ); remote_daemon = umsgs[i]->hostname; trace() << "got_server_for_job " << umsgs[i]->hostname << endl; flush_debug(); pid_t pid = fork(); if ( !pid ) { int ret = 42; try { if (!maybe_build_local (local_daemon, umsgs[i], jobs[i], ret)) ret = build_remote_int( jobs[i], umsgs[i], local_daemon, version_map[umsgs[i]->host_platform], versionfile_map[umsgs[i]->host_platform], preproc, i == 0 ); } catch ( int error ) { log_info() << "build_remote_int failed and has thrown " << error << endl; kill( getpid(), SIGTERM ); return 0; // shouldn't matter } _exit( ret ); return 0; // doesn't matter } else { jobmap[pid] = i; } } for ( int i = 0; i < torepeat; i++ ) { pid_t pid = wait( &status ); if ( pid < 0 ) { log_perror( "wait failed" ); status = -1; } else { if ( WIFSIGNALED( status ) ) { // there was some misc error in processing misc_error = true; break; } exit_codes[jobmap[pid]] = shell_exit_status( status ); } } if (! misc_error ) { string first_md5 = md5_for_file( jobs[0].outputFile() ); for ( int i = 1; i < torepeat; i++ ) { if ( !exit_codes[0] ) { // if the first failed, we fail anyway if ( exit_codes[i] == 42 ) // they are free to fail for misc reasons continue; if ( exit_codes[i] ) { log_error() << umsgs[i]->hostname << " compiled with exit code " << exit_codes[i] << " and " << umsgs[0]->hostname << " compiled with exit code " << exit_codes[0] << " - aborting!\n"; ::unlink( jobs[0].outputFile().c_str()); exit_codes[0] = -1; // overwrite break; } string other_md5 = md5_for_file( jobs[i].outputFile() ); if ( other_md5 != first_md5 ) { log_error() << umsgs[i]->hostname << " compiled " << jobs[0].outputFile() << " with md5 sum " << other_md5 << "(" << jobs[i].outputFile() << ")" << " and " << umsgs[0]->hostname << " compiled with md5 sum " << first_md5 << " - aborting!\n"; rename( jobs[0].outputFile().c_str(), ( jobs[0].outputFile() + ".caught" ).c_str() ); rename( preproc, ( string( preproc ) + ".caught" ).c_str() ); exit_codes[0] = -1; // overwrite break; } } ::unlink( jobs[i].outputFile().c_str() ); delete umsgs[i]; } } else { ::unlink( jobs[0].outputFile().c_str() ); for ( int i = 1; i < torepeat; i++ ) { ::unlink( jobs[i].outputFile().c_str()); delete umsgs[i]; } } delete umsgs[0]; ::unlink( preproc ); int ret = exit_codes[0]; delete [] umsgs; delete [] jobs; delete [] exit_codes; if ( misc_error ) throw ( 27 ); return ret; } return 0; }
bool analyse_argv( const char * const *argv, CompileJob &job, bool icerun, list<string> *extrafiles ) { 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; bool fno_color_diagnostics = false; // if rewriting includes and precompiling on remote machine, then cpp args are not local Argument_Type Arg_Cpp = compiler_only_rewrite_includes( job ) ? Arg_Rest : Arg_Local; 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; } /* Some weird build systems pass directly additional assembler files. * Example: -Wa,src/code16gcc.s * Need to handle it locally then. Search if the first part after -Wa, does not start with - */ pos = a+3; while (*pos) { if (*pos == ',' || *pos == ' ') { pos++; continue; } if (*pos == '-') break; local = true; 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]; string::size_type dot_index = p.find_last_of('.'); if (dot_index != string::npos) { string ext = p.substr(dot_index + 1); if (ext[0] != 'h' && ext[0] != 'H' && access(p.c_str(), R_OK) && access((p + ".gch").c_str(), R_OK)) always_local = true; } else always_local = true; /* Included file is not header.suffix or header.suffix.gch! */ args.append(a, Arg_Local); args.append(argv[i], Arg_Local); } } else if (str_equal("-D", a) || str_equal("-U", a) ) { args.append(a, Arg_Cpp); /* skip next word, being option argument */ if (argv[i+1]) { ++i; args.append( argv[i], Arg_Cpp ); } } else if (str_equal("-I", 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)) { args.append(a, Arg_Cpp); } else if (str_startswith("-I", a) || str_startswith("-l", a) || str_startswith("-L", a)) { args.append(a, Arg_Local); } else if (str_equal("-undef", a)) { args.append(a, Arg_Cpp); } else if (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 if (str_equal("-fno-color-diagnostics", a)) { fno_color_diagnostics = true; args.append( a, Arg_Rest ); } else if (str_equal("-fcolor-diagnostics", a)) { fno_color_diagnostics = false; args.append( a, Arg_Rest ); } else if ( str_equal( "-flto", a ) ) { // pointless when preprocessing, and Clang would emit a warning args.append( a, Arg_Remote ); } else if (str_startswith("-fplugin=", a)) { string file = a+strlen("-fplugin="); if (access(file.c_str(), R_OK) == 0) { file = get_absfilename(file); extrafiles->push_back(file); } else { always_local = true; } args.append("-fplugin="+file, Arg_Rest); } else if (str_equal("-Xclang", a)) { string a = argv[i]; if (argv[i+1]) { ++i; const char *p = argv[i]; if (str_equal("-load", p)) { if (argv[i+1] && argv[i+2] && str_equal(argv[i+1], "-Xclang")) { args.append(a, Arg_Rest); args.append(p, Arg_Rest); string file = argv[i+2]; if (access(file.c_str(), R_OK) == 0) { file = get_absfilename(file); extrafiles->push_back(file); } else { always_local = true; } args.append(argv[i+1], Arg_Rest); args.append(file, Arg_Rest); i += 2; } } else { args.append(a, Arg_Rest); args.append(p, Arg_Rest); } } } else args.append( a, Arg_Rest ); } else if (a[0] == '@') { args.append(a, Arg_Local); } 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->first == "-Xclang" ) { ++it; ++it; } else if ( it->second != Arg_Rest || it->first.at( 0 ) == '-' || 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; // redirecting Clang's output will turn off its automatic coloring, so force it, unless disabled if (compiler_is_clang(job) && colorify_possible() && !fno_color_diagnostics) args.append("-fcolor-diagnostics", Arg_Rest); 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; }