]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Refactor main option processing to a separate file
authorJoel Rosdahl <joel@rosdahl.net>
Fri, 23 Jul 2021 11:26:08 +0000 (13:26 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sat, 24 Jul 2021 05:00:10 +0000 (07:00 +0200)
src/ccache.cpp
src/ccache.hpp
src/core/CMakeLists.txt
src/core/mainoptions.cpp [new file with mode: 0644]
src/core/mainoptions.hpp [new file with mode: 0644]

index 4d013e9d57a8f7b64152d5b01949830c3a9a9630..81298bd990c2c0033b7888a47163ac7940e19d26 100644 (file)
@@ -21,7 +21,6 @@
 
 #include "Args.hpp"
 #include "ArgsInfo.hpp"
-#include "Checksum.hpp"
 #include "Context.hpp"
 #include "Depfile.hpp"
 #include "Fd.hpp"
 #include "Logging.hpp"
 #include "Manifest.hpp"
 #include "MiniTrace.hpp"
-#include "ProgressBar.hpp"
 #include "Result.hpp"
-#include "ResultDumper.hpp"
-#include "ResultExtractor.hpp"
 #include "ResultRetriever.hpp"
 #include "SignalHandler.hpp"
 #include "TemporaryFile.hpp"
 #include <core/Statistics.hpp>
 #include <core/StatsLog.hpp>
 #include <core/exceptions.hpp>
+#include <core/mainoptions.hpp>
 #include <core/types.hpp>
 #include <core/wincompat.hpp>
 #include <storage/Storage.hpp>
-#include <util/expected.hpp>
 #include <util/path.hpp>
 #include <util/string.hpp>
 
 #include "third_party/nonstd/optional.hpp"
 #include "third_party/nonstd/string_view.hpp"
 
-#ifdef HAVE_GETOPT_LONG
-#  include <getopt.h>
-#elif defined(_WIN32)
-#  include "third_party/win32/getopt.h"
-#else
-extern "C" {
-#  include "third_party/getopt_long.h"
-}
-#endif
-
 #include <fcntl.h>
 
 #ifdef HAVE_UNISTD_H
@@ -96,75 +82,6 @@ using nonstd::nullopt;
 using nonstd::optional;
 using nonstd::string_view;
 
-constexpr const char VERSION_TEXT[] =
-  R"({} version {}
-Features: {}
-
-Copyright (C) 2002-2007 Andrew Tridgell
-Copyright (C) 2009-2021 Joel Rosdahl and other contributors
-
-See <https://ccache.dev/credits.html> for a complete list of contributors.
-
-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 3 of the License, or (at your option) any later
-version.
-)";
-
-constexpr const char USAGE_TEXT[] =
-  R"(Usage:
-    {} [options]
-    {} compiler [compiler options]
-    compiler [compiler options]          (via symbolic link)
-
-Common options:
-    -c, --cleanup              delete old files and recalculate size counters
-                               (normally not needed as this is done
-                               automatically)
-    -C, --clear                clear the cache completely (except configuration)
-        --config-path PATH     operate on configuration file PATH instead of the
-                               default
-    -d, --directory PATH       operate on cache directory PATH instead of the
-                               default
-        --evict-older-than AGE remove files older than AGE (unsigned integer
-                               with a d (days) or s (seconds) suffix)
-    -F, --max-files NUM        set maximum number of files in cache to NUM (use
-                               0 for no limit)
-    -M, --max-size SIZE        set maximum size of cache to SIZE (use 0 for no
-                               limit); available suffixes: k, M, G, T (decimal)
-                               and Ki, Mi, Gi, Ti (binary); default suffix: G
-    -X, --recompress LEVEL     recompress the cache to level LEVEL (integer or
-                               "uncompressed") using the Zstandard algorithm;
-                               see "Cache compression" in the manual for details
-    -o, --set-config KEY=VAL   set configuration item KEY to value VAL
-    -x, --show-compression     show compression statistics
-    -p, --show-config          show current configuration options in
-                               human-readable format
-        --show-log-stats       print statistics counters from the stats log
-                               in human-readable format
-    -s, --show-stats           show summary of configuration and statistics
-                               counters in human-readable format
-    -z, --zero-stats           zero statistics counters
-
-    -h, --help                 print this help text
-    -V, --version              print version and copyright information
-
-Options for scripting or debugging:
-        --checksum-file PATH   print the checksum (64 bit XXH3) of the file at
-                               PATH
-        --dump-manifest PATH   dump manifest file at PATH in text format
-        --dump-result PATH     dump result file at PATH in text format
-        --extract-result PATH  extract data stored in result file at PATH to the
-                               current working directory
-    -k, --get-config KEY       print the value of configuration key KEY
-        --hash-file PATH       print the hash (160 bit BLAKE3) of the file at
-                               PATH
-        --print-stats          print statistics counter IDs and corresponding
-                               values in machine-parsable format
-
-See also the manual on <https://ccache.dev/documentation.html>.
-)";
-
 // This is a string that identifies the current "version" of the hash sum
 // computed by ccache. If, for any reason, we want to force the hash sum to be
 // different for the same input in a new ccache version, we can just change
