Răsfoiți Sursa

Enable Snappy compression support in LevelDb in cmake builds (#9596)

Denver Coneybeare 3 ani în urmă
părinte
comite
c0df0d4ab1

+ 6 - 1
CMakeLists.txt

@@ -72,7 +72,6 @@ option(
   ON
 )
 
-
 list(INSERT CMAKE_MODULE_PATH 0 ${PROJECT_SOURCE_DIR}/cmake)
 include(compiler_setup)
 include(sanitizer_options)
@@ -234,6 +233,12 @@ if(NOT ZLIB_FOUND)
 endif()
 
 
+# Snappy
+set(SNAPPY_BUILD_TESTS OFF CACHE BOOL "Firestore disabled")
+set(SNAPPY_BUILD_BENCHMARKS OFF CACHE BOOL "Firestore disabled")
+add_external_subdirectory(snappy)
+firebase_ios_add_alias(Snappy::Snappy snappy)
+
 # LevelDB
 set(LEVELDB_BUILD_TESTS OFF CACHE BOOL "Firestore disabled")
 set(LEVELDB_BUILD_BENCHMARKS OFF CACHE BOOL "Firestore disabled")

+ 14 - 0
Firestore/Example/Firestore.xcodeproj/project.pbxproj

@@ -46,6 +46,7 @@
 		06A3926F89C847846BE4D6BE /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; };
 		06BCEB9C65DFAA142F3D3F0B /* view_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = A5466E7809AD2871FFDE6C76 /* view_testing.cc */; };
 		072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; };
+		077292C9797D97D3851F15CE /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; };
 		0794FACCB1C0C4881A76C28D /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; };
 		079E63E270F3EFCA175D2705 /* cc_compilation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */; };
 		07A64E6C4EB700E3AF3FD496 /* document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908320322E4D00CC290A /* document_test.cc */; };
@@ -735,6 +736,7 @@
 		7B8D7BAC1A075DB773230505 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; };
 		7BCC5973C4F4FCC272150E31 /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; };
 		7BCF050BA04537B0E7D44730 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; };
+		7C1DC1B44729381126D083AE /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; };
 		7C5E017689012489AAB7718D /* CodableGeoPointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */; };
 		7C7BA1DB0B66EB899A928283 /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; };
 		7D25D41B013BB70ADE526055 /* target_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 526D755F65AC676234F57125 /* target_test.cc */; };
@@ -762,6 +764,7 @@
 		81AF02881A8D23D02FC202F6 /* bundle_loader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A853C81A6A5A51C9D0389EDA /* bundle_loader_test.cc */; };
 		81B23D2D4E061074958AF12F /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; };
 		81D1B1D2B66BD8310AC5707F /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; };
+		82228CD6CE4A7A9254F8E82D /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; };
 		822E5D5EC4955393DF26BC5C /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; };
 		82E3634FCF4A882948B81839 /* FIRQueryUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = FF73B39D04D1760190E6B84A /* FIRQueryUnitTests.mm */; };
 		8342277EB0553492B6668877 /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; };
@@ -851,6 +854,7 @@
 		97729B53698C0E52EB165003 /* field_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E8551D6C6FB0B1BACE9E5BAD /* field_filter_test.cc */; };
 		9774A6C2AA02A12D80B34C3C /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; };
 		9783FAEA4CF758E8C4C2D76E /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; };
+		978D9EFDC56CC2E1FA468712 /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; };
 		9860F493EBF43AF5AC0A88BD /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8FA60B08D59FEA0D6751E87F /* empty_credentials_provider_test.cc */; };
 		98708140787A9465D883EEC9 /* leveldb_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */; };
 		98FE82875A899A40A98AAC22 /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; };
@@ -1066,6 +1070,7 @@
 		C4055D868A38221B332CD03D /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; };
 		C426C6E424FB2199F5C2C5BC /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D821C2DDC800EFB9CC /* document.pb.cc */; };
 		C43A555928CB0441096F82D2 /* FIRDocumentReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E049202154AA00B64F25 /* FIRDocumentReferenceTests.mm */; };
+		C4548D8C790387C8E64F0FC4 /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; };
 		C482E724F4B10968417C3F78 /* Pods_Firestore_FuzzTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */; };
 		C4C7A8D11DC394EF81B7B1FA /* filesystem_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA02DA2FCD0001CFC6EB08DA /* filesystem_testing.cc */; };
 		C524026444E83EEBC1773650 /* objc_type_traits_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A0CF41BA5AED6049B0BEB2C /* objc_type_traits_apple_test.mm */; };
@@ -1225,6 +1230,7 @@
 		EA38690795FBAA182A9AA63E /* FIRDatabaseTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06C202154D500B64F25 /* FIRDatabaseTests.mm */; };
 		EA46611779C3EEF12822508C /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; };
 		EAA1962BFBA0EBFBA53B343F /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; };
+		EAC0914B6DCC53008483AEE3 /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; };
 		EADD28A7859FBB9BE4D913B0 /* memory_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1CA9800A53669EFBFFB824E3 /* memory_remote_document_cache_test.cc */; };
 		EB04FE18E5794FEC187A09E3 /* FSTMemorySpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */; };
 		EB2137E6FBB0DDE2DF80E3D0 /* target_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 526D755F65AC676234F57125 /* target_test.cc */; };
@@ -1701,6 +1707,7 @@
 		D5B2593BCB52957D62F1C9D3 /* perf_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = perf_spec_test.json; sourceTree = "<group>"; };
 		D5B25E7E7D6873CBA4571841 /* FIRNumericTransformTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRNumericTransformTests.mm; sourceTree = "<group>"; };
 		D7DF4A6F740086A2D8C0E28E /* Pods_Firestore_Tests_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = leveldb_snappy_test.cc; sourceTree = "<group>"; };
 		DAFF0CF521E64AC30062958F /* Firestore_Example_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Firestore_Example_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		DAFF0CF721E64AC30062958F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
 		DAFF0CF821E64AC30062958F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
@@ -2074,6 +2081,7 @@
 				5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */,
 				75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */,
 				0840319686A223CC4AD3FAB1 /* leveldb_remote_document_cache_test.cc */,
