Skip to content
πŸ“Š Extract and analyze the evolution of an iOS app's code.
Branch: master
Clone or download
Latest commit 08499fb Apr 16, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
Sources
Tests
.gitignore
CHANGELOG.md
LICENSE License updates Apr 12, 2019
Package.resolved Making things pretty Mar 25, 2019
Package.swift
README.md
SwiftInfo.podspec
prepare_for_release.sh License updates Apr 12, 2019

README.md

πŸ“Š SwiftInfo

GitHub release

SwiftInfo is a simple CLI tool that extracts, tracks and analyzes metrics that are useful for Swift apps. Besides the default tracking options that are shipped with the tool, you can customize SwiftInfo to track pretty much anything that can be conveyed in a simple .swift script.

Available Providers

Type Name Description Requirements
πŸ“¦ IPASizeProvider Size of the .ipa archive (Not the App Store size!) Successful xcodebuild archive and build logs
πŸ“Š CodeCoverageProvider Code coverage percentage Test logs, Xcode developer tools, Test targets with code coverage reports enabled
πŸ‘Ά TargetCountProvider Number of targets (dependencies) Build logs
🎯 TestCountProvider Sum of all test target's test count Test logs
⚠️ WarningCountProvider Number of warnings in a build Build logs
πŸ§™β€β™‚οΈ OBJCFileCountProvider Number of OBJ-C files and headers (for mixed OBJ-C / Swift projects) Build logs
⏰ LongestTestDurationProvider The name and duration of the longest test Test logs
πŸ› TotalTestDurationProvider The sum of the duration of all tests Test logs
πŸ–Ό LargestAssetCatalogProvider The name and size of the largest asset catalog Build logs
🎨 TotalAssetCatalogsSizeProvider The sum of the size of all asset catalogs Build logs
πŸ’» LinesOfCodeProvider Executable lines of code Same as CodeCoverageProvider.

Usage

SwiftInfo requires the raw logs of a succesful test/archive build combo to work, so it's better used as the last step of a CI pipeline.

If you use Fastlane, you can easily expose the raw logs by adding buildlog_path to scan and gym. Here's a simple example of a Fastlane step that runs tests, submits an archive to TestFlight and runs SwiftInfo (be sure to edit the folder paths to what's being used by your project):

desc "Submits a new beta build and runs SwiftInfo"
lane :beta do
  # Run tests, copying the raw logs to the project folder 
  scan(
    scheme: "MyScheme",
    buildlog_path: "./build/tests_log"
  )
    
  # Archive the app, copying the raw logs to the project folder 
  gym(
    workspace: "MyApp.xcworkspace",
    scheme: "Release",
    buildlog_path: "./build/build_log"
  )
 
  # Send to TestFlight
  pilot(
      skip_waiting_for_build_processing: true
  )

  # Run SwiftInfo
  sh("../Pods/SwiftInfo/swiftinfo")

  # Commit and push SwiftInfo's result
  sh("git add ../SwiftInfo-output/SwiftInfoOutput.json")
  sh("git commit -m \"[ci skip] Updating SwiftInfo Output JSON\"")
  push_to_git_remote
end

SwiftInfo itself is configured by creating a Infofile.swift file in your project's root. Here's an example Infofile that retrieves some data and sends it to Slack:

import SwiftInfoCore

FileUtils.buildLogFilePath = "./build/build_log/MyApp-MyConfig.log"
FileUtils.testLogFilePath = "./build/tests_log/MyApp-MyConfig.log"

let projectInfo = ProjectInfo(xcodeproj: "MyApp.xcodeproj",
                              target: "MyTarget",
                              configuration: "MyConfig")

let api = SwiftInfo(projectInfo: projectInfo)

let output = api.extract(IPASizeProvider.self) +
             api.extract(WarningCountProvider.self) +
             api.extract(TestCountProvider.self) +
             api.extract(TargetCountProvider.self, args: .init(mode: .complainOnRemovals)) +
             api.extract(CodeCoverageProvider.self, args: .init(targets: ["NetworkModule", "MyApp"])) +
             api.extract(LinesOfCodeProvider.self, args: .init(targets: ["NetworkModule", "MyApp"]))

// Send the results to Slack.
api.sendToSlack(output: output, webhookUrl: "YOUR_SLACK_WEBHOOK_HERE")

// Save the output to disk.
api.save(output: output)

You can see SwiftInfo's properties and methods here.

Available Arguments

To be able to support different types of projects, SwiftInfo provides customization options to some providers. Click on each of them to see their documentation!

πŸ‘Ά TargetCountProvider

πŸ’» LinesOfCodeProvider

πŸ“Š CodeCoverageProvider

Output

After successfully extracting data, SwiftInfo will add/update a json file in the {Infofile path}/SwiftInfo-output folder. It's important to add this file to version control after the running the tool as this is what SwiftInfo uses to compare new pieces of information.

SwiftInfo-Reader can be used to transform this output into a more visual static HTML page:

Tracking custom info

If you wish to track something that's not handled by the default providers, you can create your own provider by creating a struct that inherits from InfoProvider inside your Infofile. Here's a simple provider that tracks the number of files in a project where adding new files is bad:

struct FileCountProvider: InfoProvider {

    struct Args {
        let fromFolders: [String]
    }

    typealias Arguments = Args

    static let identifier = "file_count"
    let description = "Number of files"

    let fileCount: Int

    static func extract(fromApi api: SwiftInfo, args: Args?) throws -> FileCountProvider {
        let count = // get the number of files from the provided `args?.fromFolders`
        return FileCountProvider(fileCount: count)
    }

    // Given another instance of this provider, return a `Summary` that explains the difference between them.
    func summary(comparingWith other: FileCountProvider?, args: Args?) -> Summary {
        let prefix = "File Count"
        guard let other = other else {
            return Summary(text: prefix + ": \(count)", style: .neutral)
        }
        guard count != other.count else {
            return Summary(text: prefix + ": Unchanged. (\(count))", style: .neutral)
        }
        let modifier: String
        let style: Summary.Style
        if count > other.count {
            modifier = "*grew*"
            style = .negative
        } else {
            modifier = "was *reduced*"
            style = .positive
        }
        let difference = abs(other.count - count)
        let text = prefix + " \(modifier) by \(difference) (\(count))"
        return Summary(text: text, style: style)
    }
}

Documentation of useful types and methods from SwiftInfoCore that you can use when building custom providers will be available soon.

If you end up creating a custom provider, consider submitting it here as a pull request to have it added as a default one!

Installation

CocoaPods

pod 'SwiftInfo'

License

SwiftInfo is released under the MIT license. See LICENSE for details.

You can’t perform that action at this time.