@@ -1901,14 +1818,6 @@ set_up_uncached_err()
   Util::setenv("UNCACHED_ERR_FD", FMT("{}", uncached_fd));
 }
 
-static void
-configuration_printer(const std::string& key,
-                      const std::string& value,
-                      const std::string& origin)
-{
-  PRINT(stdout, "({}) {} = {}\n", origin, key, value);
-}
-
 static int cache_compilation(int argc, const char* const* argv);
 static Statistic do_cache_compilation(Context& ctx, const char* const* argv);
 
@@ -1943,41 +1852,6 @@ log_result_to_stats_log(Context& ctx)
     .log_result(ctx.args_info.input_file, *result_id);
 }
 
-static void
-print_compression_statistics(const storage::primary::CompressionStatistics& cs)
-{
-  const double ratio = cs.compr_size > 0
-                         ? static_cast<double>(cs.content_size) / cs.compr_size
-                         : 0.0;
-  const double savings = ratio > 0.0 ? 100.0 - (100.0 / ratio) : 0.0;
-
-  const std::string on_disk_size_str =
-    Util::format_human_readable_size(cs.on_disk_size);
-  const std::string cache_size_str =
-    Util::format_human_readable_size(cs.compr_size + cs.incompr_size);
-  const std::string compr_size_str =
-    Util::format_human_readable_size(cs.compr_size);
-  const std::string content_size_str =
-    Util::format_human_readable_size(cs.content_size);
-  const std::string incompr_size_str =
-    Util::format_human_readable_size(cs.incompr_size);
-
-  PRINT(stdout,
-        "Total data:            {:>8s} ({} disk blocks)\n",
-        cache_size_str,
-        on_disk_size_str);
-  PRINT(stdout,
-        "Compressed data:       {:>8s} ({:.1f}% of original size)\n",
-        compr_size_str,
-        100.0 - savings);
-  PRINT(stdout, "  - Original data:     {:>8s}\n", content_size_str);
-  PRINT(stdout,
-        "  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
-        ratio,
-        savings);
-  PRINT(stdout, "Incompressible data:   {:>8s}\n", incompr_size_str);
-}
-
 static void
 finalize_at_exit(Context& ctx)
 {
@@ -2291,283 +2165,6 @@ do_cache_compilation(Context& ctx, const char* const* argv)
   return Statistic::cache_miss;
 }
 