+				D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */,
 				E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */,
 				88CF09277CFA45EE1273E3BA /* leveldb_transaction_test.cc */,
 				332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */,
@@ -3612,6 +3620,7 @@
 				1D7919CD2A05C15803F5FE05 /* leveldb_mutation_queue_test.cc in Sources */,
 				23EFC681986488B033C2B318 /* leveldb_opener_test.cc in Sources */,
 				F10A3E4E164A5458DFF7EDE6 /* leveldb_remote_document_cache_test.cc in Sources */,
+				7C1DC1B44729381126D083AE /* leveldb_snappy_test.cc in Sources */,
 				7D40C8EB7755138F85920637 /* leveldb_target_cache_test.cc in Sources */,
 				B46E778F9E40864B5D2B2F1C /* leveldb_transaction_test.cc in Sources */,
 				66FAB8EAC012A3822BD4D0C9 /* leveldb_util_test.cc in Sources */,
@@ -3811,6 +3820,7 @@
 				1145D70555D8CDC75183A88C /* leveldb_mutation_queue_test.cc in Sources */,
 				1DCA68BB2EF7A9144B35411F /* leveldb_opener_test.cc in Sources */,
 				CD1E2F356FC71D7E74FCD26C /* leveldb_remote_document_cache_test.cc in Sources */,
+				077292C9797D97D3851F15CE /* leveldb_snappy_test.cc in Sources */,
 				06485D6DA8F64757D72636E1 /* leveldb_target_cache_test.cc in Sources */,
 				EC62F9E29CE3598881908FB8 /* leveldb_transaction_test.cc in Sources */,
 				7A3BE0ED54933C234FDE23D1 /* leveldb_util_test.cc in Sources */,
@@ -4024,6 +4034,7 @@
 				FE701C2D739A5371BCBD62B9 /* leveldb_mutation_queue_test.cc in Sources */,
 				98FE82875A899A40A98AAC22 /* leveldb_opener_test.cc in Sources */,
 				79D86DD18BB54D2D69DC457F /* leveldb_remote_document_cache_test.cc in Sources */,
+				82228CD6CE4A7A9254F8E82D /* leveldb_snappy_test.cc in Sources */,
 				6C388B2D0967088758FF2425 /* leveldb_target_cache_test.cc in Sources */,
 				D4572060A0FD4D448470D329 /* leveldb_transaction_test.cc in Sources */,
 				3ABF84FC618016CA6E1D3C03 /* leveldb_util_test.cc in Sources */,
@@ -4237,6 +4248,7 @@
 				A478FDD7C3F48FBFDDA7D8F5 /* leveldb_mutation_queue_test.cc in Sources */,
 				A06FBB7367CDD496887B86F8 /* leveldb_opener_test.cc in Sources */,
 				A27096F764227BC73526FED3 /* leveldb_remote_document_cache_test.cc in Sources */,
+				EAC0914B6DCC53008483AEE3 /* leveldb_snappy_test.cc in Sources */,
 				D04CBBEDB8DC16D8C201AC49 /* leveldb_target_cache_test.cc in Sources */,
 				29243A4BBB2E2B1530A62C59 /* leveldb_transaction_test.cc in Sources */,
 				08FA4102AD14452E9587A1F2 /* leveldb_util_test.cc in Sources */,
@@ -4446,6 +4458,7 @@
 				98708140787A9465D883EEC9 /* leveldb_mutation_queue_test.cc in Sources */,
 				8342277EB0553492B6668877 /* leveldb_opener_test.cc in Sources */,
 				8077722A6BB175D3108CDC55 /* leveldb_remote_document_cache_test.cc in Sources */,
+				C4548D8C790387C8E64F0FC4 /* leveldb_snappy_test.cc in Sources */,
 				284A5280F868B2B4B5A1C848 /* leveldb_target_cache_test.cc in Sources */,
 				35DB74DFB2F174865BCCC264 /* leveldb_transaction_test.cc in Sources */,
 				BEE0294A23AB993E5DE0E946 /* leveldb_util_test.cc in Sources */,
@@ -4678,6 +4691,7 @@
 				4FAD8823DC37B9CA24379E85 /* leveldb_mutation_queue_test.cc in Sources */,
 				4562CDD90F5FF0491F07C5DA /* leveldb_opener_test.cc in Sources */,
 				EE6DBFB0874A50578CE97A7F /* leveldb_remote_document_cache_test.cc in Sources */,
+				978D9EFDC56CC2E1FA468712 /* leveldb_snappy_test.cc in Sources */,
 				6380CACCF96A9B26900983DC /* leveldb_target_cache_test.cc in Sources */,
 				DDD219222EEE13E3F9F2C703 /* leveldb_transaction_test.cc in Sources */,
 				BC549E3F3F119D80741D8612 /* leveldb_util_test.cc in Sources */,

+ 295 - 0
Firestore/core/test/unit/local/leveldb_snappy_test.cc

