mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
249 lines
9.4 KiB
Swift
Executable File
249 lines
9.4 KiB
Swift
Executable File
#!/usr/bin/swift
|
|
/*
|
|
* Copyright 2020 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.
|
|
*/
|
|
|
|
// Utility script for verifying `import` and `include` syntax. This ensures a
|
|
// consistent style as well as functionality across multiple package managers.
|
|
|
|
// For more context, see https://github.com/firebase/firebase-ios-sdk/blob/main/HeadersImports.md.
|
|
|
|
import Foundation
|
|
|
|
// Skip these directories. Imports should only be repo-relative in libraries
|
|
// and unit tests.
|
|
let skipDirPatterns = ["/Sample/", "/Pods/",
|
|
"FirebaseInAppMessaging/Tests/Integration/",
|
|
"FirebaseAuth/",
|
|
// TODO: Turn Combine back on without Auth includes.
|
|
"FirebaseCombineSwift/Tests/Unit/FirebaseCombine-unit-Bridging-Header.h",
|
|
"SymbolCollisionTest/", "/gen/",
|
|
"IntegrationTesting/CocoapodsIntegrationTest/",
|
|
"FirebasePerformance/Tests/TestApp/",
|
|
"cmake-build-debug/", "build/", "ObjCIntegration/",
|
|
"FirebasePerformance/Tests/FIRPerfE2E/",
|
|
"Carthage/"] +
|
|
[
|
|
"CoreOnly/Sources", // Skip Firebase.h.
|
|
"SwiftPMTests", // The SwiftPM tests test module imports.
|
|
"IntegrationTesting/ClientApp", // The ClientApp tests module imports.
|
|
"FirebaseSessions/Protogen/", // Generated nanopb code with imports
|
|
// The zip builder's test package imports headers the way a SwiftPM package does, through its
|
|
// targets' include directories, because that's what the tests check.
|
|
"scripts/zip_builder/Fixtures/",
|
|
] +
|
|
|
|
// The following are temporary skips pending working through a first pass of the repo:
|
|
[
|
|
"FirebaseDatabase/Sources/third_party/Wrap-leveldb", // Pending SwiftPM for leveldb.
|
|
"Example",
|
|
"Firestore",
|
|
"FirebasePerformance/ProtoSupport/",
|
|
]
|
|
|
|
// Skip existence test for patterns that start with the following:
|
|
let skipImportPatterns = [
|
|
"AppCheckCore-Swift.h",
|
|
"FBLPromise",
|
|
"OCMock",
|
|
"OCMStubRecorder",
|
|
]
|
|
|
|
private class ErrorLogger {
|
|
var foundError = false
|
|
func log(_ message: String) {
|
|
print(message)
|
|
foundError = true
|
|
}
|
|
|
|
func importLog(_ message: String, _ file: String, _ line: Int) {
|
|
log("Import Error: \(file):\(line) \(message)")
|
|
}
|
|
}
|
|
|
|
private func checkFile(_ file: String, logger: ErrorLogger, inRepo repoURL: URL,
|
|
isSwiftFile: Bool) {
|
|
var fileContents = ""
|
|
do {
|
|
fileContents = try String(contentsOfFile: file, encoding: .utf8)
|
|
} catch {
|
|
logger.log("Could not read \(file). \(error)")
|
|
// Not a source file, give up and return.
|
|
return
|
|
}
|
|
|
|
guard !isSwiftFile else {
|
|
// Swift specific checks.
|
|
fileContents.components(separatedBy: .newlines)
|
|
.enumerated() // [(lineNum, line), ...]
|
|
.filter { $1.starts(with: "import FirebaseCoreExtension") }
|
|
.forEach { lineNum, line in
|
|
logger
|
|
.importLog(
|
|
"Use `internal import FirebaseCoreExtension` when importing `FirebaseCoreExtension`.",
|
|
file, lineNum
|
|
)
|
|
}
|
|
return
|
|
}
|
|
|
|
let isPublic = file.range(of: "/Public/") != nil &&
|
|
// TODO: Skip legacy GDTCCTLibrary file that isn't Public and should be moved.
|
|
// This test is used in the GoogleDataTransport's repo's CI clone of this repo.
|
|
file.range(of: "GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h") == nil
|
|
let isPrivate = file.range(of: "/Sources/Private/") != nil ||
|
|
// Delete when FirebaseInstallations fixes directory structure.
|
|
file.range(of: "Source/Library/Private/FirebaseInstallationsInternal.h") != nil ||
|
|
file.range(of: "FirebaseCore/Sources/FIROptionsInternal.h") != nil ||
|
|
file.range(of: "FirebaseCore/Extension") != nil
|
|
|
|
// Treat all files with names finishing on "Test" or "Tests" as files with tests.
|
|
let isTestFile = file.contains("Test.m") || file.contains("Tests.m") ||
|
|
file.contains("Test.swift") || file.contains("Tests.swift")
|
|
let isBridgingHeader = file.contains("Bridging-Header.h")
|
|
var inSwiftPackage = false
|
|
var inSwiftPackageElse = false
|
|
let lines = fileContents.components(separatedBy: .newlines)
|
|
var lineNum = 0
|
|
nextLine: for rawLine in lines {
|
|
let line = rawLine.trimmingCharacters(in: .whitespaces)
|
|
lineNum += 1
|
|
if line.starts(with: "#if SWIFT_PACKAGE") {
|
|
inSwiftPackage = true
|
|
} else if inSwiftPackage, line.starts(with: "#else") {
|
|
inSwiftPackage = false
|
|
inSwiftPackageElse = true
|
|
} else if inSwiftPackageElse, line.starts(with: "#endif") {
|
|
inSwiftPackageElse = false
|
|
} else if inSwiftPackage {
|
|
continue
|
|
} else if file.contains("FirebaseTestingSupport") {
|
|
// Module imports ok in SPM only test infrastructure.
|
|
continue
|
|
}
|
|
|
|
// "The #else of a SWIFT_PACKAGE check should only do CocoaPods module-style imports."
|
|
if line.starts(with: "#import") || line.starts(with: "#include") {
|
|
let importFile = line.components(separatedBy: " ")[1]
|
|
if inSwiftPackageElse {
|
|
if importFile.first != "<" {
|
|
// SharedTestUtilities files are included directly in test targets and
|
|
// use repo-relative imports.
|
|
if !file.contains("SharedTestUtilities/") {
|
|
logger
|
|
.importLog("Import in SWIFT_PACKAGE #else should start with \"<\".", file, lineNum)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
let importFileRaw = importFile.replacingOccurrences(of: "\"", with: "")
|
|
.replacingOccurrences(of: "<", with: "")
|
|
.replacingOccurrences(of: ">", with: "")
|
|
|
|
if importFile.first == "\"" {
|
|
// Public Headers should only use simple file names without paths.
|
|
if isPublic {
|
|
if importFile.contains("/") {
|
|
logger.importLog("Public header import should not include \"/\"", file, lineNum)
|
|
}
|
|
|
|
} else if !FileManager.default.fileExists(atPath: repoURL.path + "/" + importFileRaw) {
|
|
// Non-public header imports should be repo-relative paths. Unqualified imports are
|
|
// allowed in private headers.
|
|
if !isPrivate || importFile.contains("/") {
|
|
if importFileRaw.hasSuffix("-Swift.h") {
|
|
continue nextLine
|
|
}
|
|
for skip in skipImportPatterns {
|
|
if importFileRaw.starts(with: skip) {
|
|
continue nextLine
|
|
}
|
|
}
|
|
logger.importLog("Import \(importFileRaw) does not exist.", file, lineNum)
|
|
}
|
|
}
|
|
} else if importFile.first == "<", !isPrivate, !isTestFile, !isBridgingHeader, !isPublic {
|
|
// Verify that double quotes are always used for intra-module imports.
|
|
if importFileRaw.starts(with: "Firebase"),
|
|
// Allow intra-module imports of FirebaseAppCheckInterop.
|
|
// TODO: Remove the FirebaseAppCheckInterop exception when it's moved to a separate repo.
|
|
importFile.range(of: "FirebaseAppCheckInterop/FirebaseAppCheckInterop.h") == nil {
|
|
logger
|
|
.importLog("Imports internal to the repo should use double quotes not \"<\"", file,
|
|
lineNum)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func main() -> Int32 {
|
|
let logger = ErrorLogger()
|
|
// Search the path upwards to find the root of the firebase-ios-sdk repo.
|
|
var url = URL(fileURLWithPath: FileManager().currentDirectoryPath)
|
|
while url.path != "/" {
|
|
let script = url.appendingPathComponent("scripts/check_imports.swift")
|
|
if FileManager.default.fileExists(atPath: script.path) {
|
|
break
|
|
}
|
|
url = url.deletingLastPathComponent()
|
|
}
|
|
let repoURL = url
|
|
guard let contents = try? FileManager.default.contentsOfDirectory(at: repoURL,
|
|
includingPropertiesForKeys: nil,
|
|
options: [.skipsHiddenFiles])
|
|
else {
|
|
logger.log("Failed to get repo contents \(repoURL)")
|
|
return 1
|
|
}
|
|
|
|
for rootURL in contents {
|
|
if !rootURL.hasDirectoryPath {
|
|
continue
|
|
}
|
|
let enumerator = FileManager.default.enumerator(atPath: rootURL.path)
|
|
whileLoop: while let file = enumerator?.nextObject() as? String {
|
|
if let fType = enumerator?.fileAttributes?[FileAttributeKey.type] as? FileAttributeType,
|
|
fType == .typeRegular {
|
|
if file.starts(with: ".") {
|
|
continue
|
|
}
|
|
if !(file.hasSuffix(".h") ||
|
|
file.hasSuffix(".m") ||
|
|
file.hasSuffix(".mm") ||
|
|
file.hasSuffix(".c") ||
|
|
file.hasSuffix(".swift")) {
|
|
continue
|
|
}
|
|
let fullTransformPath = rootURL.path + "/" + file
|
|
for dirPattern in skipDirPatterns {
|
|
if fullTransformPath.range(of: dirPattern) != nil {
|
|
continue whileLoop
|
|
}
|
|
}
|
|
checkFile(
|
|
fullTransformPath,
|
|
logger: logger,
|
|
inRepo: repoURL,
|
|
isSwiftFile: file.hasSuffix(".swift")
|
|
)
|
|
}
|
|
}
|
|
}
|
|
return logger.foundError ? 1 : 0
|
|
}
|
|
|
|
exit(main())
|