-// The main program when not doing a compile.
-static int
-handle_main_options(int argc, const char* const* argv)
-{
-  enum longopts {
-    CHECKSUM_FILE,
-    CONFIG_PATH,
-    DUMP_MANIFEST,
-    DUMP_RESULT,
-    EVICT_OLDER_THAN,
-    EXTRACT_RESULT,
-    HASH_FILE,
-    PRINT_STATS,
-    SHOW_LOG_STATS,
-  };
-  static const struct option options[] = {
-    {"checksum-file", required_argument, nullptr, CHECKSUM_FILE},
-    {"cleanup", no_argument, nullptr, 'c'},
-    {"clear", no_argument, nullptr, 'C'},
-    {"config-path", required_argument, nullptr, CONFIG_PATH},
-    {"directory", required_argument, nullptr, 'd'},
-    {"dump-manifest", required_argument, nullptr, DUMP_MANIFEST},
-    {"dump-result", required_argument, nullptr, DUMP_RESULT},
-    {"evict-older-than", required_argument, nullptr, EVICT_OLDER_THAN},
-    {"extract-result", required_argument, nullptr, EXTRACT_RESULT},
-    {"get-config", required_argument, nullptr, 'k'},
-    {"hash-file", required_argument, nullptr, HASH_FILE},
-    {"help", no_argument, nullptr, 'h'},
-    {"max-files", required_argument, nullptr, 'F'},
-    {"max-size", required_argument, nullptr, 'M'},
-    {"print-stats", no_argument, nullptr, PRINT_STATS},
-    {"recompress", required_argument, nullptr, 'X'},
-    {"set-config", required_argument, nullptr, 'o'},
-    {"show-compression", no_argument, nullptr, 'x'},
-    {"show-config", no_argument, nullptr, 'p'},
-    {"show-log-stats", no_argument, nullptr, SHOW_LOG_STATS},
-    {"show-stats", no_argument, nullptr, 's'},
-    {"version", no_argument, nullptr, 'V'},
-    {"zero-stats", no_argument, nullptr, 'z'},
-    {nullptr, 0, nullptr, 0}};
-
-  int c;
-  while ((c = getopt_long(argc,
-                          const_cast<char* const*>(argv),
-                          "cCd:k:hF:M:po:sVxX:z",
-                          options,
-                          nullptr))
-         != -1) {
-    Context ctx;
-
-    std::string arg = optarg ? optarg : std::string();
-
-    switch (c) {
-    case CHECKSUM_FILE: {
-      Checksum checksum;
-      Fd fd(arg == "-" ? STDIN_FILENO : open(arg.c_str(), O_RDONLY));
-      Util::read_fd(*fd, [&checksum](const void* data, size_t size) {
-        checksum.update(data, size);
-      });
-      PRINT(stdout, "{:016x}\n", checksum.digest());
-      break;
-    }
-
-    case CONFIG_PATH:
-      Util::setenv("CCACHE_CONFIGPATH", arg);
-      break;
-
-    case DUMP_MANIFEST:
-      return Manifest::dump(arg, stdout) ? 0 : 1;
-
-    case DUMP_RESULT: {
-      ResultDumper result_dumper(stdout);
-      Result::Reader result_reader(arg);
-      auto error = result_reader.read(result_dumper);
-      if (error) {
-        PRINT(stderr, "Error: {}\n", *error);
-      }
-      return error ? EXIT_FAILURE : EXIT_SUCCESS;
-    }
-
-    case EVICT_OLDER_THAN: {
-      auto seconds = Util::parse_duration(arg);
-      ProgressBar progress_bar("Evicting...");
-      ctx.storage.primary.clean_old(
-        [&](double progress) { progress_bar.update(progress); }, seconds);
-      if (isatty(STDOUT_FILENO)) {
-        PRINT_RAW(stdout, "\n");
-      }
-      break;
-    }
-
-    case EXTRACT_RESULT: {
-      ResultExtractor result_extractor(".");
-      Result::Reader result_reader(arg);
-      auto error = result_reader.read(result_extractor);
-      if (error) {
-        PRINT(stderr, "Error: {}\n", *error);
-      }
-      return error ? EXIT_FAILURE : EXIT_SUCCESS;
-    }
-
-    case HASH_FILE: {
-      Hash hash;
-      if (arg == "-") {
-        hash.hash_fd(STDIN_FILENO);
-      } else {
-        hash.hash_file(arg);
-      }
-      PRINT(stdout, "{}\n", hash.digest().to_string());
-      break;
-    }
-
-    case PRINT_STATS: {
-      core::StatisticsCounters counters;
-      time_t last_updated;
-      std::tie(counters, last_updated) =
-        ctx.storage.primary.get_all_statistics();
-      core::Statistics statistics(counters);
-      PRINT_RAW(stdout, statistics.format_machine_readable(last_updated));
-      break;
-    }
-
-    case 'c': // --cleanup
-    {
-      ProgressBar progress_bar("Cleaning...");
-      ctx.storage.primary.clean_all(
-        [&](double progress) { progress_bar.update(progress); });
-      if (isatty(STDOUT_FILENO)) {
-        PRINT_RAW(stdout, "\n");
-      }
-      break;
-    }
-
-    case 'C': // --clear
-    {
-      ProgressBar progress_bar("Clearing...");
-      ctx.storage.primary.wipe_all(
-        [&](double progress) { progress_bar.update(progress); });
-      if (isatty(STDOUT_FILENO)) {
-        PRINT_RAW(stdout, "\n");
-      }
-#ifdef INODE_CACHE_SUPPORTED
-      ctx.inode_cache.drop();
-#endif
-      break;
-    }
-
-    case 'd': // --directory
-      Util::setenv("CCACHE_DIR", arg);
-      break;
-
-    case 'h': // --help
-      PRINT(stdout, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
-      exit(EXIT_SUCCESS);
-
-    case 'k': // --get-config
-      PRINT(stdout, "{}\n", ctx.config.get_string_value(arg));
-      break;
-
-    case 'F': { // --max-files
-      auto files = util::value_or_throw<core::Error>(util::parse_unsigned(arg));
-      ctx.config.set_value_in_file(
-        ctx.config.primary_config_path(), "max_files", arg);
-      if (files == 0) {
-        PRINT_RAW(stdout, "Unset cache file limit\n");
-      } else {
-        PRINT(stdout, "Set cache file limit to {}\n", files);
-      }
-      break;
-    }
-
-    case 'M': { // --max-size
-      uint64_t size = Util::parse_size(arg);
-      ctx.config.set_value_in_file(
-        ctx.config.primary_config_path(), "max_size", arg);
-      if (size == 0) {
-        PRINT_RAW(stdout, "Unset cache size limit\n");
-      } else {
-        PRINT(stdout,
-              "Set cache size limit to {}\n",
-              Util::format_human_readable_size(size));
-      }
-      break;
-    }
-
-    case 'o': { // --set-config
-      // Start searching for equal sign at position 1 to improve error message
-      // for the -o=K=V case (key "=K" and value "V").
-      size_t eq_pos = arg.find('=', 1);
-      if (eq_pos == std::string::npos) {
-        throw core::Error("missing equal sign in \"{}\"", arg);
-      }
-      std::string key = arg.substr(0, eq_pos);
-      std::string value = arg.substr(eq_pos + 1);
-      ctx.config.set_value_in_file(
-        ctx.config.primary_config_path(), key, value);
-      break;
-    }
-
-    case 'p': // --show-config
-      ctx.config.visit_items(configuration_printer);
-      break;
-
-    case SHOW_LOG_STATS: {
-      if (ctx.config.stats_log().empty()) {
-        throw core::Fatal("No stats log has been configured");
-      }
-      PRINT(stdout, "{:36}{}\n", "stats log", ctx.config.stats_log());
-      core::Statistics statistics(
-        core::StatsLog(ctx.config.stats_log()).read());
-      const auto timestamp =
-        Stat::stat(ctx.config.stats_log(), Stat::OnError::log).mtime();
-      PRINT_RAW(stdout, statistics.format_human_readable(timestamp, true));
-      break;
-    }
-
-    case 's': { // --show-stats
-      core::StatisticsCounters counters;
-      time_t last_updated;
-      std::tie(counters, last_updated) =
-        ctx.storage.primary.get_all_statistics();
-      core::Statistics statistics(counters);
-      PRINT_RAW(stdout, statistics.format_config_header(ctx.config));
-      PRINT_RAW(stdout, statistics.format_human_readable(last_updated, false));
-      PRINT_RAW(stdout, statistics.format_config_footer(ctx.config));
-      break;
-    }
-
-    case 'V': // --version
-      PRINT(VERSION_TEXT, CCACHE_NAME, CCACHE_VERSION, storage::get_features());
-      exit(EXIT_SUCCESS);
-
-    case 'x': // --show-compression
-    {
-      ProgressBar progress_bar("Scanning...");
-      const auto compression_statistics =
-        ctx.storage.primary.get_compression_statistics(
-          [&](double progress) { progress_bar.update(progress); });
-      if (isatty(STDOUT_FILENO)) {
-        PRINT_RAW(stdout, "\n\n");
-      }
-      print_compression_statistics(compression_statistics);
-      break;
-    }
-
-    case 'X': // --recompress
-    {
-      optional<int8_t> wanted_level;
-      if (arg == "uncompressed") {
-        wanted_level = nullopt;
-      } else {
-        wanted_level = util::value_or_throw<core::Error>(
-          util::parse_signed(arg, INT8_MIN, INT8_MAX, "compression level"));
-      }
-
-      ProgressBar progress_bar("Recompressing...");
-      ctx.storage.primary.recompress(
-        wanted_level, [&](double progress) { progress_bar.update(progress); });
-      break;
-    }
-
-    case 'z': // --zero-stats
-      ctx.storage.primary.zero_all_statistics();
-      PRINT_RAW(stdout, "Statistics zeroed\n");
-      break;
-
-    default:
-      PRINT(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
-      exit(EXIT_FAILURE);
-    }
-  }
-
-  return 0;
-}
-
-int ccache_main(int argc, const char* const* argv);
-
 int
 ccache_main(int argc, const char* const* argv)
 {
@@ -2576,13 +2173,13 @@ ccache_main(int argc, const char* const* argv)
     std::string program_name(Util::base_name(argv[0]));
     if (Util::same_program_name(program_name, CCACHE_NAME)) {
       if (argc < 2) {
-        PRINT(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
+        PRINT_RAW(stderr, core::get_usage_text());
         exit(EXIT_FAILURE);
       }
       // If the first argument isn't an option, then assume we are being
       // passed a compiler name and options.
       if (argv[1][0] == '-') {
-        return handle_main_options(argc, argv);
+        return core::process_main_options(argc, argv);
       }
     }
 
index 7b8f06ffa7faa298298768d6c7654e885d2dedc8..2b799fe8e12dceb04ce2996947f90ad4bc3a4029 100644 (file)
@@ -36,6 +36,8 @@ using FindExecutableFunction =
                             const std::string& name,
                             const std::string& exclude_name)>;
 
+int ccache_main(int argc, const char* const* argv);
+
 // Tested by unit tests.
 void find_compiler(Context& ctx,
                    const FindExecutableFunction& find_executable_function);
index c282613fb14e131984b954cf16ca6828642ab653..a00797514488de4eb523f7495d4bcf84c6bb8da1 100644 (file)
@@ -3,6 +3,7 @@ set(
   ${CMAKE_CURRENT_SOURCE_DIR}/Statistics.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/StatisticsCounters.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/StatsLog.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/mainoptions.cpp
 )
 
 target_sources(ccache_lib PRIVATE ${sources})
diff --git a/src/core/mainoptions.cpp b/src/core/mainoptions.cpp
new file mode 100644 (file)
index 0000000..6789125
--- /dev/null
@@ -0,0 +1,458 @@
+// Copyright (C) 2021 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// 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 3 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., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "mainoptions.hpp"
+
+#include <Checksum.hpp>
+#include <Config.hpp>
+#include <Fd.hpp>
+#include <Hash.hpp>
+#include <InodeCache.hpp>
+#include <Manifest.hpp>
+#include <ProgressBar.hpp>
+#include <ResultDumper.hpp>
+#include <ResultExtractor.hpp>
+#include <ccache.hpp>
+#include <core/Statistics.hpp>
+#include <core/StatsLog.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <storage/Storage.hpp>
+#include <storage/primary/PrimaryStorage.hpp>
+#include <util/expected.hpp>
+#include <util/string.hpp>
+
+#include <third_party/nonstd/optional.hpp>
+
+#include <fcntl.h>
+
+#include <string>
+
+#ifdef HAVE_UNISTD_H
+#  include <unistd.h>
+#endif
+
+#ifdef HAVE_GETOPT_LONG
+#  include <getopt.h>
+#elif defined(_WIN32)
+#  include <third_party/win32/getopt.h>
+#else
+extern "C" {
+#  include <third_party/getopt_long.h>
+}
+#endif
+
+namespace core {
+
+constexpr const char VERSION_TEXT[] =
+  R"({0} version {1}
+Features: {2}
+
+Copyright (C) 2002-2007 Andrew Tridgell
+Copyright (C) 2009-2021 Joel Rosdahl and other contributors
+
+See <https://ccache.dev/credits.html> for a complete list of contributors.
+
+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 3 of the License, or (at your option) any later
+version.
+)";
+
+constexpr const char USAGE_TEXT[] =
+  R"(Usage:
+    {0} [options]
+    {0} compiler [compiler options]
+    compiler [compiler options]          (via symbolic link)
+
+Common options:
+    -c, --cleanup              delete old files and recalculate size counters
+                               (normally not needed as this is done
+                               automatically)
+    -C, --clear                clear the cache completely (except configuration)
+        --config-path PATH     operate on configuration file PATH instead of the
+                               default
+    -d, --directory PATH       operate on cache directory PATH instead of the
+                               default
+        --evict-older-than AGE remove files older than AGE (unsigned integer
+                               with a d (days) or s (seconds) suffix)
+    -F, --max-files NUM        set maximum number of files in cache to NUM (use
+                               0 for no limit)
+    -M, --max-size SIZE        set maximum size of cache to SIZE (use 0 for no
+                               limit); available suffixes: k, M, G, T (decimal)
+                               and Ki, Mi, Gi, Ti (binary); default suffix: G
+    -X, --recompress LEVEL     recompress the cache to level LEVEL (integer or
+                               "uncompressed") using the Zstandard algorithm;
+                               see "Cache compression" in the manual for details
+    -o, --set-config KEY=VAL   set configuration item KEY to value VAL
+    -x, --show-compression     show compression statistics
+    -p, --show-config          show current configuration options in
+                               human-readable format
+        --show-log-stats       print statistics counters from the stats log
+                               in human-readable format
+    -s, --show-stats           show summary of configuration and statistics
+                               counters in human-readable format
+    -z, --zero-stats           zero statistics counters
+
+    -h, --help                 print this help text
+    -V, --version              print version and copyright information
+
+Options for scripting or debugging:
+        --checksum-file PATH   print the checksum (64 bit XXH3) of the file at
+                               PATH
+        --dump-manifest PATH   dump manifest file at PATH in text format
+        --dump-result PATH     dump result file at PATH in text format
+        --extract-result PATH  extract data stored in result file at PATH to the
+                               current working directory
+    -k, --get-config KEY       print the value of configuration key KEY
+        --hash-file PATH       print the hash (160 bit BLAKE3) of the file at
+                               PATH
+        --print-stats          print statistics counter IDs and corresponding
+                               values in machine-parsable format
+
+See also the manual on <https://ccache.dev/documentation.html>.
+)";
+
+static void
+configuration_printer(const std::string& key,
+                      const std::string& value,
+                      const std::string& origin)
+{
+  PRINT(stdout, "({}) {} = {}\n", origin, key, value);
+}
+
+static void
+print_compression_statistics(const storage::primary::CompressionStatistics& cs)
+{
+  const double ratio = cs.compr_size > 0
+                         ? static_cast<double>(cs.content_size) / cs.compr_size
+                         : 0.0;
+  const double savings = ratio > 0.0 ? 100.0 - (100.0 / ratio) : 0.0;
+
+  const std::string on_disk_size_str =
+    Util::format_human_readable_size(cs.on_disk_size);
+  const std::string cache_size_str =
+    Util::format_human_readable_size(cs.compr_size + cs.incompr_size);
+  const std::string compr_size_str =
+    Util::format_human_readable_size(cs.compr_size);
+  const std::string content_size_str =
+    Util::format_human_readable_size(cs.content_size);
+  const std::string incompr_size_str =
+    Util::format_human_readable_size(cs.incompr_size);
+
+  PRINT(stdout,
+        "Total data:            {:>8s} ({} disk blocks)\n",
+        cache_size_str,
+        on_disk_size_str);
+  PRINT(stdout,
+        "Compressed data:       {:>8s} ({:.1f}% of original size)\n",
+        compr_size_str,
+        100.0 - savings);
+  PRINT(stdout, "  - Original data:     {:>8s}\n", content_size_str);
+  PRINT(stdout,
+        "  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
+        ratio,
+        savings);
+  PRINT(stdout, "Incompressible data:   {:>8s}\n", incompr_size_str);
+}
+
+static std::string
+get_version_text()
+{
+  return FMT(
+    VERSION_TEXT, CCACHE_NAME, CCACHE_VERSION, storage::get_features());
+}
+
+std::string
+get_usage_text()
+{
+  return FMT(USAGE_TEXT, CCACHE_NAME);
+}
+
+int
+process_main_options(int argc, const char* const* argv)
+{
+  enum longopts {
+    CHECKSUM_FILE,
+    CONFIG_PATH,
+    DUMP_MANIFEST,
+    DUMP_RESULT,
+    EVICT_OLDER_THAN,
+    EXTRACT_RESULT,
+    HASH_FILE,
+    PRINT_STATS,
+    SHOW_LOG_STATS,
+  };
+  static const struct option options[] = {
+    {"checksum-file", required_argument, nullptr, CHECKSUM_FILE},
+    {"cleanup", no_argument, nullptr, 'c'},
+    {"clear", no_argument, nullptr, 'C'},
+    {"config-path", required_argument, nullptr, CONFIG_PATH},
+    {"directory", required_argument, nullptr, 'd'},
+    {"dump-manifest", required_argument, nullptr, DUMP_MANIFEST},
+    {"dump-result", required_argument, nullptr, DUMP_RESULT},
+    {"evict-older-than", required_argument, nullptr, EVICT_OLDER_THAN},
+    {"extract-result", required_argument, nullptr, EXTRACT_RESULT},
+    {"get-config", required_argument, nullptr, 'k'},
+    {"hash-file", required_argument, nullptr, HASH_FILE},
+    {"help", no_argument, nullptr, 'h'},
+    {"max-files", required_argument, nullptr, 'F'},
+    {"max-size", required_argument, nullptr, 'M'},
+    {"print-stats", no_argument, nullptr, PRINT_STATS},
+    {"recompress", required_argument, nullptr, 'X'},
+    {"set-config", required_argument, nullptr, 'o'},
+    {"show-compression", no_argument, nullptr, 'x'},
+    {"show-config", no_argument, nullptr, 'p'},
+    {"show-log-stats", no_argument, nullptr, SHOW_LOG_STATS},
+    {"show-stats", no_argument, nullptr, 's'},
+    {"version", no_argument, nullptr, 'V'},
+    {"zero-stats", no_argument, nullptr, 'z'},
+    {nullptr, 0, nullptr, 0}};
+
+  int c;
+  while ((c = getopt_long(argc,
+                          const_cast<char* const*>(argv),
+                          "cCd:k:hF:M:po:sVxX:z",
+                          options,
+                          nullptr))
+         != -1) {
+    Config config;
+    config.read();
+
+    std::string arg = optarg ? optarg : std::string();
+
+    switch (c) {
+    case CHECKSUM_FILE: {
+      Checksum checksum;
+      Fd fd(arg == "-" ? STDIN_FILENO : open(arg.c_str(), O_RDONLY));
+      Util::read_fd(*fd, [&checksum](const void* data, size_t size) {
+        checksum.update(data, size);
+      });
+      PRINT(stdout, "{:016x}\n", checksum.digest());
+      break;
+    }
+
+    case CONFIG_PATH:
+      Util::setenv("CCACHE_CONFIGPATH", arg);
+      break;
+
+    case DUMP_MANIFEST:
+      return Manifest::dump(arg, stdout) ? 0 : 1;
+
+    case DUMP_RESULT: {
+      ResultDumper result_dumper(stdout);
+      Result::Reader result_reader(arg);
+      auto error = result_reader.read(result_dumper);
+      if (error) {
+        PRINT(stderr, "Error: {}\n", *error);
+      }
+      return error ? EXIT_FAILURE : EXIT_SUCCESS;
+    }
+
+    case EVICT_OLDER_THAN: {
+      auto seconds = Util::parse_duration(arg);
+      ProgressBar progress_bar("Evicting...");
+      storage::primary::PrimaryStorage(config).clean_old(
+        [&](double progress) { progress_bar.update(progress); }, seconds);
+      if (isatty(STDOUT_FILENO)) {
+        PRINT_RAW(stdout, "\n");
+      }
+      break;
+    }
+
+    case EXTRACT_RESULT: {
+      ResultExtractor result_extractor(".");
+      Result::Reader result_reader(arg);
+      auto error = result_reader.read(result_extractor);
+      if (error) {
+        PRINT(stderr, "Error: {}\n", *error);
+      }
+      return error ? EXIT_FAILURE : EXIT_SUCCESS;
+    }
+
+    case HASH_FILE: {
+      Hash hash;
+      if (arg == "-") {
+        hash.hash_fd(STDIN_FILENO);
+      } else {
+        hash.hash_file(arg);
+      }
+      PRINT(stdout, "{}\n", hash.digest().to_string());
+      break;
+    }
+
+    case PRINT_STATS: {
+      StatisticsCounters counters;
+      time_t last_updated;
+      std::tie(counters, last_updated) =
+        storage::primary::PrimaryStorage(config).get_all_statistics();
+      Statistics statistics(counters);
+      PRINT_RAW(stdout, statistics.format_machine_readable(last_updated));
+      break;
+    }
+
+    case 'c': // --cleanup
+    {
+      ProgressBar progress_bar("Cleaning...");
+      storage::primary::PrimaryStorage(config).clean_all(
+        [&](double progress) { progress_bar.update(progress); });
+      if (isatty(STDOUT_FILENO)) {
+        PRINT_RAW(stdout, "\n");
+      }
+      break;
+    }
+
+    case 'C': // --clear
+    {
+      ProgressBar progress_bar("Clearing...");
+      storage::primary::PrimaryStorage(config).wipe_all(
+        [&](double progress) { progress_bar.update(progress); });
+      if (isatty(STDOUT_FILENO)) {
+        PRINT_RAW(stdout, "\n");
+      }
+#ifdef INODE_CACHE_SUPPORTED
+      InodeCache(config).drop();
+#endif
+      break;
+    }
+
+    case 'd': // --directory
+      Util::setenv("CCACHE_DIR", arg);
+      break;
+
+    case 'h': // --help
+      PRINT(stdout, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
+      exit(EXIT_SUCCESS);
+
+    case 'k': // --get-config
+      PRINT(stdout, "{}\n", config.get_string_value(arg));
+      break;
+
+    case 'F': { // --max-files
+      auto files = util::value_or_throw<Error>(util::parse_unsigned(arg));
+      config.set_value_in_file(config.primary_config_path(), "max_files", arg);
+      if (files == 0) {
+        PRINT_RAW(stdout, "Unset cache file limit\n");
+      } else {
+        PRINT(stdout, "Set cache file limit to {}\n", files);
+      }
+      break;
+    }
+
+    case 'M': { // --max-size
+      uint64_t size = Util::parse_size(arg);
+      config.set_value_in_file(config.primary_config_path(), "max_size", arg);
+      if (size == 0) {
+        PRINT_RAW(stdout, "Unset cache size limit\n");
+      } else {
+        PRINT(stdout,
+              "Set cache size limit to {}\n",
+              Util::format_human_readable_size(size));
+      }
+      break;
+    }
+
+    case 'o': { // --set-config
+      // Start searching for equal sign at position 1 to improve error message
+      // for the -o=K=V case (key "=K" and value "V").
+      size_t eq_pos = arg.find('=', 1);
+      if (eq_pos == std::string::npos) {
+        throw Error("missing equal sign in \"{}\"", arg);
+      }
+      std::string key = arg.substr(0, eq_pos);
+      std::string value = arg.substr(eq_pos + 1);
+      config.set_value_in_file(config.primary_config_path(), key, value);
+      break;
+    }
+
+    case 'p': // --show-config
+      config.visit_items(configuration_printer);
+      break;
+
+    case SHOW_LOG_STATS: {
+      if (config.stats_log().empty()) {
+        throw Fatal("No stats log has been configured");
+      }
+      PRINT(stdout, "{:36}{}\n", "stats log", config.stats_log());
+      Statistics statistics(StatsLog(config.stats_log()).read());
+      const auto timestamp =
+        Stat::stat(config.stats_log(), Stat::OnError::log).mtime();
+      PRINT_RAW(stdout, statistics.format_human_readable(timestamp, true));
+      break;
+    }
+
+    case 's': { // --show-stats
+      StatisticsCounters counters;
+      time_t last_updated;
+      std::tie(counters, last_updated) =
+        storage::primary::PrimaryStorage(config).get_all_statistics();
+      Statistics statistics(counters);
+      PRINT_RAW(stdout, statistics.format_config_header(config));
+      PRINT_RAW(stdout, statistics.format_human_readable(last_updated, false));
+      PRINT_RAW(stdout, statistics.format_config_footer(config));
+      break;
+    }
+
+    case 'V': // --version
+      PRINT_RAW(stdout, get_version_text());
+      exit(EXIT_SUCCESS);
+
+    case 'x': // --show-compression
+    {
+      ProgressBar progress_bar("Scanning...");
+      const auto compression_statistics =
+        storage::primary::PrimaryStorage(config).get_compression_statistics(
+          [&](double progress) { progress_bar.update(progress); });
+      if (isatty(STDOUT_FILENO)) {
+        PRINT_RAW(stdout, "\n\n");
+      }
+      print_compression_statistics(compression_statistics);
+      break;
+    }
+
+    case 'X': // --recompress
+    {
+      nonstd::optional<int8_t> wanted_level;
+      if (arg == "uncompressed") {
+        wanted_level = nonstd::nullopt;
+      } else {
+        wanted_level = util::value_or_throw<Error>(
+          util::parse_signed(arg, INT8_MIN, INT8_MAX, "compression level"));
+      }
+
+      ProgressBar progress_bar("Recompressing...");
+      storage::primary::PrimaryStorage(config).recompress(
+        wanted_level, [&](double progress) { progress_bar.update(progress); });
+      break;
+    }
+
+    case 'z': // --zero-stats
+      storage::primary::PrimaryStorage(config).zero_all_statistics();
+      PRINT_RAW(stdout, "Statistics zeroed\n");
+      break;
+
+    default:
+      PRINT(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  return 0;
+}
+
+} // namespace core
diff --git a/src/core/mainoptions.hpp b/src/core/mainoptions.hpp
new file mode 100644 (file)
index 0000000..e0f33e6
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// 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 3 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., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#pragma once
+
+#include <string>
+
+namespace core {
+
+// The main program when not doing a compile.
+int process_main_options(int argc, const char* const* argv);
+
+std::string get_usage_text();
+
+} // namespace core