Skip to content

RUM-7285 Add api-surface step to Lint stage #2112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Lint:
- make clean repo-setup ENV=ci
- make lint license-check
- make rum-models-verify sr-models-verify
- make api-surface-verify

Unit Tests (iOS):
stage: test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ extension SessionReplay {
/// - startRecordingImmediately: If the recording should start automatically. When `true`, the recording starts automatically; when `false` it doesn't, and the recording will need to be started manually. Default: `true`.
/// - customEndpoint: Custom server url for sending replay data. Default: `nil`.
/// - featureFlags: Experimental feature flags.
public init( // swiftlint:disable:this function_default_parameter_at_end
// swiftlint:disable function_default_parameter_at_end
public init(
replaySampleRate: Float = 100,
textAndInputPrivacyLevel: TextAndInputPrivacyLevel,
imagePrivacyLevel: ImagePrivacyLevel,
Expand All @@ -102,6 +103,7 @@ extension SessionReplay {
self.customEndpoint = customEndpoint
self.featureFlags = featureFlags
}
// swiftlint:enable function_default_parameter_at_end

/// Creates Session Replay configuration.
/// - Parameters:
Expand Down
104 changes: 64 additions & 40 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,18 @@ export DD_SDK_DATADOG_XCCONFIG_CI
# Installs tools and dependencies with homebrew.
# Do not call 'brew update' and instead let Bitrise use its own brew bottle mirror.
dependencies:
@echo "⚙️ Installing dependencies..."
@bundle install
@brew list swiftlint &>/dev/null || brew install swiftlint
@brew upgrade carthage
@carthage bootstrap --platform iOS,tvOS --use-xcframeworks
@echo $$DD_SDK_BASE_XCCONFIG > xcconfigs/Base.local.xcconfig;
@brew list gh &>/dev/null || brew install gh
@echo "⚙️ Installing dependencies..."
@bundle install
@brew list swiftlint &>/dev/null || brew install swiftlint
@brew upgrade carthage
@carthage bootstrap --platform iOS,tvOS --use-xcframeworks
@echo $$DD_SDK_BASE_XCCONFIG > xcconfigs/Base.local.xcconfig;
@brew list gh &>/dev/null || brew install gh
ifeq (${ci}, true)
@echo $$DD_SDK_BASE_XCCONFIG_CI >> xcconfigs/Base.local.xcconfig;
@echo $$DD_SDK_DATADOG_XCCONFIG_CI > xcconfigs/Datadog.local.xcconfig;
@echo $$DD_SDK_BASE_XCCONFIG_CI >> xcconfigs/Base.local.xcconfig;
@echo $$DD_SDK_DATADOG_XCCONFIG_CI > xcconfigs/Datadog.local.xcconfig;
ifndef DD_DISABLE_TEST_INSTRUMENTING
@echo $$DD_SDK_TESTING_XCCONFIG_CI > xcconfigs/DatadogSDKTesting.local.xcconfig;
@echo $$DD_SDK_TESTING_XCCONFIG_CI > xcconfigs/DatadogSDKTesting.local.xcconfig;
endif

endif
Expand Down Expand Up @@ -356,40 +356,64 @@ sr-snapshot-tests-open:
@$(ECHO_TITLE) "make sr-snapshot-tests-open"
./tools/sr-snapshot-test.sh --open-project

# Generate api-surface files for Datadog and DatadogObjc.
# Generate api-surface files for Datadog and DatadogObjc
api-surface:
@echo "Generating api-surface-swift"
@cd tools/api-surface && \
swift run api-surface spm \
--path ../../ \
--library-name DatadogCore \
--library-name DatadogLogs \
--library-name DatadogTrace \
--library-name DatadogRUM \
--library-name DatadogCrashReporting \
--library-name DatadogWebViewTracking \
--library-name DatadogSessionReplay \
> ../../api-surface-swift && \
cd -

@echo "Generating api-surface-objc"
@cd tools/api-surface && \
swift run api-surface spm \
--path ../../ \
--library-name DatadogObjc \
> ../../api-surface-objc && \
cd -
@$(ECHO_TITLE) "make api-surface"
@echo "Generating api-surface-swift"
@cd tools/api-surface && \
swift run api-surface generate \
--path ../../ \
--library-name DatadogCore \
--library-name DatadogLogs \
--library-name DatadogTrace \
--library-name DatadogRUM \
--library-name DatadogCrashReporting \
--library-name DatadogWebViewTracking \
--library-name DatadogSessionReplay \
--output-file ../../api-surface-swift

@echo "Generating api-surface-objc"
@cd tools/api-surface && \
swift run api-surface generate \
--path ../../ \
--library-name DatadogObjc \
--output-file ../../api-surface-objc

# Verify API surface files for Datadog and DatadogObjc
api-surface-verify:
@$(ECHO_TITLE) "make api-surface-verify"
@echo "Verifying api-surface-swift"
@cd tools/api-surface && \
swift run api-surface verify \
--path ../../ \
--library-name DatadogCore \
--library-name DatadogLogs \
--library-name DatadogTrace \
--library-name DatadogRUM \
--library-name DatadogCrashReporting \
--library-name DatadogWebViewTracking \
--library-name DatadogSessionReplay \
--output-file /tmp/api-surface-swift-generated \
../../api-surface-swift

@echo "Verifying api-surface-objc"
@cd tools/api-surface && \
swift run api-surface verify \
--path ../../ \
--library-name DatadogObjc \
--output-file /tmp/api-surface-objc-generated \
../../api-surface-objc

# Generate Datadog monitors terraform definition for E2E tests:
e2e-monitors-generate:
@echo "Deleting previous 'main.tf as it will be soon generated."
@rm -f tools/nightly-e2e-tests/monitors-gen/main.tf
@echo "Deleting previous Terraform state and backup as we don't need to track it."
@rm -f tools/nightly-e2e-tests/monitors-gen/terraform.tfstate
@rm -f tools/nightly-e2e-tests/monitors-gen/terraform.tfstate.backup
@echo "⚙️ Generating 'main.tf':"
@./tools/nightly-e2e-tests/nightly_e2e.py generate-tf --tests-dir ../../Datadog/E2ETests
@echo "⚠️ Remember to delete all iOS monitors manually from Mobile-Integration org before running 'terraform apply'."
@echo "Deleting previous 'main.tf as it will be soon generated."
@rm -f tools/nightly-e2e-tests/monitors-gen/main.tf
@echo "Deleting previous Terraform state and backup as we don't need to track it."
@rm -f tools/nightly-e2e-tests/monitors-gen/terraform.tfstate
@rm -f tools/nightly-e2e-tests/monitors-gen/terraform.tfstate.backup
@echo "⚙️ Generating 'main.tf':"
@./tools/nightly-e2e-tests/nightly_e2e.py generate-tf --tests-dir ../../Datadog/E2ETests
@echo "⚠️ Remember to delete all iOS monitors manually from Mobile-Integration org before running 'terraform apply'."

# Creates dogfooding PR in shopist-ios
dogfood-shopist:
Expand Down
16 changes: 9 additions & 7 deletions api-surface-swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public enum Datadog
public static func stopInstance(named instanceName: String = CoreRegistry.defaultInstanceName)
public static func initialize(with configuration: Configuration,trackingConsent: TrackingConsent,instanceName: String = CoreRegistry.defaultInstanceName) -> DatadogCoreProtocol


# ----------------------------------
# API surface for DatadogLogs:
# ----------------------------------
Expand Down Expand Up @@ -182,7 +181,6 @@ public enum Logs
public protocol LogEventMapper
func map(event: LogEvent, callback: @escaping (LogEvent) -> Void)


# ----------------------------------
# API surface for DatadogTrace:
# ----------------------------------
Expand Down Expand Up @@ -337,7 +335,6 @@ public enum SpanTags
public class Tracer
public static func shared(in core: DatadogCoreProtocol = CoreRegistry.default) -> OTTracer


# ----------------------------------
# API surface for DatadogRUM:
# ----------------------------------
Expand Down Expand Up @@ -1790,7 +1787,6 @@ public enum PerformanceMetric
case flutterRasterTime
case jsFrameTimeSeconds


# ----------------------------------
# API surface for DatadogCrashReporting:
# ----------------------------------
Expand All @@ -1799,7 +1795,6 @@ public final class CrashReporting
public static func enable(in core: DatadogCoreProtocol = CoreRegistry.default)
public static func enable()


# ----------------------------------
# API surface for DatadogWebViewTracking:
# ----------------------------------
Expand All @@ -1815,7 +1810,6 @@ public enum WebViewTracking
public func send(body: Any, slotId: String? = nil)
public static func messageEmitter(in core: DatadogCoreProtocol,logsSampleRate: Float = 100) -> AbstractMessageEmitter


# ----------------------------------
# API surface for DatadogSessionReplay:
# ----------------------------------
Expand Down Expand Up @@ -2222,9 +2216,17 @@ public enum SessionReplay
public var touchPrivacyLevel: TouchPrivacyLevel
public var startRecordingImmediately: Bool
public var customEndpoint: URL?
public init( // swiftlint:disable:this function_default_parameter_at_endreplaySampleRate: Float = 100,textAndInputPrivacyLevel: TextAndInputPrivacyLevel,imagePrivacyLevel: ImagePrivacyLevel,touchPrivacyLevel: TouchPrivacyLevel,startRecordingImmediately: Bool = true,customEndpoint: URL? = nil)
public var featureFlags: FeatureFlags
public init(replaySampleRate: Float = 100,textAndInputPrivacyLevel: TextAndInputPrivacyLevel,imagePrivacyLevel: ImagePrivacyLevel,touchPrivacyLevel: TouchPrivacyLevel,startRecordingImmediately: Bool = true,customEndpoint: URL? = nil,featureFlags: FeatureFlags = .defaults)
public init(replaySampleRate: Float = 100,defaultPrivacyLevel: SessionReplayPrivacyLevel = .mask,startRecordingImmediately: Bool = true,customEndpoint: URL? = nil)
public mutating func setAdditionalNodeRecorders(_ additionalNodeRecorders: [SessionReplayNodeRecorder])
[?] extension SessionReplay.Configuration
public typealias FeatureFlags = [FeatureFlag: Bool]
public enum FeatureFlag
case swiftui
[?] extension SessionReplay.Configuration.FeatureFlags
public static var defaults: Self
public subscript(flag: Key) -> Bool
public enum objc_TextAndInputPrivacyLevelOverride: Int
case none
case maskSensitiveInputs
Expand Down
105 changes: 93 additions & 12 deletions tools/api-surface/Sources/APISurfaceCore/Commands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
import Foundation
import ArgumentParser

public var printFunction: (String) -> Void = { print($0) }

public struct SPMLibrarySurfaceCommand: ParsableCommand {
public struct GenerateCommand: ParsableCommand {
public static let configuration = CommandConfiguration(
commandName: "spm",
abstract: "Prints API surface for given SPM library or list of libraries."
commandName: "generate",
abstract: "Generate API surface files for given SPM library or list of libraries."
)

@Option(help: "Specify a library name (use this option multiple times to provide list of libraries).")
Expand All @@ -21,23 +19,106 @@ public struct SPMLibrarySurfaceCommand: ParsableCommand {
@Option(help: "The path to the folder containing `Package.swift`.")
var path: String

@Option(help: "The file to which the generated API surface should be written.")
var outputFile: String

public init() {}

public func run() throws {
try generateAPISurface(
libraryName: libraryName,
path: path,
outputFile: outputFile
)
}
}

public struct VerifyCommand: ParsableCommand {
public static let configuration = CommandConfiguration(
commandName: "verify",
abstract: "Verify that a generated API surface matches the reference file."
)

@Option(help: "Specify a library name (use this option multiple times to provide a list of libraries).")
var libraryName: [String]

@Option(help: "The path to the folder containing `Package.swift`.")
var path: String

@Option(help: "The temporary file to which the generated API surface should be written.")
var outputFile: String

@Argument(help: "Path to the reference API surface file to compare against.")
var referencePath: String

public init() {}

public func run() throws {
var printSeparator = false
for libraryName in libraryName {
let surface = try APISurface(spmLibraryName: libraryName, inPath: path)
try generateAPISurface(
libraryName: libraryName,
path: path,
outputFile: outputFile
)

// Compare the generated files with the reference files
let diff = try compareFiles(reference: referencePath, generated: outputFile)

if !diff.isEmpty {
throw ValidationError("""
❌ API surface mismatch detected!
Run `make api-surface` locally to update reference files and commit the changes.
""")
}

print("✅ API surface files are up-to-date.")
}

private func compareFiles(reference: String, generated: String) throws -> String {
let referenceContent = try String(contentsOfFile: reference)
let generatedContent = try String(contentsOfFile: generated)

return referenceContent == generatedContent ? "" : "Difference in \(reference)"
}
}

private func generateAPISurface(
libraryName: [String],
path: String,
outputFile: String
) throws {
var output = ""
var printSeparator = false

for library in libraryName {
do {
let surface = try APISurface(spmLibraryName: library, inPath: path)
if printSeparator {
printFunction("\n")
output.append("\n")
}
printFunction("""

output.append("""
# ----------------------------------
# API surface for \(libraryName):
# API surface for \(library):
# ----------------------------------

""")
printFunction(try surface.print())

output.append("\n")
output.append(try surface.print() + "\n")

printSeparator = true
} catch {
print("❌ Error generating API surface for library \(library): \(error)")
throw error
}
}

// Write the output to the specified file
do {
try output.write(toFile: outputFile, atomically: true, encoding: .utf8)
print("✅ API surface written to \(outputFile)")
} catch {
print("❌ Error writing API surface to \(outputFile): \(error)")
throw error
}
}
9 changes: 5 additions & 4 deletions tools/api-surface/Sources/api-surface/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import APISurfaceCore

private struct RootCommand: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "api-surface",
abstract: "Prints API surface for given module.",
abstract: "A tool to manage API surface files.",
subcommands: [
SPMLibrarySurfaceCommand.self
]
GenerateCommand.self,
VerifyCommand.self
],
defaultSubcommand: GenerateCommand.self
)
}

Expand Down
Loading