@@ -0,0 +1,295 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <array>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "Firestore/core/src/local/leveldb_util.h"
+#include "Firestore/core/src/util/filesystem.h"
+#include "Firestore/core/src/util/path.h"
+
+#include "gtest/gtest.h"
+#include "leveldb/db.h"
+
+namespace {
+
+using ::firebase::firestore::local::ConvertStatus;
+using ::firebase::firestore::util::Filesystem;
+using ::firebase::firestore::util::Path;
+
+// Creates a LevelDb database that uses Snappy compression for at least one of
+// its blocks. Attempting to iterate over this database using a LevelDb library
+// that does not have Snappy compression compiled in will return a failed status
+// with reason "corruption".
+Path CreateLevelDbDatabaseThatUsesSnappyCompression();
+
+// Creates and opens a LevelDb database that contains at least one block that
+// is compressed with Snappy compression, then iterates over it, invoking the
+// given callback with the status at each point in the iteration. Once the
+// callback is invoked with a `status` where `status.ok()` is not true, then
+// iteration will stop and the callback will not be invoked again.
+void IterateOverLevelDbDatabaseThatUsesSnappyCompression(
+    std::function<void(const leveldb::Status&)>);
+
+#if FIREBASE_TESTS_BUILT_BY_CMAKE
+
+// Ensure that LevelDb is compiled with Snappy compression support.
+// See https://github.com/firebase/firebase-ios-sdk/pull/9596 for details.
+TEST(LevelDbSnappy, LevelDbSupportsSnappy) {
+  IterateOverLevelDbDatabaseThatUsesSnappyCompression(
+      [](const leveldb::Status& status) {
+        ASSERT_TRUE(status.ok()) << ConvertStatus(status);
+      });
+}
+
+#else  // FIREBASE_TESTS_BUILT_BY_CMAKE
+
+// Ensure that LevelDb is NOT compiled with Snappy compression support.
+TEST(LevelDbSnappy, LevelDbDoesNotSupportSnappy) {
+  bool got_failed_status = false;
+  IterateOverLevelDbDatabaseThatUsesSnappyCompression(
+      [&](const leveldb::Status& status) {
+        if (!status.ok()) {
+          got_failed_status = true;
+          ASSERT_TRUE(status.IsCorruption()) << ConvertStatus(status);
+        }
+      });
+
+  if (!HasFailure()) {
+    ASSERT_TRUE(got_failed_status)
+        << "Reading a Snappy-compressed LevelDb database was successful; "
+           "however, it should NOT have been successful "
+           "since Snappy support is expected to NOT be available.";
+  }
+}
+
+#endif  // FIREBASE_TESTS_BUILT_BY_CMAKE
+
+void IterateOverLevelDbDatabaseThatUsesSnappyCompression(
+    std::function<void(const leveldb::Status&)> callback) {
+  std::unique_ptr<leveldb::DB> db;
+  {
+    Path leveldb_path = CreateLevelDbDatabaseThatUsesSnappyCompression();
+    if (leveldb_path.empty()) {
+      return;
+    }
+
+    leveldb::Options options;
+    options.create_if_missing = false;
+
+    leveldb::DB* db_ptr;
+    leveldb::Status status =
+        leveldb::DB::Open(options, leveldb_path.ToUtf8String(), &db_ptr);
+
+    ASSERT_TRUE(status.ok())
+        << "Opening LevelDb database " << leveldb_path.ToUtf8String()
+        << " failed: " << ConvertStatus(status);
+
+    db.reset(db_ptr);
+  }
+
+  std::unique_ptr<leveldb::Iterator> it(
+      db->NewIterator(leveldb::ReadOptions()));
+  for (it->SeekToFirst(); it->Valid(); it->Next()) {
+    callback(it->status());
+    if (!it->status().ok()) {
+      return;
+    }
+  }
+
+  // Invoke the callback on the final status.
+  callback(it->status());
+}
+
+template <typename T>
+void WriteFile(const Path& dir,
+               const std::string& file_name,
+               const T& data_array) {
+  Filesystem* fs = Filesystem::Default();
+  {
+    auto status = fs->RecursivelyCreateDir(dir);
+    if (!status.ok()) {
+      FAIL() << "Creating directory failed: " << dir.ToUtf8String() << " ("
+             << status.error_message() << ")";
+    }
+  }
+
+  Path file = dir.AppendUtf8(file_name);
+  std::ofstream out_file(file.native_value(), std::ios::binary);
+  if (!out_file) {
+    FAIL() << "Unable to open file for writing: " << file.ToUtf8String();
+  }
+
+  out_file.write(reinterpret_cast<const char*>(data_array.data()),
+                 data_array.size());
+  out_file.close();
+  if (!out_file) {
+    FAIL() << "Writing to file failed: " << file.ToUtf8String();
+  }
+}
+
+const std::array<unsigned char, 0x00000165> LevelDbSnappyFile_000005_ldb{
+    0x84, 0x03, 0x80, 0x00, 0x42, 0x00, 0x85, 0x71, 0x75, 0x65, 0x72, 0x79,
+    0x5F, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x01, 0x8B, 0x43, 0x6F,
+    0x6C, 0x41, 0x2F, 0x44, 0x6F, 0x63, 0x41, 0x2F, 0x43, 0x6F, 0x6C, 0x42,
+    0x01, 0x0A, 0x68, 0x42, 0x7C, 0x66, 0x3A, 0x7C, 0x6F, 0x62, 0x3A, 0x5F,
+    0x5F, 0x6E, 0x61, 0x6D, 0x65, 0x5F, 0x5F, 0x61, 0x73, 0x63, 0x00, 0x01,
+    0x8C, 0x82, 0x80, 0x01, 0x07, 0x00, 0x05, 0x01, 0x08, 0x01, 0x13, 0x50,
+    0x11, 0x3E, 0x01, 0x16, 0x00, 0x0A, 0x05, 0x15, 0xF0, 0x3C, 0x00, 0x08,
+    0x02, 0x20, 0x05, 0x32, 0x4A, 0x12, 0x48, 0x70, 0x72, 0x6F, 0x6A, 0x65,
+    0x63, 0x74, 0x73, 0x2F, 0x54, 0x65, 0x73, 0x74, 0x54, 0x65, 0x72, 0x6D,
+    0x69, 0x6E, 0x61, 0x74, 0x65, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61,
+    0x73, 0x65, 0x73, 0x2F, 0x28, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74,
+    0x29, 0x2F, 0x64, 0x6F, 0x63, 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x73, 0x01,
+    0x7B, 0x3E, 0x85, 0x00, 0x0C, 0x0D, 0x07, 0x50, 0x08, 0x15, 0x5A, 0x00,
+    0x03, 0xFE, 0x5A, 0x00, 0x2E, 0x5A, 0x00, 0x38, 0x07, 0x12, 0x06, 0x5F,
+    0x67, 0x6C, 0x6F, 0x62, 0x61, 0x6C, 0x00, 0x01, 0x80, 0x01, 0x0B, 0x11,
+    0x65, 0x1C, 0x10, 0x05, 0x20, 0x01, 0x12, 0x07, 0x06, 0x09, 0x15, 0x10,
+    0x00, 0x03, 0x01, 0x10, 0x04, 0x00, 0x01, 0x09, 0x10, 0x24, 0x01, 0x12,
+    0x01, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x01, 0x35, 0x00, 0x06,
+    0x09, 0x15, 0x10, 0x37, 0x0C, 0x07, 0x01, 0x05, 0x09, 0x0B, 0x10, 0x36,
+    0x0C, 0x07, 0x01, 0x04, 0x09, 0x0B, 0x10, 0x35, 0x0C, 0x07, 0x01, 0x03,
+    0x09, 0x0B, 0x4C, 0x34, 0x0C, 0x07, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+    0x2C, 0x6E, 0xE0, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0xC0, 0xF2, 0xA1, 0xB0, 0x00, 0x09, 0x03, 0x86, 0x01, 0xFF, 0xFF,
+    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x87, 0x02, 0x00, 0x00, 0x00, 0x00,
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x58, 0xC2, 0x94, 0x06, 0x8C, 0x02, 0x08,
+    0x99, 0x02, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x57, 0xFB, 0x80, 0x8B, 0x24, 0x75, 0x47, 0xDB,
+};
+const std::array<unsigned char, 0x000000E8> LevelDbSnappyFile_000017_ldb{
+    0x00, 0x14, 0x50, 0x85, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x01,
+    0x8C, 0x82, 0x80, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
+    0x02, 0x20, 0x0A, 0x32, 0x4A, 0x12, 0x48, 0x70, 0x72, 0x6F, 0x6A, 0x65,
+    0x63, 0x74, 0x73, 0x2F, 0x54, 0x65, 0x73, 0x74, 0x54, 0x65, 0x72, 0x6D,
+    0x69, 0x6E, 0x61, 0x74, 0x65, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61,
+    0x73, 0x65, 0x73, 0x2F, 0x28, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74,
+    0x29, 0x2F, 0x64, 0x6F, 0x63, 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x73, 0x2F,
+    0x43, 0x6F, 0x6C, 0x41, 0x2F, 0x44, 0x6F, 0x63, 0x41, 0x2F, 0x43, 0x6F,
+    0x6C, 0x42, 0x2F, 0x44, 0x6F, 0x63, 0x42, 0x07, 0x12, 0x06, 0x5F, 0x67,
+    0x6C, 0x6F, 0x62, 0x61, 0x6C, 0x00, 0x01, 0x80, 0x01, 0x0D, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x10, 0x0A, 0x20, 0x01, 0x00, 0x00,
+    0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xCD, 0xE0, 0x39, 0x00,
+    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF2, 0xA1, 0xB0,
+    0x00, 0x09, 0x03, 0x86, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+    0x00, 0x8A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+    0xE4, 0xA7, 0x7E, 0x74, 0x8F, 0x01, 0x08, 0x9C, 0x01, 0x17, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xFB, 0x80, 0x8B,
+    0x24, 0x75, 0x47, 0xDB,
+};
+const std::array<unsigned char, 0x00000000> LevelDbSnappyFile_000085_ldb{};
+const std::array<unsigned char, 0x00000010> LevelDbSnappyFile_CURRENT{
+    0x4D, 0x41, 0x4E, 0x49, 0x46, 0x45, 0x53, 0x54,
+    0x2D, 0x30, 0x30, 0x30, 0x30, 0x38, 0x34, 0x0A,
+};
+const std::array<unsigned char, 0x000000B5> LevelDbSnappyFile_LOG_old{
+    0x32, 0x30, 0x32, 0x32, 0x2F, 0x30, 0x34, 0x2F, 0x30, 0x34, 0x2D, 0x31,
+    0x31, 0x3A, 0x33, 0x39, 0x3A, 0x34, 0x36, 0x2E, 0x32, 0x35, 0x37, 0x32,
+    0x35, 0x31, 0x20, 0x30, 0x78, 0x37, 0x30, 0x30, 0x30, 0x30, 0x35, 0x33,
+    0x31, 0x34, 0x30, 0x30, 0x30, 0x20, 0x52, 0x65, 0x63, 0x6F, 0x76, 0x65,
+    0x72, 0x69, 0x6E, 0x67, 0x20, 0x6C, 0x6F, 0x67, 0x20, 0x23, 0x38, 0x31,
+    0x0A, 0x32, 0x30, 0x32, 0x32, 0x2F, 0x30, 0x34, 0x2F, 0x30, 0x34, 0x2D,
+    0x31, 0x31, 0x3A, 0x33, 0x39, 0x3A, 0x34, 0x36, 0x2E, 0x33, 0x30, 0x34,
+    0x35, 0x35, 0x32, 0x20, 0x30, 0x78, 0x37, 0x30, 0x30, 0x30, 0x30, 0x35,
+    0x33, 0x31, 0x34, 0x30, 0x30, 0x30, 0x20, 0x44, 0x65, 0x6C, 0x65, 0x74,
+    0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3D, 0x33, 0x20, 0x23, 0x38, 0x30,
+    0x0A, 0x32, 0x30, 0x32, 0x32, 0x2F, 0x30, 0x34, 0x2F, 0x30, 0x34, 0x2D,
+    0x31, 0x31, 0x3A, 0x33, 0x39, 0x3A, 0x34, 0x36, 0x2E, 0x33, 0x30, 0x35,
+    0x30, 0x36, 0x34, 0x20, 0x30, 0x78, 0x37, 0x30, 0x30, 0x30, 0x30, 0x35,
+    0x33, 0x31, 0x34, 0x30, 0x30, 0x30, 0x20, 0x44, 0x65, 0x6C, 0x65, 0x74,
+    0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3D, 0x30, 0x20, 0x23, 0x38, 0x31,
+    0x0A,
+};
+const std::array<unsigned char, 0x000000B5> LevelDbSnappyFile_LOG{
+    0x32, 0x30, 0x32, 0x32, 0x2F, 0x30, 0x34, 0x2F, 0x30, 0x34, 0x2D, 0x31,
+    0x31, 0x3A, 0x35, 0x36, 0x3A, 0x35, 0x36, 0x2E, 0x34, 0x39, 0x33, 0x31,
+    0x34, 0x32, 0x20, 0x30, 0x78, 0x37, 0x30, 0x30, 0x30, 0x30, 0x61, 0x32,
+    0x35, 0x34, 0x30, 0x30, 0x30, 0x20, 0x52, 0x65, 0x63, 0x6F, 0x76, 0x65,
+    0x72, 0x69, 0x6E, 0x67, 0x20, 0x6C, 0x6F, 0x67, 0x20, 0x23, 0x38, 0x33,
+    0x0A, 0x32, 0x30, 0x32, 0x32, 0x2F, 0x30, 0x34, 0x2F, 0x30, 0x34, 0x2D,
+    0x31, 0x31, 0x3A, 0x35, 0x36, 0x3A, 0x35, 0x36, 0x2E, 0x35, 0x33, 0x34,
+    0x37, 0x34, 0x35, 0x20, 0x30, 0x78, 0x37, 0x30, 0x30, 0x30, 0x30, 0x61,
+    0x32, 0x35, 0x34, 0x30, 0x30, 0x30, 0x20, 0x44, 0x65, 0x6C, 0x65, 0x74,
+    0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3D, 0x33, 0x20, 0x23, 0x38, 0x32,
+    0x0A, 0x32, 0x30, 0x32, 0x32, 0x2F, 0x30, 0x34, 0x2F, 0x30, 0x34, 0x2D,
+    0x31, 0x31, 0x3A, 0x35, 0x36, 0x3A, 0x35, 0x36, 0x2E, 0x35, 0x33, 0x35,
+    0x32, 0x34, 0x32, 0x20, 0x30, 0x78, 0x37, 0x30, 0x30, 0x30, 0x30, 0x61,
+    0x32, 0x35, 0x34, 0x30, 0x30, 0x30, 0x20, 0x44, 0x65, 0x6C, 0x65, 0x74,
+    0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3D, 0x30, 0x20, 0x23, 0x38, 0x33,
+    0x0A,
+};
+const std::array<unsigned char, 0x000000C2> LevelDbSnappyFile_MANIFEST_000084{
+    0x45, 0x63, 0x9F, 0xDD, 0xAC, 0x00, 0x01, 0x01, 0x1A, 0x6C, 0x65, 0x76,
+    0x65, 0x6C, 0x64, 0x62, 0x2E, 0x42, 0x79, 0x74, 0x65, 0x77, 0x69, 0x73,
+    0x65, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6F, 0x72, 0x07,
+    0x00, 0x05, 0xE5, 0x02, 0x42, 0x85, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5F,
+    0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x01, 0x8B, 0x43, 0x6F, 0x6C,
+    0x41, 0x2F, 0x44, 0x6F, 0x63, 0x41, 0x2F, 0x43, 0x6F, 0x6C, 0x42, 0x2F,
+    0x44, 0x6F, 0x63, 0x42, 0x7C, 0x66, 0x3A, 0x7C, 0x6F, 0x62, 0x3A, 0x5F,
+    0x5F, 0x6E, 0x61, 0x6D, 0x65, 0x5F, 0x5F, 0x61, 0x73, 0x63, 0x00, 0x01,
+    0x8C, 0x82, 0x80, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13,
+    0x85, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x01, 0x80, 0x01,
+    0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x11, 0xE8, 0x01,
+    0x14, 0x85, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x01, 0x8C, 0x82,
+    0x80, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x85, 0x74,
+    0x61, 0x72, 0x67, 0x65, 0x74, 0x5F, 0x67, 0x6C, 0x6F, 0x62, 0x61, 0x6C,
+    0x00, 0x01, 0x80, 0x01, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1,
+    0x03, 0xAC, 0xBA, 0x08, 0x00, 0x01, 0x02, 0x55, 0x09, 0x00, 0x03, 0x56,
+    0x04, 0x0D,
+};
+
+Path LevelDbDir() {
+  Filesystem* fs = Filesystem::Default();
+  Path dir = fs->TempDir().AppendUtf8("LevelDbSnappyTest");
+
+  // Delete the directory first to ensure isolation between runs.
+  auto status = fs->RecursivelyRemove(dir);
+  EXPECT_TRUE(status.ok()) << "Failed to clean up leveldb in directory "
+                           << dir.ToUtf8String() << ": " << status.ToString();
+  if (!status.ok()) {
+    return {};
+  }
+
+  return dir;
+}
+
+Path CreateLevelDbDatabaseThatUsesSnappyCompression() {
+  Path leveldb_dir = LevelDbDir();
+  if (leveldb_dir.empty()) {
+    return {};
+  }
+
+  WriteFile(leveldb_dir, "000005.ldb", LevelDbSnappyFile_000005_ldb);
+  WriteFile(leveldb_dir, "000017.ldb", LevelDbSnappyFile_000017_ldb);
+  WriteFile(leveldb_dir, "000085.ldb", LevelDbSnappyFile_000085_ldb);
+  WriteFile(leveldb_dir, "CURRENT", LevelDbSnappyFile_CURRENT);
+  WriteFile(leveldb_dir, "LOG.old", LevelDbSnappyFile_LOG_old);
+  WriteFile(leveldb_dir, "LOG", LevelDbSnappyFile_LOG);
+  WriteFile(leveldb_dir, "MANIFEST-000084", LevelDbSnappyFile_MANIFEST_000084);
+
+  return leveldb_dir;
+}
+
+}  // namespace

