build: make local Firebase configuration optional
This commit is contained in:
1 parent
16a25ea6fa
commit
e2d31322f2
8 files changed
+130
-96
No files matched your search
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string></string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string></string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>UNCONFIGURED</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string></string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string></string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false/>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false/>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<false/>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<false/>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<false/>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
+11
-11
@@ -2,9 +2,9 @@
|
||||
|
||||
The repository does not contain Firebase project configuration or RevenueCat SDK
|
||||
keys. Both app targets use `Services.xcconfig` for Debug and Release builds.
|
||||
Without local overrides, Firebase stays unconfigured, Crashlytics symbol upload
|
||||
is disabled, and RevenueCat purchases are unavailable. This does not bypass Pro
|
||||
feature access checks.
|
||||
Without a local Firebase plist or RevenueCat key, the corresponding service stays
|
||||
unconfigured. Crashlytics symbol upload is disabled by default. This does not
|
||||
bypass Pro feature access checks.
|
||||
|
||||
## Configure your own services
|
||||
|
||||
@@ -13,17 +13,17 @@ feature access checks.
|
||||
2. Set `REVENUECAT_API_KEY` to your RevenueCat public Apple SDK key and set
|
||||
`REVENUECAT_ENTITLEMENT_ID` to the entitlement configured in your project.
|
||||
Never put a private RevenueCat management key in an app build.
|
||||
3. Download your own Firebase Apple app's `GoogleService-Info.plist` to
|
||||
`Configuration/Firebase/GoogleService-Info.plist`. Its bundle ID must match the
|
||||
app being built. Uncomment `FIREBASE_CONFIG_PATH` in the local xcconfig.
|
||||
3. Create `Configuration/Firebase/` and download your own Firebase Apple app's
|
||||
`GoogleService-Info.plist` there. Its bundle ID must match the app being built.
|
||||
4. To upload Crashlytics symbols from Release builds, also uncomment
|
||||
`FIREBASE_UPLOAD_SYMBOLS = YES`. Runtime diagnostics still require user consent.
|
||||
|
||||
The local xcconfig and Firebase plist are ignored by Git. Keep the tracked
|
||||
Firebase example unconfigured: its intentionally mismatched bundle ID prevents
|
||||
SDK initialization. CI can provision the same ignored files from its secret
|
||||
store, or pass build-setting overrides to `xcodebuild`. Never publish those files
|
||||
or build logs containing their values.
|
||||
The local xcconfig and Firebase plist are ignored by Git. There is no Firebase
|
||||
example plist: the build copies the real file only when it exists, and removes a
|
||||
previously bundled copy when it does not. A missing plist also skips symbol upload.
|
||||
CI can provision the same ignored files from its secret store, or pass RevenueCat
|
||||
and symbol-upload build-setting overrides to `xcodebuild`. Never publish those
|
||||
files or build logs containing their values.
|
||||
|
||||
Firebase client configuration and RevenueCat public SDK keys are shipped in app
|
||||
binaries; they are not server-side secrets. Keeping them out of this repository
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
REVENUECAT_API_KEY =
|
||||
REVENUECAT_ENTITLEMENT_ID = termind_pro
|
||||
|
||||
// Download your own Firebase Apple app configuration before enabling these.
|
||||
// FIREBASE_CONFIG_PATH = Configuration/Firebase/GoogleService-Info.plist
|
||||
// Download your own Firebase Apple app configuration before enabling uploads.
|
||||
// FIREBASE_UPLOAD_SYMBOLS = YES
|
||||
@@ -1,7 +1,9 @@
|
||||
// Public builds have no service credentials. Local overrides are never tracked.
|
||||
REVENUECAT_API_KEY =
|
||||
REVENUECAT_ENTITLEMENT_ID = termind_pro
|
||||
FIREBASE_CONFIG_PATH = Configuration/Firebase/Example/GoogleService-Info.plist
|
||||
FIREBASE_UPLOAD_SYMBOLS = NO
|
||||
|
||||
// Let sandboxed scripts read the optional plist through their directory input.
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
|
||||
#include? "Services.local.xcconfig"
|
||||
@@ -0,0 +1,83 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import plistlib
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
SCRIPT = Path(__file__).resolve().parents[1] / "configure-firebase.sh"
|
||||
|
||||
|
||||
class FirebaseConfigurationTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary_directory = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temporary_directory.cleanup)
|
||||
self.root = Path(self.temporary_directory.name)
|
||||
self.source = self.root / "Configuration/Firebase/GoogleService-Info.plist"
|
||||
self.bundled = self.root / "Products/Termind.app/Contents/Resources/GoogleService-Info.plist"
|
||||
self.upload_marker = self.root / "upload-arguments"
|
||||
uploader = self.root / "SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"
|
||||
uploader.parent.mkdir(parents=True)
|
||||
uploader.write_text('#!/bin/sh\nprintf "%s\\n" "$@" > "$UPLOAD_MARKER"\n')
|
||||
uploader.chmod(0o700)
|
||||
self.environment = {
|
||||
**os.environ,
|
||||
"SRCROOT": str(self.root),
|
||||
"TARGET_BUILD_DIR": str(self.root / "Products"),
|
||||
"UNLOCALIZED_RESOURCES_FOLDER_PATH": "Termind.app/Contents/Resources",
|
||||
"BUILD_DIR": str(self.root / "Build/Products"),
|
||||
"UPLOAD_MARKER": str(self.upload_marker),
|
||||
}
|
||||
|
||||
def write_configuration(self):
|
||||
self.source.parent.mkdir(parents=True)
|
||||
self.source.write_bytes(plistlib.dumps({"BUNDLE_ID": "com.example.test"}))
|
||||
|
||||
def run_phase(self, configuration="Debug", upload="NO"):
|
||||
subprocess.run(
|
||||
["/bin/sh", str(SCRIPT)],
|
||||
env={
|
||||
**self.environment,
|
||||
"CONFIGURATION": configuration,
|
||||
"FIREBASE_UPLOAD_SYMBOLS": upload,
|
||||
},
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
def test_missing_configuration_neither_creates_a_plist_nor_uploads(self):
|
||||
self.run_phase(configuration="Release", upload="YES")
|
||||
self.assertFalse(self.bundled.exists())
|
||||
self.assertFalse(self.upload_marker.exists())
|
||||
|
||||
def test_local_configuration_is_copied_without_uploading_by_default(self):
|
||||
self.write_configuration()
|
||||
self.run_phase(configuration="Release")
|
||||
self.assertEqual(self.bundled.read_bytes(), self.source.read_bytes())
|
||||
self.assertFalse(self.upload_marker.exists())
|
||||
|
||||
def test_debug_never_uploads_even_when_enabled(self):
|
||||
self.write_configuration()
|
||||
self.run_phase(upload="YES")
|
||||
self.assertTrue(self.bundled.exists())
|
||||
self.assertFalse(self.upload_marker.exists())
|
||||
|
||||
def test_release_uploads_the_bundled_configuration_only_when_enabled(self):
|
||||
self.write_configuration()
|
||||
self.run_phase(configuration="Release", upload="YES")
|
||||
self.assertEqual(self.upload_marker.read_text().splitlines(), ["-gsp", str(self.bundled)])
|
||||
|
||||
def test_removing_local_configuration_clears_a_previous_build(self):
|
||||
self.write_configuration()
|
||||
self.run_phase()
|
||||
self.assertTrue(self.bundled.exists())
|
||||
self.source.unlink()
|
||||
self.run_phase(configuration="Release", upload="YES")
|
||||
self.assertFalse(self.bundled.exists())
|
||||
self.assertFalse(self.upload_marker.exists())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
source_plist="${SRCROOT}/Configuration/Firebase/GoogleService-Info.plist"
|
||||
bundled_plist="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleService-Info.plist"
|
||||
|
||||
if [ ! -f "${source_plist}" ]; then
|
||||
# An incremental build must not retain another developer's service configuration.
|
||||
rm -f "${bundled_plist}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "${bundled_plist}")"
|
||||
cp "${source_plist}" "${bundled_plist}"
|
||||
|
||||
if [ "${CONFIGURATION}" = "Release" ] && [ "${FIREBASE_UPLOAD_SYMBOLS:-NO}" = "YES" ]; then
|
||||
"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run" -gsp "${bundled_plist}"
|
||||
fi
|
||||
@@ -66,42 +66,6 @@ struct ArchitectureBoundaryTests {
|
||||
try assertNoImports(in: "../Frameworks/TermindInfrastructure/Commerce", forbidden: ["StoreKit"])
|
||||
}
|
||||
|
||||
@Test
|
||||
func serviceCredentialsStayOutsideSharedBuildSettings() throws {
|
||||
let repositoryRoot = sourceRoot.deletingLastPathComponent().standardizedFileURL
|
||||
let projectData = try Data(contentsOf: repositoryRoot.appending(path: "Termind.xcodeproj/project.pbxproj"))
|
||||
let project = try #require(
|
||||
PropertyListSerialization.propertyList(from: projectData, format: nil) as? [String: Any])
|
||||
let objects = try #require(project["objects"] as? [String: [String: Any]])
|
||||
let rootID = try #require(project["rootObject"] as? String)
|
||||
let configurationListID = try #require(objects[rootID]?["buildConfigurationList"] as? String)
|
||||
let configurationIDs = try #require(objects[configurationListID]?["buildConfigurations"] as? [String])
|
||||
for id in configurationIDs {
|
||||
let configuration = try #require(objects[id])
|
||||
let reference = try #require(configuration["baseConfigurationReference"] as? String)
|
||||
#expect(objects[reference]?["path"] as? String == "Configuration/Services.xcconfig")
|
||||
}
|
||||
for object in objects.values where object["isa"] as? String == "XCBuildConfiguration" {
|
||||
let settings = try #require(object["buildSettings"] as? [String: Any])
|
||||
#expect(settings["REVENUECAT_API_KEY"] == nil)
|
||||
}
|
||||
|
||||
let defaults = try String(
|
||||
contentsOf: repositoryRoot.appending(path: "Configuration/Services.xcconfig"), encoding: .utf8)
|
||||
#expect(defaults.contains("REVENUECAT_API_KEY =\n"))
|
||||
#expect(defaults.contains("FIREBASE_UPLOAD_SYMBOLS = NO\n"))
|
||||
#expect(defaults.contains("#include? \"Services.local.xcconfig\""))
|
||||
|
||||
let templateData = try Data(
|
||||
contentsOf: repositoryRoot.appending(path: "Configuration/Firebase/Example/GoogleService-Info.plist"))
|
||||
let template = try #require(
|
||||
PropertyListSerialization.propertyList(from: templateData, format: nil) as? [String: Any])
|
||||
for field in ["API_KEY", "GCM_SENDER_ID", "PROJECT_ID", "STORAGE_BUCKET", "GOOGLE_APP_ID"] {
|
||||
#expect((template[field] as? String)?.isEmpty == true)
|
||||
}
|
||||
#expect(template["BUNDLE_ID"] as? String == "UNCONFIGURED")
|
||||
}
|
||||
|
||||
@Test
|
||||
func diagnosisSDKStaysBehindInfrastructureAndStartsWithCollectionDisabled() throws {
|
||||
for module in ["TermindDomain", "TermindApplication", "TermindPresentation", "TermindPersistence"] {
|
||||
|
||||
@@ -100,8 +100,6 @@
|
||||
F1BA00000000000000000302 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = F1BA00000000000000000002 /* FirebaseCrashlytics */; };
|
||||
F1BA00000000000000000401 /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = F1BA00000000000000000001 /* FirebaseCore */; };
|
||||
F1BA00000000000000000402 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = F1BA00000000000000000002 /* FirebaseCrashlytics */; };
|
||||
F1BA0000000000000000A001 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F1BA00000000000000000020 /* GoogleService-Info.plist */; };
|
||||
F1BA0000000000000000A002 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F1BA00000000000000000020 /* GoogleService-Info.plist */; };
|
||||
F6ACDF3012081771F6C8F659 /* AgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = TRMD0000000000000010001B /* AgentKit */; };
|
||||
F996BFD771BC77E67AB91FFB /* TermindInfrastructure.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67B48098792E58C2955768F2 /* TermindInfrastructure.framework */; };
|
||||
FACB579B671967F922A33CD9 /* TermindApplication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 10111AE8F047FB59B318813A /* TermindApplication.framework */; };
|
||||
@@ -493,7 +491,6 @@
|
||||
DBE378B130478CFB00B16AA1 /* Termind.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; name = Termind.storekit; path = StoreKit/Termind.storekit; sourceTree = "<group>"; };
|
||||
F04700000000000000000011 /* TFMTests/SSHForwardingPumpTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFMTests/SSHForwardingPumpTests.swift; sourceTree = SOURCE_ROOT; };
|
||||
F04700000000000000000012 /* TFMTests/SSHForwardingWireTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFMTests/SSHForwardingWireTests.swift; sourceTree = SOURCE_ROOT; };
|
||||
F1BA00000000000000000020 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "$(FIREBASE_CONFIG_PATH)"; sourceTree = SOURCE_ROOT; };
|
||||
F1BA00000000000000000021 /* Services.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Configuration/Services.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||
TRMD00000000000000000001 /* Termind.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Termind.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
TRMD00000000000000020001 /* TFMTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TFMTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -858,7 +855,6 @@
|
||||
TRMD00000000000000000030 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F1BA00000000000000000020 /* GoogleService-Info.plist */,
|
||||
F1BA00000000000000000021 /* Services.xcconfig */,
|
||||
BENC00000000000000000010 /* Benchmarks/PortForwarding */,
|
||||
CB622EAF077B1253532F1E18 /* TFIWidgets */,
|
||||
@@ -908,7 +904,7 @@
|
||||
AC89B83FA8E4154383D9AC81 /* Sources */,
|
||||
7B09CFC5B32584951BD4C8F9 /* Frameworks */,
|
||||
8F0C5245592A0E2AFB85E50F /* Resources */,
|
||||
F1BA00000000000000003002 /* Upload Crashlytics Symbols */,
|
||||
F1BA00000000000000003002 /* Configure Firebase */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -1236,7 +1232,7 @@
|
||||
TRMD00000000000000000020 /* Frameworks */,
|
||||
TRMD00000000000000000080 /* Resources */,
|
||||
TRMD00000000000000000085 /* Embed MCP Bridge */,
|
||||
F1BA00000000000000002002 /* Upload Crashlytics Symbols */,
|
||||
F1BA00000000000000002002 /* Configure Firebase */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -1482,7 +1478,6 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F1BA0000000000000000A002 /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1511,7 +1506,6 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F1BA0000000000000000A001 /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1525,7 +1519,7 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
F1BA00000000000000002002 /* Upload Crashlytics Symbols */ = {
|
||||
F1BA00000000000000002002 /* Configure Firebase */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1537,18 +1531,20 @@
|
||||
"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)",
|
||||
"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Info.plist",
|
||||
"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)",
|
||||
"$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist",
|
||||
"$(SRCROOT)/Configuration/",
|
||||
"$(SRCROOT)/Scripts/configure-firebase.sh",
|
||||
"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)",
|
||||
"$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)",
|
||||
);
|
||||
name = "Upload Crashlytics Symbols";
|
||||
name = "Configure Firebase";
|
||||
outputPaths = (
|
||||
"$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if [ \"${CONFIGURATION}\" = \"Release\" ] && [ \"${FIREBASE_UPLOAD_SYMBOLS}\" = \"YES\" ]; then\n \"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\" -gsp \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleService-Info.plist\"\nfi\n";
|
||||
shellScript = "/bin/sh \"${SRCROOT}/Scripts/configure-firebase.sh\"\n";
|
||||
};
|
||||
F1BA00000000000000003002 /* Upload Crashlytics Symbols */ = {
|
||||
F1BA00000000000000003002 /* Configure Firebase */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1560,16 +1556,18 @@
|
||||
"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)",
|
||||
"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Info.plist",
|
||||
"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)",
|
||||
"$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist",
|
||||
"$(SRCROOT)/Configuration/",
|
||||
"$(SRCROOT)/Scripts/configure-firebase.sh",
|
||||
"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)",
|
||||
"$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)",
|
||||
);
|
||||
name = "Upload Crashlytics Symbols";
|
||||
name = "Configure Firebase";
|
||||
outputPaths = (
|
||||
"$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if [ \"${CONFIGURATION}\" = \"Release\" ] && [ \"${FIREBASE_UPLOAD_SYMBOLS}\" = \"YES\" ]; then\n \"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\" -gsp \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleService-Info.plist\"\nfi\n";
|
||||
shellScript = "/bin/sh \"${SRCROOT}/Scripts/configure-firebase.sh\"\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
|
||||
Reference in new issue
Block a user