static void usage(FILE *f) { fprintf(f, "\nUsage: rdm [...options...]\n\n" " --help|-h Display this page.\n" " --version Display version.\n" "\nServer options:\n" " --clear-project-caches|-C Clear out project caches.\n" " --test|-t [arg] Run this test.\n" " --test-timeout|-z [arg] Timeout for test to complete.\n" " --completion-cache-size|-i [arg] Number of translation units to cache (default " STR(DEFAULT_COMPLETION_CACHE_SIZE) ").\n" " --completion-no-filter Don't filter private members and destructors from completions.\n" " --config|-c [arg] Use this file instead of ~/.rdmrc.\n" " --data-dir|-d [arg] Use this directory to store persistent data (default ~/.rtags).\n" " --daemon Run as daemon (detach from terminal).\n" " --disable-sighandler|-x Disable signal handler to dump stack for crashes.\n" " --disallow-multiple-sources|-m With this setting different sources will be merged for each source file.\n" " --enable-NDEBUG|-g Don't remove -DNDEBUG from compile lines.\n" " --enable-compiler-manager|-R Query compilers for their actual include paths instead of letting clang use its own.\n" " --exclude-filter|-X [arg] Files to exclude from rdm, default \"" EXCLUDEFILTER_DEFAULT "\".\n" " --extra-compilers|-U [arg] Override additional known compilers.\n" #ifdef FILEMANAGER_OPT_IN " --filemanager-watch|-M Use a file system watcher for filemanager.\n" #else " --no-filemanager-watch|-M Don't use a file system watcher for filemanager.\n" #endif " --no-filemanager Don't scan project directory for files. (rc -P won't work)\n" " --watch-sources-only Only watch source files (not dependencies).\n" " --job-count|-j [arg] Spawn this many concurrent processes for indexing (default %d).\n" " --header-error-job-count|-H [arg] Allow this many concurrent header error jobs (default std::max(1, --job-count / 2)).\n" " --log-file|-L [arg] Log to this file.\n" " --log-file-log-level [arg] Log level for log file (default is error):\n" " options are: error, warning, debug or verbose-debug.\n" #ifndef OS_FreeBSD #endif " --no-filesystem-watcher|-B Disable file system watching altogether. Reindexing has to happen manually.\n" " --no-file-lock Disable file locking. Not entirely safe but might improve performance on certain systems.\n" " --no-rc|-N Don't load any rc files.\n" " --no-startup-project|-o Don't restore the last current project on startup.\n" " --rp-connect-timeout|-O [arg] Timeout for connection from rp to rdm in ms (0 means no timeout) (default " STR(DEFAULT_RP_CONNECT_TIMEOUT) ").\n" " --rp-connect-attempts [arg] Number of times rp attempts to connect to rdm before giving up. (default " STR(DEFAULT_RP_CONNECT_ATTEMPTS) ").\n" " --rp-indexer-message-timeout|-T [arg] Timeout for rp indexer-message in ms (0 means no timeout) (default " STR(DEFAULT_RP_INDEXER_MESSAGE_TIMEOUT) ").\n" " --rp-nice-value|-a [arg] Nice value to use for rp (nice(2)) (default is no nicing).\n" " --rp-visit-file-timeout|-Z [arg] Timeout for rp visitfile commands in ms (0 means no timeout) (default " STR(DEFAULT_RP_VISITFILE_TIMEOUT) ").\n" " --separate-debug-and-release|-E Normally rdm doesn't consider release and debug as different builds. Pass this if you want it to.\n" " --setenv|-e [arg] Set this environment variable (--setenv \"foobar=1\").\n" " --silent|-S No logging to stdout.\n" " --socket-file|-n [arg] Use this file for the server socket (default ~/.rdm).\n" " --tcp-port [arg] Listen on this tcp socket (default none).\n" " --start-suspended|-Q Start out suspended (no reindexing enabled).\n" " --suspend-rp-on-crash|-q Suspend rp in SIGSEGV handler (default " DEFAULT_SUSPEND_RP ").\n" " --rp-log-to-syslog Make rp log to syslog\n" " --log-timestamp Add timestamp to logs\n" " --thread-stack-size|-k [arg] Set stack size for threadpool to this (default %zu).\n" " --verbose|-v Change verbosity, multiple -v's are allowed.\n" " --watch-system-paths|-w Watch system paths for changes.\n" " --block-argument|-G [arg] Block this argument from being passed to clang. E.g. rdm --block-argument -fno-inline\n" " --progress|-p Report compilation progress in diagnostics output.\n" #ifdef RTAGS_HAS_LAUNCHD " --launchd Run as a launchd job (use launchd API to retrieve socket opened by launchd on rdm's behalf).\n" #endif " --inactivity-timeout [arg] Time in seconds after which rdm will quit if there's been no activity (N.B., once rdm has quit, something will need to re-run it!).\n" "\nCompiling/Indexing options:\n" " --allow-Wpedantic|-P Don't strip out -Wpedantic. This can cause problems in certain projects.\n" " --define|-D [arg] Add additional define directive to clang.\n" " --ignore-printf-fixits|-F Disregard any clang fixit that looks like it's trying to fix format for printf and friends.\n" " --include-path|-I [arg] Add additional include path to clang.\n" " --isystem|-s [arg] Add additional system include path to clang.\n" " --Weverything|-u Use -Weverything.\n" " --no-Wall|-W Don't use -Wall.\n" " --no-no-unknown-warnings-option|-Y Don't pass -Wno-unknown-warning-option\n" " --no-spell-checking|-l Don't pass -fspell-checking.\n" " --no-unlimited-error|-f Don't pass -ferror-limit=0 to clang.\n" " --Wlarge-by-value-copy|-r [arg] Use -Wlarge-by-value-copy=[arg] when invoking clang.\n" " --max-file-map-cache-size|-y [arg] Max files to cache per query (Should not exceed maximum number of open file descriptors allowed per process) (default " STR(DEFAULT_RDM_MAX_FILE_MAP_CACHE_SIZE) ").\n" " --no-comments Don't parse/store doxygen comments.\n" " --arg-transform|-V [arg] Use arg to transform arguments. [arg] should be a executable with (execv(3)).\n" " --debug-locations [arg] Set debug locations.\n" " --validate-file-maps Spend some time validating project data on startup.\n" " --pch-enabled Enable PCH (experimental).\n" " --rp-path [path] Path to rp (default %s).\n" , std::max(2, ThreadPool::idealThreadCount()), defaultStackSize, defaultRP().constData()); }
int main(int argc, char** argv) { RemoveCrashDump removeCrashDump; #ifdef OS_Darwin struct rlimit rlp; if (getrlimit(RLIMIT_NOFILE, &rlp) == 0) { if (rlp.rlim_cur < 1000) { rlp.rlim_cur = 1000; setrlimit(RLIMIT_NOFILE, &rlp); } } #endif Rct::findExecutablePath(*argv); bool daemon = false; Server::Options serverOpts; const char * runtimeDir = getenv("XDG_RUNTIME_DIR"); if (runtimeDir == NULL) { serverOpts.socketFile = String::format<128>("%s.rdm", Path::home().constData()); } else { serverOpts.socketFile = String::format<1024>("%s/rdm.socket", runtimeDir); } serverOpts.jobCount = std::max(2, ThreadPool::idealThreadCount()); serverOpts.headerErrorJobCount = -1; serverOpts.rpVisitFileTimeout = DEFAULT_RP_VISITFILE_TIMEOUT; serverOpts.rpIndexDataMessageTimeout = DEFAULT_RP_INDEXER_MESSAGE_TIMEOUT; serverOpts.rpConnectTimeout = DEFAULT_RP_CONNECT_TIMEOUT; serverOpts.rpConnectAttempts = DEFAULT_RP_CONNECT_ATTEMPTS; serverOpts.maxFileMapScopeCacheSize = DEFAULT_RDM_MAX_FILE_MAP_CACHE_SIZE; serverOpts.errorLimit = DEFAULT_ERROR_LIMIT; serverOpts.rpNiceValue = INT_MIN; serverOpts.options = Server::Wall|Server::SpellChecking; serverOpts.maxCrashCount = DEFAULT_MAX_CRASH_COUNT; serverOpts.completionCacheSize = DEFAULT_COMPLETION_CACHE_SIZE; serverOpts.maxIncludeCompletionDepth = DEFAULT_MAX_INCLUDE_COMPLETION_DEPTH; serverOpts.rp = defaultRP(); strcpy(crashDumpFilePath, "crash.dump"); #ifdef FILEMANAGER_OPT_IN serverOpts.options |= Server::NoFileManagerWatch; #endif // #ifndef NDEBUG // serverOpts.options |= Server::SuspendRPOnCrash; // #endif serverOpts.dataDir = String::format<128>("%s.rtags", Path::home().constData()); if (!serverOpts.dataDir.exists()) { const char * dataDir = getenv("XDG_CACHE_HOME"); serverOpts.dataDir = dataDir ? dataDir : Path::home() + ".cache"; serverOpts.dataDir += "/rtags/"; serverOpts.dataDir.mkdir(Path::Recursive); } Path logFile; Flags<LogFlag> logFlags = DontRotate|LogStderr; LogLevel logLevel(LogLevel::Error); LogLevel logFileLogLevel(LogLevel::Error); bool sigHandler = true; assert(Path::home().endsWith('/')); int inactivityTimeout = 0; const std::initializer_list<CommandLineParser::Option<OptionType> > opts = { { None, 0, 0, CommandLineParser::NoValue, "Options:" }, { Help, "help", 'h', CommandLineParser::NoValue, "Display this page." }, { Version, "version", 0, CommandLineParser::NoValue, "Display version." }, { IncludePath, "include-path", 'I', CommandLineParser::Required, "Add additional include path to clang." }, { Isystem, "isystem", 's', CommandLineParser::Required, "Add additional system include path to clang." }, { Define, "define", 'D', CommandLineParser::Required, "Add additional define directive to clang." }, { DefaultArgument, "default-argument", 0, CommandLineParser::Required, "Add additional argument to clang." }, { LogFile, "log-file", 'L', CommandLineParser::Required, "Log to this file." }, { CrashDumpFile, "crash-dump-file", 0, CommandLineParser::Required, "File to dump crash log to (default is <datadir>/crash.dump)." }, { SetEnv, "setenv", 'e', CommandLineParser::Required, "Set this environment variable (--setenv \"foobar=1\")." }, { NoWall, "no-Wall", 'W', CommandLineParser::NoValue, "Don't use -Wall." }, { Weverything, "Weverything", 'u', CommandLineParser::NoValue, "Use -Weverything." }, { Verbose, "verbose", 'v', CommandLineParser::NoValue, "Change verbosity, multiple -v's are allowed." }, { JobCount, "job-count", 'j', CommandLineParser::Required, String::format("Spawn this many concurrent processes for indexing (default %d).", std::max(2, ThreadPool::idealThreadCount())) }, { HeaderErrorJobCount, "header-error-job-count", 'H', CommandLineParser::Required, "Allow this many concurrent header error jobs (default std::max(1, --job-count / 2))." }, { Test, "test", 't', CommandLineParser::Required, "Run this test." }, { TestTimeout, "test-timeout", 'z', CommandLineParser::Required, "Timeout for test to complete." }, { CleanSlate, "clean-slate", 'C', CommandLineParser::NoValue, "Clear out all data." }, { DisableSigHandler, "disable-sighandler", 'x', CommandLineParser::NoValue, "Disable signal handler to dump stack for crashes." }, { Silent, "silent", 'S', CommandLineParser::NoValue, "No logging to stdout/stderr." }, { ExcludeFilter, "exclude-filter", 'X', CommandLineParser::Required, "Files to exclude from rdm, default \"" DEFAULT_EXCLUDEFILTER "\"." }, { SocketFile, "socket-file", 'n', CommandLineParser::Required, "Use this file for the server socket (default ~/.rdm)." }, { DataDir, "data-dir", 'd', CommandLineParser::Required, "Use this directory to store persistent data (default $XDG_CACHE_HOME/rtags otherwise ~/.cache/rtags)." }, { IgnorePrintfFixits, "ignore-printf-fixits", 'F', CommandLineParser::NoValue, "Disregard any clang fixit that looks like it's trying to fix format for printf and friends." }, { ErrorLimit, "error-limit", 'f', CommandLineParser::Required, "Set error limit to argument (-ferror-limit={arg} (default " STR(DEFAULT_ERROR_LIMIT) ")." }, { BlockArgument, "block-argument", 'G', CommandLineParser::Required, "Block this argument from being passed to clang. E.g. rdm --block-argument -fno-inline" }, { NoSpellChecking, "no-spell-checking", 'l', CommandLineParser::NoValue, "Don't pass -fspell-checking." }, { LargeByValueCopy, "large-by-value-copy", 'r', CommandLineParser::Required, "Use -Wlarge-by-value-copy=[arg] when invoking clang." }, { AllowMultipleSources, "allow-multiple-sources", 'm', CommandLineParser::NoValue, "Don't merge source files added with -c." }, { NoStartupProject, "no-startup-project", 'o', CommandLineParser::NoValue, "Don't restore the last current project on startup." }, { NoNoUnknownWarningsOption, "no-no-unknown-warnings-option", 'Y', CommandLineParser::NoValue, "Don't pass -Wno-unknown-warning-option." }, { IgnoreCompiler, "ignore-compiler", 'b', CommandLineParser::Required, "Ignore this compiler." }, { CompilerWrappers, "compiler-wrappers", 0, CommandLineParser::Required, "Consider these filenames compiler wrappers (split on ;), default " DEFAULT_COMPILER_WRAPPERS "\"." }, { WatchSystemPaths, "watch-system-paths", 'w', CommandLineParser::NoValue, "Watch system paths for changes." }, { RpVisitFileTimeout, "rp-visit-file-timeout", 'Z', CommandLineParser::Required, "Timeout for rp visitfile commands in ms (0 means no timeout) (default " STR(DEFAULT_RP_VISITFILE_TIMEOUT) ")." }, { RpIndexerMessageTimeout, "rp-indexer-message-timeout", 'T', CommandLineParser::Required, "Timeout for rp indexer-message in ms (0 means no timeout) (default " STR(DEFAULT_RP_INDEXER_MESSAGE_TIMEOUT) ")." }, { RpConnectTimeout, "rp-connect-timeout", 'O', CommandLineParser::Required, "Timeout for connection from rp to rdm in ms (0 means no timeout) (default " STR(DEFAULT_RP_CONNECT_TIMEOUT) ")." }, { RpConnectAttempts, "rp-connect-attempts", 0, CommandLineParser::Required, "Number of times rp attempts to connect to rdm before giving up. (default " STR(DEFAULT_RP_CONNECT_ATTEMPTS) ")." }, { RpNiceValue, "rp-nice-value", 'a', CommandLineParser::Required, "Nice value to use for rp (nice(2)) (default is no nicing)." }, { SuspendRpOnCrash, "suspend-rp-on-crash", 'q', CommandLineParser::NoValue, "Suspend rp in SIGSEGV handler (default " DEFAULT_SUSPEND_RP ")." }, { RpLogToSyslog, "rp-log-to-syslog", 0, CommandLineParser::NoValue, "Make rp log to syslog." }, { StartSuspended, "start-suspended", 'Q', CommandLineParser::NoValue, "Start out suspended (no reindexing enabled)." }, { SeparateDebugAndRelease, "separate-debug-and-release", 'E', CommandLineParser::NoValue, "Normally rdm doesn't consider release and debug as different builds. Pass this if you want it to." }, { Separate32BitAnd64Bit, "separate-32-bit-and-64-bit", 0, CommandLineParser::NoValue, "Normally rdm doesn't consider -m32 and -m64 as different builds. Pass this if you want it to." }, { SourceIgnoreIncludePathDifferencesInUsr, "ignore-include-path-differences-in-usr", 0, CommandLineParser::NoValue, "Don't consider sources that only differ in includepaths within /usr (not including /usr/home/) as different builds." }, { MaxCrashCount, "max-crash-count", 'K', CommandLineParser::Required, "Max number of crashes before giving up a sourcefile (default " STR(DEFAULT_MAX_CRASH_COUNT) ")." }, { CompletionCacheSize, "completion-cache-size", 'i', CommandLineParser::Required, "Number of translation units to cache (default " STR(DEFAULT_COMPLETION_CACHE_SIZE) ")." }, { CompletionNoFilter, "completion-no-filter", 0, CommandLineParser::NoValue, "Don't filter private members and destructors from completions." }, { CompletionLogs, "completion-logs", 0, CommandLineParser::NoValue, "Log more info about completions." }, { MaxIncludeCompletionDepth, "max-include-completion-depth", 0, CommandLineParser::Required, "Max recursion depth for header completion (default " STR(DEFAULT_MAX_INCLUDE_COMPLETION_DEPTH) ")." }, { AllowWpedantic, "allow-Wpedantic", 'P', CommandLineParser::NoValue, "Don't strip out -Wpedantic. This can cause problems in certain projects." }, { AllowWErrorAndWFatalErrors, "allow-Werror", 0, CommandLineParser::NoValue, "Don't strip out -Werror and -Wfatal-errors. By default these are stripped out. " }, { EnableCompilerManager, "enable-compiler-manager", 'R', CommandLineParser::NoValue, "Query compilers for their actual include paths instead of letting clang use its own." }, { EnableNDEBUG, "enable-NDEBUG", 'g', CommandLineParser::NoValue, "Don't remove -DNDEBUG from compile lines." }, { Progress, "progress", 'p', CommandLineParser::NoValue, "Report compilation progress in diagnostics output." }, { MaxFileMapCacheSize, "max-file-map-cache-size", 'y', CommandLineParser::Required, "Max files to cache per query (Should not exceed maximum number of open file descriptors allowed per process) (default " STR(DEFAULT_RDM_MAX_FILE_MAP_CACHE_SIZE) ")." }, #ifdef FILEMANAGER_OPT_IN { FileManagerWatch, "filemanager-watch", 'M', CommandLineParser::NoValue, "Use a file system watcher for filemanager." }, #else { NoFileManagerWatch, "no-filemanager-watch", 'M', CommandLineParser::NoValue, "Don't use a file system watcher for filemanager." }, #endif { NoFileManager, "no-filemanager", 0, CommandLineParser::NoValue, "Don't scan project directory for files. (rc -P won't work)." }, { NoFileLock, "no-file-lock", 0, CommandLineParser::NoValue, "Disable file locking. Not entirely safe but might improve performance on certain systems." }, { PchEnabled, "pch-enabled", 0, CommandLineParser::NoValue, "Enable PCH (experimental)." }, { NoFilesystemWatcher, "no-filesystem-watcher", 'B', CommandLineParser::NoValue, "Disable file system watching altogether. Reindexing has to be triggered manually." }, { ArgTransform, "arg-transform", 'V', CommandLineParser::Required, "Use arg to transform arguments. [arg] should be executable with (execv(3))." }, { NoComments, "no-comments", 0, CommandLineParser::NoValue, "Don't parse/store doxygen comments." }, #ifdef RTAGS_HAS_LAUNCHD { Launchd, "launchd", 0, CommandLineParser::NoValue, "Run as a launchd job (use launchd API to retrieve socket opened by launchd on rdm's behalf)." }, #endif { InactivityTimeout, "inactivity-timeout", 0, CommandLineParser::Required, "Time in seconds after which rdm will quit if there's been no activity (N.B., once rdm has quit, something will need to re-run it!)." }, { Daemon, "daemon", 0, CommandLineParser::NoValue, "Run as daemon (detach from terminal)." }, { LogFileLogLevel, "log-file-log-level", 0, CommandLineParser::Required, "Log level for log file (default is error), options are: error, warning, debug or verbose-debug." }, { WatchSourcesOnly, "watch-sources-only", 0, CommandLineParser::NoValue, "Only watch source files (not dependencies)." }, { DebugLocations, "debug-locations", 0, CommandLineParser::NoValue, "Set debug locations." }, { ValidateFileMaps, "validate-file-maps", 0, CommandLineParser::NoValue, "Spend some time validating project data on startup." }, { TcpPort, "tcp-port", 0, CommandLineParser::Required, "Listen on this tcp socket (default none)." }, { RpPath, "rp-path", 0, CommandLineParser::Required, String::format<256>("Path to rp (default %s).", defaultRP().constData()) }, { LogTimestamp, "log-timestamp", 0, CommandLineParser::NoValue, "Add timestamp to logs." }, { LogFlushOption, "log-flush", 0, CommandLineParser::NoValue, "Flush stderr/stdout after each log." }, { SandboxRoot, "sandbox-root", 0, CommandLineParser::Required, "Create index using relative paths by stripping dir (enables copying of tag index db files without need to reindex)." }, { PollTimer, "poll-timer", 0, CommandLineParser::Required, "Poll the database of the current project every <arg> seconds. " }, { NoRealPath, "no-realpath", 0, CommandLineParser::NoValue, "Don't use realpath(3) for files" }, { Noop, "config", 'c', CommandLineParser::Required, "Use this file (instead of ~/.rdmrc)." }, { Noop, "no-rc", 'N', CommandLineParser::NoValue, "Don't load any rc files." } }; std::function<CommandLineParser::ParseStatus(OptionType type, String &&value, size_t &idx, const List<String> &args)> cb; cb = [&](OptionType type, String &&value, size_t &, const List<String> &) -> CommandLineParser::ParseStatus { switch (type) { case None: case Noop: break; case Help: { CommandLineParser::help(stdout, Rct::executablePath().fileName(), opts); return { String(), CommandLineParser::Parse_Ok }; } case Version: { fprintf(stdout, "%s\n", RTags::versionString().constData()); return { String(), CommandLineParser::Parse_Ok }; } case IncludePath: { serverOpts.includePaths.append(Source::Include(Source::Include::Type_Include, Path::resolved(value))); break; } case Isystem: { serverOpts.includePaths.append(Source::Include(Source::Include::Type_System, Path::resolved(value))); break; } case Define: { const size_t eq = value.indexOf('='); Source::Define def; if (eq == String::npos) { def.define = std::move(value); } else { def.define = value.left(eq); def.value = value.mid(eq + 1); } serverOpts.defines.append(def); break; } case DefaultArgument: { serverOpts.defaultArguments.append(std::move(value)); break; } case LogFile: { logFile = std::move(value); logFile.resolve(); logLevel = LogLevel::None; break; } case CrashDumpFile: { strncpy(crashDumpFilePath, value.constData(), sizeof(crashDumpFilePath) - 1); break; } case SetEnv: { putenv(&value[0]); break; } case NoWall: { serverOpts.options &= ~Server::Wall; break; } case Weverything: { serverOpts.options |= Server::Weverything; break; } case Verbose: { if (logLevel != LogLevel::None) ++logLevel; break; } case JobCount: { bool ok; serverOpts.jobCount = String(value).toULong(&ok); if (!ok) { return { String::format<1024>("Can't parse argument to -j %s. -j must be a positive integer.\n", value.constData()), CommandLineParser::Parse_Error }; } break; } case HeaderErrorJobCount: { bool ok; serverOpts.headerErrorJobCount = String(value).toULong(&ok); if (!ok) { return { String::format<1024>("Can't parse argument to -H %s. -H must be a positive integer.", value.constData()), CommandLineParser::Parse_Error }; } break; } case Test: { Path test(value); if (!test.resolve() || !test.isFile()) { return { String::format<1024>("%s doesn't seem to be a file", value.constData()), CommandLineParser::Parse_Error }; } serverOpts.tests += test; break; } case TestTimeout: { serverOpts.testTimeout = atoi(value.constData()); if (serverOpts.testTimeout <= 0) { return { String::format<1024>("Invalid argument to -z %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case PollTimer: { serverOpts.pollTimer = atoi(value.constData()); if (serverOpts.pollTimer < 0) { return { String::format<1024>("Invalid argument to --poll-timer %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case CleanSlate: { serverOpts.options |= Server::ClearProjects; break; } case DisableSigHandler: { sigHandler = false; break; } case Silent: { logLevel = LogLevel::None; break; } case ExcludeFilter: { serverOpts.excludeFilters += String(value).split(';'); break; } case SocketFile: { serverOpts.socketFile = std::move(value); serverOpts.socketFile.resolve(); break; } case DataDir: { serverOpts.dataDir = String::format<128>("%s", Path::resolved(value).constData()); break; } case IgnorePrintfFixits: { serverOpts.options |= Server::IgnorePrintfFixits; break; } case ErrorLimit: { bool ok; serverOpts.errorLimit = String(value).toULong(&ok); if (!ok) { return { String::format<1024>("Can't parse argument to --error-limit %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case BlockArgument: { serverOpts.blockedArguments << value; break; } case NoSpellChecking: { serverOpts.options &= ~Server::SpellChecking; break; } case LargeByValueCopy: { int large = atoi(value.constData()); if (large <= 0) { return { String::format<1024>("Can't parse argument to -r %s", value.constData()), CommandLineParser::Parse_Error }; } serverOpts.defaultArguments.append("-Wlarge-by-value-copy=" + String(value)); // ### not quite working break; } case AllowMultipleSources: { serverOpts.options |= Server::AllowMultipleSources; break; } case NoStartupProject: { serverOpts.options |= Server::NoStartupCurrentProject; break; } case NoNoUnknownWarningsOption: { serverOpts.options |= Server::NoNoUnknownWarningsOption; break; } case IgnoreCompiler: { serverOpts.ignoredCompilers.insert(Path::resolved(value)); break; } case CompilerWrappers: { serverOpts.compilerWrappers = String(value).split(";", String::SkipEmpty).toSet(); break; } case WatchSystemPaths: { serverOpts.options |= Server::WatchSystemPaths; break; } case RpVisitFileTimeout: { serverOpts.rpVisitFileTimeout = atoi(value.constData()); if (serverOpts.rpVisitFileTimeout < 0) { return { String::format<1024>("Invalid argument to -Z %s", value.constData()), CommandLineParser::Parse_Error }; } if (!serverOpts.rpVisitFileTimeout) serverOpts.rpVisitFileTimeout = -1; break; } case RpIndexerMessageTimeout: { serverOpts.rpIndexDataMessageTimeout = atoi(value.constData()); if (serverOpts.rpIndexDataMessageTimeout <= 0) { return { String::format<1024>("Can't parse argument to -T %s.", value.constData()), CommandLineParser::Parse_Error }; } break; } case RpConnectTimeout: { serverOpts.rpConnectTimeout = atoi(value.constData()); if (serverOpts.rpConnectTimeout < 0) { return { String::format<1024>("Invalid argument to -O %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case RpConnectAttempts: { serverOpts.rpConnectAttempts = atoi(value.constData()); if (serverOpts.rpConnectAttempts <= 0) { return { String::format<1024>("Invalid argument to --rp-connect-attempts %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case RpNiceValue: { bool ok; serverOpts.rpNiceValue = value.toLong(&ok); if (!ok) { return { String::format<1024>("Can't parse argument to -a %s.", value.constData()), CommandLineParser::Parse_Error }; } break; } case SuspendRpOnCrash: { serverOpts.options |= Server::SuspendRPOnCrash; break; } case RpLogToSyslog: { serverOpts.options |= Server::RPLogToSyslog; break; } case StartSuspended: { serverOpts.options |= Server::StartSuspended; break; } case SeparateDebugAndRelease: { serverOpts.options |= Server::SeparateDebugAndRelease; break; } case Separate32BitAnd64Bit: { serverOpts.options |= Server::Separate32BitAnd64Bit; break; } case SourceIgnoreIncludePathDifferencesInUsr: { serverOpts.options |= Server::SourceIgnoreIncludePathDifferencesInUsr; break; } case MaxCrashCount: { serverOpts.maxCrashCount = atoi(value.constData()); if (serverOpts.maxCrashCount <= 0) { return { String::format<1024>("Invalid argument to -K %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case CompletionCacheSize: { serverOpts.completionCacheSize = atoi(value.constData()); if (serverOpts.completionCacheSize <= 0) { return { String::format<1024>("Invalid argument to -i %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case CompletionNoFilter: { serverOpts.options |= Server::CompletionsNoFilter; break; } case CompletionLogs: { serverOpts.options |= Server::CompletionLogs; break; } case MaxIncludeCompletionDepth: { serverOpts.maxIncludeCompletionDepth = strtoul(value.constData(), 0, 10); break; } case AllowWpedantic: { serverOpts.options |= Server::AllowPedantic; break; } case AllowWErrorAndWFatalErrors: { serverOpts.options |= Server::AllowWErrorAndWFatalErrors; break; } case EnableCompilerManager: { serverOpts.options |= Server::EnableCompilerManager; break; } case EnableNDEBUG: { serverOpts.options |= Server::EnableNDEBUG; break; } case Progress: { serverOpts.options |= Server::Progress; break; } case MaxFileMapCacheSize: { serverOpts.maxFileMapScopeCacheSize = atoi(value.constData()); if (serverOpts.maxFileMapScopeCacheSize <= 0) { return { String::format<1024>("Invalid argument to -y %s", value.constData()), CommandLineParser::Parse_Error }; } break; } #ifdef FILEMANAGER_OPT_IN case FileManagerWatch: { serverOpts.options &= ~Server::NoFileManagerWatch; break; } #else case NoFileManagerWatch: { serverOpts.options |= Server::NoFileManagerWatch; break; } #endif case NoFileManager: { serverOpts.options |= Server::NoFileManager; break; } case NoFileLock: { serverOpts.options |= Server::NoFileLock; break; } case PchEnabled: { serverOpts.options |= Server::PCHEnabled; break; } case NoFilesystemWatcher: { serverOpts.options |= Server::NoFileSystemWatch; break; } case ArgTransform: { serverOpts.argTransform = Process::findCommand(value); if (!value.isEmpty() && serverOpts.argTransform.isEmpty()) { return { String::format<1024>("Invalid argument to -V. Can't resolve %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case NoComments: { serverOpts.options |= Server::NoComments; break; } #ifdef RTAGS_HAS_LAUNCHD case Launchd: { serverOpts.options |= Server::Launchd; break; } #endif case InactivityTimeout: { inactivityTimeout = atoi(value.constData()); // seconds. if (inactivityTimeout <= 0) { return { String::format<1024>("Invalid argument to --inactivity-timeout %s", value.constData()), CommandLineParser::Parse_Error }; } break; } case Daemon: { daemon = true; logLevel = LogLevel::None; break; } case LogFileLogLevel: { if (!strcasecmp(value.constData(), "verbose-debug")) { logFileLogLevel = LogLevel::VerboseDebug; } else if (!strcasecmp(value.constData(), "debug")) { logFileLogLevel = LogLevel::Debug; } else if (!strcasecmp(value.constData(), "warning")) { logFileLogLevel = LogLevel::Warning; } else if (!strcasecmp(value.constData(), "error")) { logFileLogLevel = LogLevel::Error; } else { return { String::format<1024>("Unknown log level: %s options are error, warning, debug or verbose-debug", value.constData()), CommandLineParser::Parse_Error }; } break; } case WatchSourcesOnly: { serverOpts.options |= Server::WatchSourcesOnly; break; } case DebugLocations: { if (value == "clear" || value == "none") { serverOpts.debugLocations.clear(); } else { serverOpts.debugLocations << value; } break; } case ValidateFileMaps: { serverOpts.options |= Server::ValidateFileMaps; break; } case TcpPort: { serverOpts.tcpPort = atoi(value.constData()); if (!serverOpts.tcpPort) { return { String::format<1024>("Invalid port %s for --tcp-port", value.constData()), CommandLineParser::Parse_Error }; } break; } case RpPath: { serverOpts.rp = std::move(value); if (serverOpts.rp.isFile()) { serverOpts.rp.resolve(); } else { return { String::format<1024>("%s is not a file", value.constData()), CommandLineParser::Parse_Error }; } break; } case LogTimestamp: { logFlags |= LogTimeStamp; break; } case LogFlushOption: { logFlags |= LogFlush; break; } case SandboxRoot: { serverOpts.sandboxRoot = std::move(value); if (!serverOpts.sandboxRoot.endsWith('/')) serverOpts.sandboxRoot += '/'; if (!serverOpts.sandboxRoot.resolve() || !serverOpts.sandboxRoot.isDir()) { return { String::format<1024>("%s is not a valid directory for sandbox-root", serverOpts.sandboxRoot.constData()), CommandLineParser::Parse_Error }; } break; } case NoRealPath: { Path::setRealPathEnabled(false); serverOpts.options |= Server::NoRealPath; break; } } return { String(), CommandLineParser::Parse_Exec }; }; const std::initializer_list<CommandLineParser::Option<CommandLineParser::ConfigOptionType> > configOpts = { { CommandLineParser::Config, "config", 'c', CommandLineParser::Required, "Use this file (instead of ~/.rdmrc)." }, { CommandLineParser::NoRc, "no-rc", 'N', CommandLineParser::NoValue, "Don't load any rc files." } }; const CommandLineParser::ParseStatus status = CommandLineParser::parse<OptionType>(argc, argv, opts, NullFlags, cb, "rdm", configOpts); switch (status.status) { case CommandLineParser::Parse_Error: fprintf(stderr, "%s\n", status.error.constData()); return 1; case CommandLineParser::Parse_Ok: return 0; case CommandLineParser::Parse_Exec: break; } if (daemon) { switch (fork()) { case -1: fprintf(stderr, "Failed to fork (%d) %s\n", errno, strerror(errno)); return 1; case 0: setsid(); switch (fork()) { case -1: fprintf(stderr, "Failed to fork (%d) %s\n", errno, strerror(errno)); return 1; case 0: break; default: return 0; } break; default: return 0; } } if (serverOpts.excludeFilters.isEmpty()) serverOpts.excludeFilters = String(DEFAULT_EXCLUDEFILTER).split(';'); if (serverOpts.compilerWrappers.isEmpty()) serverOpts.compilerWrappers = String(DEFAULT_COMPILER_WRAPPERS).split(';').toSet(); if (!serverOpts.headerErrorJobCount) { serverOpts.headerErrorJobCount = std::max<size_t>(1, serverOpts.jobCount / 2); } else { serverOpts.headerErrorJobCount = std::min(serverOpts.headerErrorJobCount, serverOpts.jobCount); } if (sigHandler) { signal(SIGSEGV, signalHandler); signal(SIGBUS, signalHandler); signal(SIGILL, signalHandler); signal(SIGABRT, signalHandler); } if (!initLogging(argv[0], logFlags, logLevel, logFile, logFileLogLevel)) { fprintf(stderr, "Can't initialize logging with %d %s %s\n", logLevel.toInt(), logFile.constData(), logFlags.toString().constData()); return 1; } #ifdef RTAGS_HAS_LAUNCHD if (serverOpts.options & Server::Launchd) { // Clamp inactivity timeout. launchd starts to worry if the // process runs for less than 10 seconds. static const int MIN_INACTIVITY_TIMEOUT = 15; // includes // fudge factor. if (inactivityTimeout < MIN_INACTIVITY_TIMEOUT) { inactivityTimeout = MIN_INACTIVITY_TIMEOUT; fprintf(stderr, "launchd mode - clamped inactivity timeout to %d to avoid launchd warnings.\n", inactivityTimeout); } } #endif EventLoop::SharedPtr loop(new EventLoop); loop->init(EventLoop::MainEventLoop|EventLoop::EnableSigIntHandler|EventLoop::EnableSigTermHandler); auto server = std::make_shared<Server>(); if (!serverOpts.tests.isEmpty()) { char buf[1024]; Path path; while (true) { strcpy(buf, "/tmp/rtags-test-XXXXXX"); if (!mkdtemp(buf)) { fprintf(stderr, "Failed to mkdtemp (%d)\n", errno); return 1; } path = buf; path.resolve(); break; } serverOpts.dataDir = path; strcpy(buf, "/tmp/rtags-sock-XXXXXX"); const int fd = mkstemp(buf); if (fd == -1) { fprintf(stderr, "Failed to mkstemp (%d)\n", errno); return 1; } close(fd); serverOpts.socketFile = buf; serverOpts.socketFile.resolve(); } serverOpts.dataDir = serverOpts.dataDir.ensureTrailingSlash(); #ifdef HAVE_BACKTRACE if (strlen(crashDumpFilePath)) { if (crashDumpFilePath[0] != '/') { const String f = crashDumpFilePath; snprintf(crashDumpFilePath, sizeof(crashDumpFilePath), "%s%s", serverOpts.dataDir.constData(), f.constData()); } snprintf(crashDumpTempFilePath, sizeof(crashDumpTempFilePath), "%s.tmp", crashDumpFilePath); Path::mkdir(serverOpts.dataDir); crashDumpFile = fopen(crashDumpTempFilePath, "w"); if (!crashDumpFile) { fprintf(stderr, "Couldn't open temp file %s for write (%d)\n", crashDumpTempFilePath, errno); } } #endif if (!server->init(serverOpts)) { cleanupLogging(); return 1; } if (!serverOpts.tests.isEmpty()) { return server->runTests() ? 0 : 1; } loop->setInactivityTimeout(inactivityTimeout * 1000); loop->exec(); const int ret = server->exitCode(); server.reset(); cleanupLogging(); return ret; }
int main(int argc, char** argv) { #ifdef OS_Darwin struct rlimit rlp; if (getrlimit(RLIMIT_NOFILE, &rlp) == 0) { if (rlp.rlim_cur < 1000) { rlp.rlim_cur = 1000; setrlimit(RLIMIT_NOFILE, &rlp); } } #endif { pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_getstacksize(&attr, &defaultStackSize); pthread_attr_destroy(&attr); if (defaultStackSize < 1024 * 1024 * 4) { // 4 megs should be enough for everyone right? defaultStackSize = 1024 * 1024 * 4; } } Rct::findExecutablePath(*argv); struct option opts[] = { { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 2 }, { "include-path", required_argument, 0, 'I' }, { "isystem", required_argument, 0, 's' }, { "define", required_argument, 0, 'D' }, { "log-file", required_argument, 0, 'L' }, { "setenv", required_argument, 0, 'e' }, { "no-Wall", no_argument, 0, 'W' }, { "Weverything", no_argument, 0, 'u' }, { "cache-AST", required_argument, 0, 'A' }, { "verbose", no_argument, 0, 'v' }, { "job-count", required_argument, 0, 'j' }, { "header-error-job-count", required_argument, 0, 'H' }, { "test", required_argument, 0, 't' }, { "test-timeout", required_argument, 0, 'z' }, { "clean-slate", no_argument, 0, 'C' }, { "disable-sighandler", no_argument, 0, 'x' }, { "silent", no_argument, 0, 'S' }, { "exclude-filter", required_argument, 0, 'X' }, { "socket-file", required_argument, 0, 'n' }, { "config", required_argument, 0, 'c' }, { "no-rc", no_argument, 0, 'N' }, { "data-dir", required_argument, 0, 'd' }, { "ignore-printf-fixits", no_argument, 0, 'F' }, { "no-unlimited-errors", no_argument, 0, 'f' }, { "block-argument", required_argument, 0, 'G' }, { "no-spell-checking", no_argument, 0, 'l' }, { "large-by-value-copy", required_argument, 0, 'r' }, { "disallow-multiple-sources", no_argument, 0, 'm' }, { "no-startup-project", no_argument, 0, 'o' }, { "no-no-unknown-warnings-option", no_argument, 0, 'Y' }, { "ignore-compiler", required_argument, 0, 'b' }, { "watch-system-paths", no_argument, 0, 'w' }, { "rp-visit-file-timeout", required_argument, 0, 'Z' }, { "rp-indexer-message-timeout", required_argument, 0, 'T' }, { "rp-connect-timeout", required_argument, 0, 'O' }, { "rp-connect-attempts", required_argument, 0, 3 }, { "rp-nice-value", required_argument, 0, 'a' }, { "thread-stack-size", required_argument, 0, 'k' }, { "suspend-rp-on-crash", no_argument, 0, 'q' }, { "rp-log-to-syslog", no_argument, 0, 7 }, { "start-suspended", no_argument, 0, 'Q' }, { "separate-debug-and-release", no_argument, 0, 'E' }, { "max-crash-count", required_argument, 0, 'K' }, { "completion-cache-size", required_argument, 0, 'i' }, { "completion-no-filter", no_argument, 0, 8 }, { "extra-compilers", required_argument, 0, 'U' }, { "allow-Wpedantic", no_argument, 0, 'P' }, { "enable-compiler-manager", no_argument, 0, 'R' }, { "enable-NDEBUG", no_argument, 0, 'g' }, { "progress", no_argument, 0, 'p' }, { "max-file-map-cache-size", required_argument, 0, 'y' }, #ifdef OS_FreeBSD { "filemanager-watch", no_argument, 0, 'M' }, #else { "no-filemanager-watch", no_argument, 0, 'M' }, #endif { "no-filemanager", no_argument, 0, 15 }, { "no-file-lock", no_argument, 0, 13 }, { "pch-enabled", no_argument, 0, 14 }, { "no-filesystem-watcher", no_argument, 0, 'B' }, { "arg-transform", required_argument, 0, 'V' }, { "no-comments", no_argument, 0, 1 }, #ifdef RTAGS_HAS_LAUNCHD { "launchd", no_argument, 0, 4 }, #endif { "inactivity-timeout", required_argument, 0, 5 }, { "daemon", no_argument, 0, 6 }, { "log-file-log-level", required_argument, 0, 9 }, { "watch-sources-only", no_argument, 0, 10 }, { "debug-locations", no_argument, 0, 11 }, { "validate-file-maps", no_argument, 0, 16 }, { "tcp-port", required_argument, 0, 12 }, { "rp-path", required_argument, 0, 17 }, { "log-timestamp", no_argument, 0, 18 }, { 0, 0, 0, 0 } }; const String shortOptions = Rct::shortOptions(opts); if (getenv("RTAGS_DUMP_UNUSED")) { String unused; for (int i=0; i<26; ++i) { if (!shortOptions.contains('a' + i)) unused.append('a' + i); if (!shortOptions.contains('A' + i)) unused.append('A' + i); } printf("Unused: %s\n", unused.constData()); for (int i=0; opts[i].name; ++i) { if (opts[i].name) { if (!opts[i].val) { printf("No shortoption for %s\n", opts[i].name); } else if (opts[i].name[0] != opts[i].val) { printf("Not ideal option for %s|%c\n", opts[i].name, opts[i].val); } } } return 0; } bool daemon = false; List<String> argCopy; List<char*> argList; { bool norc = false; Path rcfile = Path::home() + ".rdmrc"; opterr = 0; StackBuffer<128, char*> originalArgv(argc); memcpy(originalArgv, argv, sizeof(char*) * argc); /* getopt will molest argv by moving pointers around when it sees * fit. Their idea of an optional argument is different from ours so we * have to take a copy of argv before they get their sticky fingers all * over it. * * We think this should be okay for an optional argument: * -s something * * They only populate optarg if you do: * -ssomething. * * We don't want to copy argv into argList before processing rc files * since command line args should take precedence over things in rc * files. * */ while (true) { const int c = getopt_long(argc, argv, shortOptions.constData(), opts, 0); if (c == -1) break; switch (c) { case 'N': norc = true; break; case 'c': rcfile = optarg; break; default: break; } } opterr = 1; argList.append(argv[0]); if (!norc) { String rc = Path("/etc/rdmrc").readAll(); if (!rc.isEmpty()) { for (const String& s : rc.split('\n')) { if (!s.isEmpty() && !s.startsWith('#')) argCopy += s.split(' '); } } if (!rcfile.isEmpty()) { rc = rcfile.readAll(); if (!rc.isEmpty()) { for (const String& s : rc.split('\n')) { if (!s.isEmpty() && !s.startsWith('#')) argCopy += s.split(' '); } } } const int s = argCopy.size(); for (int i=0; i<s; ++i) { String &arg = argCopy.at(i); if (!arg.isEmpty()) argList.append(arg.data()); } } for (int i=1; i<argc; ++i) argList.append(originalArgv[i]); optind = 1; } Server::Options serverOpts; serverOpts.threadStackSize = defaultStackSize; serverOpts.socketFile = String::format<128>("%s.rdm", Path::home().constData()); serverOpts.jobCount = std::max(2, ThreadPool::idealThreadCount()); serverOpts.headerErrorJobCount = -1; serverOpts.rpVisitFileTimeout = DEFAULT_RP_VISITFILE_TIMEOUT; serverOpts.rpIndexDataMessageTimeout = DEFAULT_RP_INDEXER_MESSAGE_TIMEOUT; serverOpts.rpConnectTimeout = DEFAULT_RP_CONNECT_TIMEOUT; serverOpts.rpConnectAttempts = DEFAULT_RP_CONNECT_ATTEMPTS; serverOpts.maxFileMapScopeCacheSize = DEFAULT_RDM_MAX_FILE_MAP_CACHE_SIZE; serverOpts.rpNiceValue = INT_MIN; serverOpts.options = Server::Wall|Server::SpellChecking; serverOpts.maxCrashCount = DEFAULT_MAX_CRASH_COUNT; serverOpts.completionCacheSize = DEFAULT_COMPLETION_CACHE_SIZE; serverOpts.rp = defaultRP(); #ifdef OS_FreeBSD serverOpts.options |= Server::NoFileManagerWatch; #endif // #ifndef NDEBUG // serverOpts.options |= Server::SuspendRPOnCrash; // #endif serverOpts.dataDir = String::format<128>("%s.rtags", Path::home().constData()); const char *logFile = 0; Flags<LogFlag> logFlags = DontRotate|LogStderr; LogLevel logLevel(LogLevel::Error); LogLevel logFileLogLevel(LogLevel::Error); bool sigHandler = true; assert(Path::home().endsWith('/')); int argCount = argList.size(); char **args = argList.data(); bool defaultDataDir = true; int inactivityTimeout = 0; while (true) { const int c = getopt_long(argCount, args, shortOptions.constData(), opts, 0); if (c == -1) break; switch (c) { case 'N': case 'c': // ignored break; case 'S': logLevel = LogLevel::None; break; case 'X': serverOpts.excludeFilters += String(optarg).split(';'); break; case 'G': serverOpts.blockedArguments << optarg; break; case 1: serverOpts.options |= Server::NoComments; break; case 10: serverOpts.options |= Server::WatchSourcesOnly; break; case 11: if (!strcmp(optarg, "clear") || !strcmp(optarg, "none")) { serverOpts.debugLocations.clear(); } else { serverOpts.debugLocations << optarg; } break; case 12: serverOpts.tcpPort = atoi(optarg); if (!serverOpts.tcpPort) { fprintf(stderr, "Invalid port %s for --tcp-port\n", optarg); return 1; } break; case 13: serverOpts.options |= Server::NoFileLock; break; case 14: serverOpts.options |= Server::PCHEnabled; break; case 15: serverOpts.options |= Server::NoFileManager; break; case 16: serverOpts.options |= Server::ValidateFileMaps; break; case 17: serverOpts.rp = optarg; if (serverOpts.rp.isFile()) serverOpts.rp.resolve(); break; case 18: logFlags |= LogTimeStamp; break; case 2: fprintf(stdout, "%s\n", RTags::versionString().constData()); return 0; case 6: daemon = true; logLevel = LogLevel::None; break; case 9: if (!strcasecmp(optarg, "verbose-debug")) { logFileLogLevel = LogLevel::VerboseDebug; } else if (!strcasecmp(optarg, "debug")) { logFileLogLevel = LogLevel::Debug; } else if (!strcasecmp(optarg, "warning")) { logFileLogLevel = LogLevel::Warning; } else if (!strcasecmp(optarg, "error")) { logFileLogLevel = LogLevel::Error; } else { fprintf(stderr, "Unknown log level: %s options are error, warning, debug or verbose-debug\n", optarg); return 1; } break; case 'U': serverOpts.extraCompilers.append(std::regex(optarg)); break; case 'E': serverOpts.options |= Server::SeparateDebugAndRelease; break; case 'g': serverOpts.options |= Server::EnableNDEBUG; break; case 'Q': serverOpts.options |= Server::StartSuspended; break; case 'Z': serverOpts.rpVisitFileTimeout = atoi(optarg); if (serverOpts.rpVisitFileTimeout < 0) { fprintf(stderr, "Invalid argument to -Z %s\n", optarg); return 1; } if (!serverOpts.rpVisitFileTimeout) serverOpts.rpVisitFileTimeout = -1; break; case 'y': serverOpts.maxFileMapScopeCacheSize = atoi(optarg); if (serverOpts.maxFileMapScopeCacheSize <= 0) { fprintf(stderr, "Invalid argument to -y %s\n", optarg); return 1; } break; case 'O': serverOpts.rpConnectTimeout = atoi(optarg); if (serverOpts.rpConnectTimeout < 0) { fprintf(stderr, "Invalid argument to -O %s\n", optarg); return 1; } break; case 3: serverOpts.rpConnectAttempts = atoi(optarg); if (serverOpts.rpConnectAttempts <= 0) { fprintf(stderr, "Invalid argument to --rp-connect-attempts %s\n", optarg); return 1; } break; case 'k': serverOpts.threadStackSize = atoi(optarg); if (serverOpts.threadStackSize < 0) { fprintf(stderr, "Invalid argument to -k %s\n", optarg); return 1; } break; case 'b': serverOpts.ignoredCompilers.insert(Path::resolved(optarg)); break; case 't': { Path test(optarg); if (!test.resolve() || !test.isFile()) { fprintf(stderr, "%s doesn't seem to be a file\n", optarg); return 1; } serverOpts.tests += test; break; } case 'z': serverOpts.testTimeout = atoi(optarg); if (serverOpts.testTimeout <= 0) { fprintf(stderr, "Invalid argument to -z %s\n", optarg); return 1; } break; case 'n': serverOpts.socketFile = optarg; break; case 'd': defaultDataDir = false; serverOpts.dataDir = String::format<128>("%s", Path::resolved(optarg).constData()); break; case 'h': usage(stdout); return 0; case 'Y': serverOpts.options |= Server::NoNoUnknownWarningsOption; break; case 'p': serverOpts.options |= Server::Progress; break; case 'R': serverOpts.options |= Server::EnableCompilerManager; break; case 'm': serverOpts.options |= Server::DisallowMultipleSources; break; case 'o': serverOpts.options |= Server::NoStartupCurrentProject; break; case 'w': serverOpts.options |= Server::WatchSystemPaths; break; case 'q': serverOpts.options |= Server::SuspendRPOnCrash; break; case 'M': #ifdef OS_FreeBSD serverOpts.options &= ~Server::NoFileManagerWatch; #else serverOpts.options |= Server::NoFileManagerWatch; #endif break; case 'B': serverOpts.options |= Server::NoFileSystemWatch; break; case 'V': serverOpts.argTransform = Process::findCommand(optarg); if (strlen(optarg) && serverOpts.argTransform.isEmpty()) { fprintf(stderr, "Invalid argument to -V. Can't resolve %s", optarg); return 1; } break; case 'F': serverOpts.options |= Server::IgnorePrintfFixits; break; case 'f': serverOpts.options |= Server::NoUnlimitedErrors; break; case 'l': serverOpts.options &= ~Server::SpellChecking; break; case 'W': serverOpts.options &= ~Server::Wall; break; case 'u': serverOpts.options |= Server::Weverything; break; case 'P': serverOpts.options |= Server::AllowPedantic; break; case 'C': serverOpts.options |= Server::ClearProjects; break; case 'e': putenv(optarg); break; case 'x': sigHandler = false; break; case 'K': serverOpts.maxCrashCount = atoi(optarg); if (serverOpts.maxCrashCount <= 0) { fprintf(stderr, "Invalid argument to -K %s\n", optarg); return 1; } break; case 'i': serverOpts.completionCacheSize = atoi(optarg); if (serverOpts.completionCacheSize <= 0) { fprintf(stderr, "Invalid argument to -i %s\n", optarg); return 1; } break; case 'T': serverOpts.rpIndexDataMessageTimeout = atoi(optarg); if (serverOpts.rpIndexDataMessageTimeout <= 0) { fprintf(stderr, "Can't parse argument to -T %s.\n", optarg); return 1; } break; case 'a': { bool ok; serverOpts.rpNiceValue = String(optarg).toLong(&ok); if (!ok) { fprintf(stderr, "Can't parse argument to -a %s.\n", optarg); return 1; } break; } case 'j': { bool ok; serverOpts.jobCount = String(optarg).toULong(&ok); if (!ok) { fprintf(stderr, "Can't parse argument to -j %s. -j must be a positive integer.\n", optarg); return 1; } break; } case 'H': { bool ok; serverOpts.headerErrorJobCount = String(optarg).toULong(&ok); if (!ok) { fprintf(stderr, "Can't parse argument to -H %s. -H must be a positive integer.\n", optarg); return 1; } break; } case 'r': { int large = atoi(optarg); if (large <= 0) { fprintf(stderr, "Can't parse argument to -r %s\n", optarg); return 1; } serverOpts.defaultArguments.append("-Wlarge-by-value-copy=" + String(optarg)); // ### not quite working break; } case 'D': { const char *eq = strchr(optarg, '='); Source::Define def; if (!eq) { def.define = optarg; } else { def.define = String(optarg, eq - optarg); def.value = eq + 1; } serverOpts.defines.append(def); break; } case 'I': serverOpts.includePaths.append(Source::Include(Source::Include::Type_Include, Path::resolved(optarg))); break; case 's': serverOpts.includePaths.append(Source::Include(Source::Include::Type_System, Path::resolved(optarg))); break; case 'L': logFile = optarg; logLevel = LogLevel::None; break; case 'v': if (logLevel != LogLevel::None) ++logLevel; break; #ifdef RTAGS_HAS_LAUNCHD case 4: serverOpts.options |= Server::Launchd; break; #endif case 5: inactivityTimeout = atoi(optarg); // seconds. if (inactivityTimeout <= 0) { fprintf(stderr, "Invalid argument to --inactivity-timeout %s\n", optarg); return 1; } break; case 7: serverOpts.options |= Server::RPLogToSyslog; break; case 8: serverOpts.options |= Server::CompletionsNoFilter; break; case '?': { fprintf(stderr, "Run rdm --help for help\n"); return 1; } } } if (optind < argCount) { fprintf(stderr, "rdm: unexpected option -- '%s'\n", args[optind]); return 1; } if (daemon) { switch (fork()) { case -1: fprintf(stderr, "Failed to fork (%d) %s\n", errno, strerror(errno)); return 1; case 0: setsid(); switch (fork()) { case -1: fprintf(stderr, "Failed to fork (%d) %s\n", errno, strerror(errno)); return 1; case 0: break; default: return 0; } break; default: return 0; } } if (serverOpts.excludeFilters.isEmpty()) serverOpts.excludeFilters = String(EXCLUDEFILTER_DEFAULT).split(';'); if (!serverOpts.headerErrorJobCount) { serverOpts.headerErrorJobCount = std::max<size_t>(1, serverOpts.jobCount / 2); } else { serverOpts.headerErrorJobCount = std::min(serverOpts.headerErrorJobCount, serverOpts.jobCount); } if (sigHandler) { signal(SIGSEGV, sigSegvHandler); signal(SIGILL, sigSegvHandler); signal(SIGABRT, sigSegvHandler); } // Shell-expand logFile Path logPath(logFile); logPath.resolve(); if (!initLogging(argv[0], logFlags, logLevel, logPath.constData(), logFileLogLevel)) { fprintf(stderr, "Can't initialize logging with %d %s %s\n", logLevel.toInt(), logFile ? logFile : "", logFlags.toString().constData()); return 1; } #ifdef RTAGS_HAS_LAUNCHD if (serverOpts.options & Server::Launchd) { // Clamp inactivity timeout. launchd starts to worry if the // process runs for less than 10 seconds. static const int MIN_INACTIVITY_TIMEOUT = 15; // includes // fudge factor. if (inactivityTimeout < MIN_INACTIVITY_TIMEOUT) { inactivityTimeout = MIN_INACTIVITY_TIMEOUT; fprintf(stderr, "launchd mode - clamped inactivity timeout to %d to avoid launchd warnings.\n", inactivityTimeout); } } #endif EventLoop::SharedPtr loop(new EventLoop); loop->init(EventLoop::MainEventLoop|EventLoop::EnableSigIntHandler|EventLoop::EnableSigTermHandler); std::shared_ptr<Server> server(new Server); if (!serverOpts.tests.isEmpty()) { char buf[1024]; Path path; while (true) { strcpy(buf, "/tmp/rtags-test-XXXXXX"); if (!mkdtemp(buf)) { fprintf(stderr, "Failed to mkdtemp (%d)\n", errno); return 1; } path = buf; path.resolve(); break; } serverOpts.dataDir = path; strcpy(buf, "/tmp/rtags-sock-XXXXXX"); const int fd = mkstemp(buf); if (fd == -1) { fprintf(stderr, "Failed to mkstemp (%d)\n", errno); return 1; } close(fd); serverOpts.socketFile = buf; serverOpts.socketFile.resolve(); } if (defaultDataDir) { Path migration = String::format<128>("%s.rtags-file", Path::home().constData()); if (migration.isDir()) { Rct::removeDirectory(serverOpts.dataDir); rename(migration.constData(), serverOpts.dataDir.constData()); error() << "Migrated datadir from ~/.rtags-file ~/.rtags"; } } serverOpts.dataDir = serverOpts.dataDir.ensureTrailingSlash(); if (!server->init(serverOpts)) { cleanupLogging(); return 1; } if (!serverOpts.tests.isEmpty()) { return server->runTests() ? 0 : 1; } loop->setInactivityTimeout(inactivityTimeout * 1000); loop->exec(); const int ret = server->exitCode(); server.reset(); cleanupLogging(); return ret; }