+ 8 - 0
cmake/cc_rules.cmake

@@ -116,6 +116,14 @@ function(firebase_ios_add_test target)
 
   firebase_ios_set_common_target_options(${target} ${flag_OPTIONS})
 
+  # Set a preprocessor define so that tests can distinguish between having been
+  # built by Xcode vs. cmake.
+  target_compile_definitions(
+    ${target}
+    PRIVATE
+    FIREBASE_TESTS_BUILT_BY_CMAKE
+  )
+
   target_link_libraries(${target} PRIVATE GTest::GTest GTest::Main)
 endfunction()
 

+ 1 - 0
cmake/external/CMakeLists.txt

@@ -35,6 +35,7 @@ include(c-ares)
 include(googletest)
 include(GoogleUtilities)
 include(grpc)
+include(snappy)
 include(leveldb)
 include(libfuzzer)
 include(nanopb)

+ 14 - 0
cmake/external/leveldb.cmake

@@ -14,15 +14,28 @@
 
 include(ExternalProject)
 
+include(python_setup)
+FirebaseSetupPythonInterpreter(
+  OUTVAR MY_PYTHON_EXECUTABLE
+  KEY LevelDbPatch
+)
+
 if(TARGET leveldb)
   return()
 endif()
 
 set(version 1.22)
 
+ExternalProject_Get_property(snappy SOURCE_DIR)
+set(snappy_source_dir "${SOURCE_DIR}")
+ExternalProject_Get_property(snappy BINARY_DIR)
+set(snappy_binary_dir "${BINARY_DIR}")
+
 ExternalProject_Add(
   leveldb
 
+  DEPENDS snappy
+
   DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR}
   DOWNLOAD_NAME leveldb-${version}.tar.gz
   URL https://github.com/google/leveldb/archive/${version}.tar.gz
@@ -34,6 +47,7 @@ ExternalProject_Add(
   BUILD_COMMAND     ""
   INSTALL_COMMAND   ""
   TEST_COMMAND      ""
+  PATCH_COMMAND     "${MY_PYTHON_EXECUTABLE}" ${CMAKE_CURRENT_LIST_DIR}/leveldb_patch.py --snappy-source-dir ${snappy_source_dir} --snappy-binary-dir ${snappy_binary_dir}
 
   HTTP_HEADER "${EXTERNAL_PROJECT_HTTP_HEADER}"
 )

+ 144 - 0
cmake/external/leveldb_patch.py

@@ -0,0 +1,144 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Modify the CMakeLists.txt from LevelDb to staticly link Snappy compression
+support.
+"""
+
+import argparse
+import dataclasses
+import os
+import pathlib
+from typing import Iterable, Sequence
+
+
+def main() -> None:
+  arg_parser = argparse.ArgumentParser()
+  arg_parser.add_argument("--snappy-source-dir", required=True)
+  arg_parser.add_argument("--snappy-binary-dir", required=True)
+  parsed_args = arg_parser.parse_args()
+  del arg_parser
+  snappy_source_dir = pathlib.Path(parsed_args.snappy_source_dir)
+  snappy_binary_dir = pathlib.Path(parsed_args.snappy_binary_dir)
+  del parsed_args
+
+  cmakelists_txt_file = pathlib.Path("CMakeLists.txt")
+  with cmakelists_txt_file.open("rt", encoding="utf8") as f:
+    lines = tuple(f)
+
+  patcher = CMakeListsPatcher(
+    lines,
+    os.path.abspath(__file__),
+    snappy_source_dir,
+    snappy_binary_dir,
+  )
+
+  patched_lines = tuple(patcher.patch())
+
+  with cmakelists_txt_file.open("wt", encoding="utf8") as f:
+    f.writelines(patched_lines)
+
+
+@dataclasses.dataclass(frozen=True)
+class LineComponents:
+  full: str
+  indent: str
+  line: str
+  eol: str
+
+
+class CMakeListsPatcher:
+
+  SNAPPY_DETECT_LINE = \
+    """check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)"""
+  SNAPPY_INCLUDE_LINE = \
+    "target_include_directories(leveldb"
+  SNAPPY_LINK_LINE = \
+    "target_link_libraries(leveldb snappy)"
+
+  def __init__(
+      self,
+      lines: Sequence[str],
+      script_name: str,
+      snappy_source_dir: pathlib.Path,
+      snappy_binary_dir: pathlib.Path) -> None:
+    self.i = 0
+    self.lines = lines
+    self.script_name = script_name
+    self.snappy_source_dir_str = snappy_source_dir.as_posix()
+    self.snappy_binary_dir_str = snappy_binary_dir.as_posix()
+
+  def patch(self) -> Iterable[str]:
+    while self.i < len(self.lines):
+      full_line = self.lines[self.i]
+      line = self._split_line(full_line)
+      self.i += 1
+
+      if line.line == self.SNAPPY_DETECT_LINE:
+        yield from self._on_snappy_detect_line(line)
+      elif line.line == self.SNAPPY_INCLUDE_LINE:
+        yield full_line
+        yield from self._on_leveldb_include_start()
+      elif line.line == self.SNAPPY_LINK_LINE:
+        yield from self._on_leveldb_snappy_link_line(line)
+      else:
+        yield full_line
+
+  def _begin_mod_line(self, mod_name: str) -> str:
+    return f"# BEGIN: {mod_name} modification by {self.script_name}"
+
+  def _end_mod_line(self, mod_name: str) -> str:
+    return f"# END: {mod_name} modification by {self.script_name}"
+
+  def _on_snappy_detect_line(self, line: LineComponents) -> Iterable[str]:
+    yield self._begin_mod_line("snappy_detect_line") + line.eol
+    yield line.indent + "# " + line.line + line.eol
+    yield line.indent + """set(HAVE_SNAPPY ON CACHE BOOL "")""" + line.eol
+    yield self._end_mod_line("snappy_detect_line") + line.eol
+
+  def _on_leveldb_include_start(self) -> Iterable[str]:
+    line1 = self._split_line(self.lines[self.i])
+    line2 = self._split_line(self.lines[self.i + 1])
+    begin_mod_line = self._begin_mod_line("leveldb_include_start")
+
+    if line1.line == begin_mod_line:
+      return
+
+    yield begin_mod_line + line1.eol
+    yield line1.indent + "PRIVATE" + line1.eol
+    yield line2.indent + self.snappy_source_dir_str + line2.eol
+    yield line2.indent + self.snappy_binary_dir_str + line2.eol
+    yield self._end_mod_line("leveldb_include_start") + line1.eol
+
+  def _on_leveldb_snappy_link_line(self, line: LineComponents) -> Iterable[str]:
+    yield self._begin_mod_line("leveldb_snappy_link_line") + line.eol
+    yield line.indent + "# " + line.line + line.eol
+    yield line.indent + f"target_link_libraries(leveldb Snappy::Snappy)" + line.eol
+    yield self._end_mod_line("leveldb_snappy_link_line") + line.eol
+
+  def _split_line(self, line: str) -> LineComponents:
+    line_rstripped = line.rstrip()
+    eol = line[len(line_rstripped):]
+    line_stripped = line_rstripped.strip()
+    indent = line_rstripped[:len(line_rstripped) - len(line_stripped)]
+    return LineComponents(full=line, indent=indent, line=line_stripped, eol=eol)
+
+
+class LeveDbPatchException(Exception):
+  pass
+
+
+if __name__ == "__main__":
+  main()

+ 328 - 0
cmake/external/leveldb_patch_test.py

@@ -0,0 +1,328 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import leveldb_patch
+import pathlib
+import unittest
+
+
+class CMakeListsPatcherTest(unittest.TestCase):
+
+  def setUp(self):
+    super().setUp()
+    self.sample_snappy_source_dir = pathlib.Path("a/b/snappy_source_dir")
+    self.sample_snappy_binary_dir = pathlib.Path("a/b/snappy_binary_dir")
+
+  def test_snappy_detect_line_is_commented_and_replaced(self):
+    lines = (
+      """check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)""",
+    )
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      "MyCoolScript",
+      self.sample_snappy_source_dir,
+      self.sample_snappy_binary_dir,
+    )
+
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "# BEGIN: snappy_detect_line modification by MyCoolScript",
+      """# check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)""",
+      """set(HAVE_SNAPPY ON CACHE BOOL "")""",
+      "# END: snappy_detect_line modification by MyCoolScript",
+    ])
+
+  def test_snappy_detect_line_has_indent_and_eol_preserved(self):
+    lines = (
+      """  check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)   \n""",
+    )
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      "MyCoolScript",
+      self.sample_snappy_source_dir,
+      self.sample_snappy_binary_dir,
+    )
+
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "# BEGIN: snappy_detect_line modification by MyCoolScript   \n",
+      """  # check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)   \n""",
+      """  set(HAVE_SNAPPY ON CACHE BOOL "")   \n""",
+      "# END: snappy_detect_line modification by MyCoolScript   \n",
+    ])
+
+  def test_snappy_detect_line_does_not_affect_surrounding_lines(self):
+    lines = (
+      "aaa",
+      "bbb",
+      """check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)""",
+      "ccc",
+      "ddd",
+    )
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      "MyCoolScript",
+      self.sample_snappy_source_dir,
+      self.sample_snappy_binary_dir,
+    )
+
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "aaa",
+      "bbb",
+      "# BEGIN: snappy_detect_line modification by MyCoolScript",
+      """# check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)""",
+      """set(HAVE_SNAPPY ON CACHE BOOL "")""",
+      "# END: snappy_detect_line modification by MyCoolScript",
+      "ccc",
+      "ddd",
+    ])
+
+  def test_snappy_include_is_amended(self):
+    lines = (
+      "target_include_directories(leveldb",
+      "PUBLIC",
+      "path1",
+      "path2",
+      ")",
+    )
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      script_name="MyCoolSript",
+      snappy_source_dir=pathlib.Path("a/b"),
+      snappy_binary_dir=pathlib.Path("c/d"),
+    )
+
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "target_include_directories(leveldb",
+      "# BEGIN: leveldb_include_start modification by MyCoolSript",
+      "PRIVATE",
+      "a/b",
+      "c/d",
+      "# END: leveldb_include_start modification by MyCoolSript",
+      "PUBLIC",
+      "path1",
+      "path2",
+      ")",
+    ])
+
+  def test_snappy_include_lines_adopt_indenting_and_eol_convention(self):
+    lines = (
+      "target_include_directories(leveldb\n",
+      "  PUBLIC   \n",
+      "      path1 \n",
+      "      path2 \n",
+      ")\n",
+    )
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      script_name="MyCoolSript",
+      snappy_source_dir=pathlib.Path("a/b"),
+      snappy_binary_dir=pathlib.Path("c/d"),
+    )
+
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "target_include_directories(leveldb\n",
+      "# BEGIN: leveldb_include_start modification by MyCoolSript   \n",
+      "  PRIVATE   \n",
+      "      a/b \n",
+      "      c/d \n",
+      "# END: leveldb_include_start modification by MyCoolSript   \n",
+      "  PUBLIC   \n",
+      "      path1 \n",
+      "      path2 \n",
+      ")\n",
+    ])
+
+  def test_snappy_include_line_does_not_affect_surrounding_lines(self):
+    lines = (
+      "aaa",
+      "bbb",
+      "target_include_directories(leveldb",
+      "PUBLIC",
+      "path1",
+      "path2",
+      ")",
+      "ccc",
+      "ddd",
+    )
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      script_name="MyCoolSript",
+      snappy_source_dir=pathlib.Path("a/b"),
+      snappy_binary_dir=pathlib.Path("c/d"),
+    )
+
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "aaa",
+      "bbb",
+      "target_include_directories(leveldb",
+      "# BEGIN: leveldb_include_start modification by MyCoolSript",
+      "PRIVATE",
+      "a/b",
+      "c/d",
+      "# END: leveldb_include_start modification by MyCoolSript",
+      "PUBLIC",
+      "path1",
+      "path2",
+      ")",
+      "ccc",
+      "ddd",
+    ])
+
+  def test_leveldb_snappy_link_line_is_commented_and_replaced(self):
+    lines = (
+      "target_link_libraries(leveldb snappy)",
+    )
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      script_name="MyCoolSript",
+      snappy_source_dir=pathlib.Path("a/b"),
+      snappy_binary_dir=pathlib.Path("c/d"),
+    )
+
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "# BEGIN: leveldb_snappy_link_line modification by MyCoolSript",
+      "# target_link_libraries(leveldb snappy)",
+      "target_link_libraries(leveldb Snappy::Snappy)",
+      "# END: leveldb_snappy_link_line modification by MyCoolSript",
+    ])
+
+  def test_leveldb_snappy_link_line_has_indent_and_eol_preserved(self):
+    lines = (
+      " target_link_libraries(leveldb snappy)   \n",
+    )
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      script_name="MyCoolSript",
+      snappy_source_dir=pathlib.Path("a/b"),
+      snappy_binary_dir=pathlib.Path("c/d"),
+    )
+
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "# BEGIN: leveldb_snappy_link_line modification by MyCoolSript   \n",
+      " # target_link_libraries(leveldb snappy)   \n",
+      " target_link_libraries(leveldb Snappy::Snappy)   \n",
+      "# END: leveldb_snappy_link_line modification by MyCoolSript   \n",
+    ])
+
+  def test_leveldb_snappy_link_line_does_not_affect_surrounding_lines(self):
+    lines = (
+      "aaa",
+      "bbb",
+      "target_link_libraries(leveldb snappy)",
+      "ccc",
+      "ddd",
+    )
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      script_name="MyCoolSript",
+      snappy_source_dir=pathlib.Path("a/b"),
+      snappy_binary_dir=pathlib.Path("c/d"),
+    )
+
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "aaa",
+      "bbb",
+      "# BEGIN: leveldb_snappy_link_line modification by MyCoolSript",
+      "# target_link_libraries(leveldb snappy)",
+      "target_link_libraries(leveldb Snappy::Snappy)",
+      "# END: leveldb_snappy_link_line modification by MyCoolSript",
+      "ccc",
+      "ddd",
+    ])
+
+  def test_all_patches_combined(self):
+    lines = (
+      """check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)""",
+      "target_include_directories(leveldb",
+      "PUBLIC",
+      "path1",
+      ")",
+      "target_link_libraries(leveldb snappy)",
+    )
+
+    patcher = leveldb_patch.CMakeListsPatcher(
+      lines,
+      script_name="MyCoolSript",
+      snappy_source_dir=pathlib.Path("a/b"),
+      snappy_binary_dir=pathlib.Path("c/d"),
+    )
+    patched_lines = tuple(patcher.patch())
+
+    self.assertSequenceEqual(patched_lines, [
+      "# BEGIN: snappy_detect_line modification by MyCoolSript",
+      """# check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)""",
+      """set(HAVE_SNAPPY ON CACHE BOOL "")""",
+      "# END: snappy_detect_line modification by MyCoolSript",
+      "target_include_directories(leveldb",
+      "# BEGIN: leveldb_include_start modification by MyCoolSript",
+      "PRIVATE",
+      "a/b",
+      "c/d",
+      "# END: leveldb_include_start modification by MyCoolSript",
+      "PUBLIC",
+      "path1",
+      ")",
+      "# BEGIN: leveldb_snappy_link_line modification by MyCoolSript",
+      "# target_link_libraries(leveldb snappy)",
+      "target_link_libraries(leveldb Snappy::Snappy)",
+      "# END: leveldb_snappy_link_line modification by MyCoolSript",
+    ])
+
+  def test_idempotence(self):
+    lines = (
+      """check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)\n""",
+      "target_include_directories(leveldb",
+      "PUBLIC",
+      "path1",
+      ")",
+      "target_link_libraries(leveldb snappy)",
+    )
+
+    patcher1 = leveldb_patch.CMakeListsPatcher(
+      lines,
+      script_name="MyCoolSript",
+      snappy_source_dir=pathlib.Path("a/b"),
+      snappy_binary_dir=pathlib.Path("c/d"),
+    )
+    patched_lines1 = tuple(patcher1.patch())
+    patcher2 = leveldb_patch.CMakeListsPatcher(
+      patched_lines1,
+      script_name="MyCoolSript",
+      snappy_source_dir=pathlib.Path("a/b"),
+      snappy_binary_dir=pathlib.Path("c/d"),
+    )
+    patched_lines2 = tuple(patcher2.patch())
+
+    self.assertSequenceEqual(patched_lines1, patched_lines2)
+
+
+if __name__ == "__main__":
+  unittest.main()

+ 40 - 0
cmake/external/snappy.cmake

@@ -0,0 +1,40 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+include(ExternalProject)
+
+if(TARGET snappy)
+  return()
+endif()
+
+set(version 1.1.9)
+
+ExternalProject_Add(
+  snappy
+
+  DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR}
+  DOWNLOAD_NAME snappy-${version}.tar.gz
+  URL https://github.com/google/snappy/archive/refs/tags/${version}.tar.gz
+  URL_HASH SHA256=75c1fbb3d618dd3a0483bff0e26d0a92b495bbe5059c8b4f1c962b478b6e06e7
+
+  PREFIX ${PROJECT_BINARY_DIR}
+
+  CONFIGURE_COMMAND ""
+  BUILD_COMMAND     ""
+  INSTALL_COMMAND   ""
+  TEST_COMMAND      ""
+  PATCH_COMMAND     patch -Np1 -i ${CMAKE_CURRENT_LIST_DIR}/snappy.patch
+
+  HTTP_HEADER "${EXTERNAL_PROJECT_HTTP_HEADER}"
+)

+ 12 - 0
cmake/external/snappy.patch

@@ -0,0 +1,12 @@
+diff -Naur snappy/snappy.cc snappy_patched/snappy.cc
+--- snappy/snappy.cc	2022-04-12 20:44:55.000000000 -0400
++++ snappy_patched/snappy.cc	2022-04-12 20:47:05.000000000 -0400
+@@ -1014,7 +1014,7 @@
+ }
+ 
+ SNAPPY_ATTRIBUTE_ALWAYS_INLINE
+-size_t AdvanceToNextTag(const uint8_t** ip_p, size_t* tag) {
++inline size_t AdvanceToNextTag(const uint8_t** ip_p, size_t* tag) {
+   const uint8_t*& ip = *ip_p;
+   // This section is crucial for the throughput of the decompression loop.
+   // The latency of an iteration is fundamentally constrained by the

+ 1 - 0
scripts/check_whitespace.sh

@@ -26,6 +26,7 @@ options=(
 )
 
 git grep "${options[@]}" -- \
+    ':(exclude)cmake/external/snappy.patch' \
     ':(exclude)Crashlytics/ProtoSupport' \
     ':(exclude)Crashlytics/UnitTests/Data' \
     ':(exclude)Firebase/CoreDiagnostics/ProtoSupport' \