Framework-level dependency graph generator for Xcode projects - with support for custom graphing

Overview

XCGrapher

xcgrapher is, by default, a framework-level dependency graph generator for Xcode projects. It works by reading local clones of the source, so it's not a problem if your project relies on internal/unpublished frameworks.

However, it is so much more than just that. xcgrapher supports custom (Swift) plugins so you can easily parse your source code and quickly create graphs that are meaningful to you and your team!

Basic Usage

To produce a graph of imported Cocoapods and Swift Package Manager modules for the target SomeApp in the project SomeApp.xcodeproj:

xcgrapher --project SomeApp.xcodeproj --target SomeApp --pods --spm

This produces the following image:

You could also pass --apple to include native frameworks in the graph. See xcgrapher --help for more options.

Installation

brew tap maxchuquimia/scripts
brew install xcgrapher
gem install xcodeproj # If you use Cocoapods you probably don't need to do this

Or, just clone the project and make install.

Custom Graphs

What if (for example) your team has it's own property wrappers for dependency injection? You can graph it's usage that too!

Create yourself a new Swift Package and subclass XCGrapherPlugin from the package maxchuquimia/XCGrapherPluginSupport. You can override a function that will be called once with every source file in your project and it's (SPM) dependencies. Then you can parse each file as needed and generate an array of arrows that will be drawn.

In fact, xcgrapher's default behaviour is implemented as a plugin too!

For full documentation take a look at the XCGrapherPluginSupport repo.

How it works

Main Project Target

xcgrapher uses xcodeproj (a Cocoapods gem) to find all the source files of the given target. It then reads them and creates a list of imports to know which --pods, --spm and/or --apple modules are part of the target.

Swift Package Manager

xcgrapher builds the --project so that all it's SPM dependencies are cloned. It parses the build output to find the location of these clones and calls swift package describe on each. Then it iterates through all the source files of each package to find their import lines and repeats.

Cocoapods

xcgrapher uses the Podfile.lock to discover what each pod's dependencies are. Change the location of the lockfile with the --podlock option if needed. Cocoapods source files are not currently searched so graphing links to imported Apple frameworks from a pod is unsupported, as is file-by-file processing in a custom plugin. xcgrapher is really geared towards Xcode projects and Swift Packages.

Apple

xcgrapher assumes /System/Library/Frameworks and another path (see NativeDependencyManager.swift) contains a finite list of frameworks belonging to Apple. This probably isn't ideal for some cases, so open a PR if you know a better way!

Carthage

Carthage dependencies are currently unsupported. Need it? Add it! Conform to the DependencyManager protocol and have a look at the usage of SwiftPackageManager.

Notes on Development

Here's a few things to bear in mind if you're adding a feature:

  • Run make configure before trying to build or run tests from Xcode (this is done automatically before make build or make install)
  • Tests pass in Xcode, however you can't run the project from within Xcode (due to enivironment variables missing). When developing I like to just make install after unit tests are passing and then do my manual testing from the command line.
Comments
  • Lint Project

    Lint Project

    Description

    This PR is a proposal to lint the project, but not necessarily using the rules that I've applied myself.

    I decided to lint the project because I noticed some code style weirdness that I have a personal preference to fix 🙈 and that are easy enough for a linter to resolve. So I decided to run 3 linters:

    • SwiftLint (v0.48.1, which's the version I happen to have installed, although it's not the latest), using their default rule set.
    • SwiftFormat (v0.43.1, which's the latest version), using their default rule set.
    • Manual code clean up 😊

    It's important to notice that I ran them in the order I've written above: first SwiftLint, the SwiftFormat, then manual. This is important because SwiftLint default rule set undoes some of the changes executed by SwiftFormat's default rule set (namely trailing commas in multiline dictionaries/sequences, in this particular project).

    If you agree with this proposal, we can merge it as is and then add customization config files for each of the linters, and eventually perhaps automate the linting process somehow. Also, if this gets approved, then I can lint #7 as well 😊

    opened by rogerluan 10
  • Fix `[main] The folder “checkouts” doesn’t exist.` error

    Fix `[main] The folder “checkouts” doesn’t exist.` error

    Description

    This PR aims to solve the error [main] The folder “checkouts” doesn’t exist. without the need to allowing customization of the checkouts directory.

    Relates to #3

    It's a simpler implementation alternative over https://github.com/maxchuquimia/xcgrapher/commit/cc1ae0651c3986cae24c12857640bf5210931583. Not sure which solution is better, or if they're really equivalent. Mine's a bit uglier, bug significantly simpler 😇The other is certainly more elegant and fixes the root source (i.e. in the struct's initializer, instead of at the only call site that uses the referred struct). I'm open to discuss which solution goes in.

    I really like the the extra debugging (error tracing) capabilities that https://github.com/maxchuquimia/xcgrapher/commit/cc1ae0651c3986cae24c12857640bf5210931583 adds, though! It was quite painful to debug this issue without something like that. Looking forward to having those changes merged in soon! 😃

    Cheers.

    opened by rogerluan 7
  • Undefined method `real_path` when installed from homebrew

    Undefined method `real_path` when installed from homebrew

    I've installed the app via homebrew as outlined in the README. When I try and run it for my project, I get the following error:

    $ xcgrapher --project Retailer.xcodeproj --target Retailer --apple
    
    [XCGrapher] Loading plugin /usr/local/Cellar/xcgrapher/0.0.9/lib/libXCGrapherModuleImportPlugin.dylib
    [XCGrapher] Generating list of source files in Retailer
    [ShellTask] ruby -r xcodeproj -e 'Xcodeproj::Project.open("Retailer.xcodeproj").targets.filter do |t| t.name == "Retailer" end.first.source_build_phase.files.to_a.each do |f| puts f.file_ref.real_path.to_s end'
    [main] -e:1:in `block in <main>': undefined method `real_path' for nil:NilClass (NoMethodError)
    	from -e:1:in `each'
    	from -e:1:in `<main>'
    

    Is there something wrong with the command I ran, or is this a bug?

    opened by mpdifran 6
  • Library not loaded: @rpath/libXCGrapherPluginSupport.dylib

    Library not loaded: @rpath/libXCGrapherPluginSupport.dylib

    Setup: MacOS 11.6, M1 Steps to reproduce:

    brew tap maxchuquimia/scripts
    brew install xcgrapher
    gem install xcodeproj 
    xcgrapher
    
    

    Output

    
    dyld: Library not loaded: @rpath/libXCGrapherPluginSupport.dylib
      Referenced from: /opt/homebrew/bin/xcgrapher
      Reason: image not found
    [1]    4170 abort      xcgrapher
    
    opened by Ewg777 5
  • Support for other package managers?

    Support for other package managers?

    Just wondering if there's a plan to support Carthage (looks like there's support for Cocoapods, SPM, and Apple Frameworks only).

    Also, is it possible to run this tool on a Swift Package (i.e. pass in the Package.swift)?

    opened by mpdifran 3
  • Support Private Remote Swift Packages

    Support Private Remote Swift Packages

    I'd like to use this project to plot dependency graphs for private remote dependencies as well. This is tricky because it will need some sort of authentication.

    Might relate to #8

    opened by rogerluan 2
  • brew tap maxchuquimia/scripts failed

    brew tap maxchuquimia/scripts failed

    When I run the brew tap maxchuquimia/scripts command, I get the following error: Any idea?

    ==> Tapping maxchuquimia/scripts
    Cloning into '/usr/local/Homebrew/Library/Taps/maxchuquimia/homebrew-scripts'...
    remote: Enumerating objects: 183, done.
    remote: Counting objects: 100% (44/44), done.
    remote: Compressing objects: 100% (29/29), done.
    remote: Total 183 (delta 30), reused 29 (delta 15), pack-reused 139
    Receiving objects: 100% (183/183), 1.83 MiB | 4.39 MiB/s, done.
    Resolving deltas: 100% (78/78), done.
    Error: Invalid formula: /usr/local/Homebrew/Library/Taps/maxchuquimia/homebrew-scripts/Formula/timeout.rb
    timeout: Calling bottle :unneeded is disabled! There is no replacement.
    Please report this issue to the maxchuquimia/scripts tap (not Homebrew/brew or Homebrew/core):
      /usr/local/Homebrew/Library/Taps/maxchuquimia/homebrew-scripts/Formula/timeout.rb:8
    
    Error: Invalid formula: /usr/local/Homebrew/Library/Taps/maxchuquimia/homebrew-scripts/Formula/git-pr.rb
    git-pr: Calling bottle :unneeded is disabled! There is no replacement.
    Please report this issue to the maxchuquimia/scripts tap (not Homebrew/brew or Homebrew/core):
      /usr/local/Homebrew/Library/Taps/maxchuquimia/homebrew-scripts/Formula/git-pr.rb:10
    
    Error: Cannot tap maxchuquimia/scripts: invalid syntax in tap!
    
    opened by ruunak 1
  • Add Support to `--package path/to/Package.swift` Parameter

    Add Support to `--package path/to/Package.swift` Parameter

    Description

    This PR picks up the work that was initiated here https://github.com/maxchuquimia/xcgrapher/commit/d04da282e4207dd72e6c63e99438430000698fed and aims to bring support for the --package argument, which receives a path to a Package.swift file, and plots a graph for the dependencies of the given Swift Package.

    The heavy lifting was really done by @maxchuquimia. What I did was finish up the TODO left here https://github.com/maxchuquimia/xcgrapher/commit/d04da282e4207dd72e6c63e99438430000698fed#diff-153d5429d399c18d0a6dd32f593f6d1411fd0c8e9df19e62701497835913edb9R31 and fix the unit tests. I also added a new dependency to the sample SPM project to make the resulting graph look more interesting (where 2 packages (SomePackage and Moya) depends on the same dependency (Alamofire).

    Resolves #4

    Note: this PR was branched off of #6 so please review & possibly merge it first, before reviewing & merging this one. As I'm working off of a GitHub Fork, I can't point this PR against my other branch 😬

    Demo

    image
    opened by rogerluan 1
  • Does this support

    Does this support "internal" cocoapods added via sub-projects inside the workspace?

    I was using this branch/PR https://github.com/maxchuquimia/xcgrapher/pull/10 and it managed to graph the main target project but didn't pick up on any of the other "sub-projects" that we have in the workspace added in the podfile.

    Thanks

    bug 
    opened by oliverfoggin 1
  • [Refactor] Add Ruby Utility to Help Write Ruby Commands More Naturally

    [Refactor] Add Ruby Utility to Help Write Ruby Commands More Naturally

    Description

    This PR aims to make writing Ruby commands more naturally, in a script-like way, instead of dealing with those awkward CLI syntax.

    I thought of opening a separate PR for this feature since it may not be a desired change 🤷 If that's the case, we can easily and simply close this PR 👍

    Discussion

    Sorry for piling follow up PRs on top of open PRs 😆 This branch was branched off of #10 @maxchuquimia if you'd like to invite me as a member/co-owner of this repo, these branches can be managed more easily by me (so they point to the right branch instead of all pointing to master, which makes them look completely unrelated 😬 ). Your call though, don't worry if not 😊

    opened by rogerluan 2
  • Add Support to Local Swift Packages from SPM, Xcode Project & Workspaces

    Add Support to Local Swift Packages from SPM, Xcode Project & Workspaces

    Description

    This PR resolves #8

    NOTE: This PR branches off of #7 , please review that one first. Since I can only work off of GitHub Forks, I can't point this PR to that other branch 😞

    Discussion

    Feel free to ask as many questions as needed @maxchuquimia 🙇 There're a looot of changes haha

    Please don't mind about code style issues (issues that would be caught by a linter) right now. We can fix those as we move forward with the linter PR 👍

    Resources

    These docs have helped me a lot: https://www.rubydoc.info/gems/xcodeproj/

    Demo

    xcgrapher --workspace="SomeApp.xcworkspace" --scheme="SomeAppScheme" --spm && open /tmp/xcgrapher
    
    image
    opened by rogerluan 9
  • Support Local Swift Packages

    Support Local Swift Packages

    I'd like to use this project to plot dependency graphs for local dependencies as well (e.g. .package(name: "SomeDependency", path: "../path/to/some/other/dependency"),

    I'll investigate how to implement this. Any guidance will be really appreciated, though! 🙏

    opened by rogerluan 0
  • Let's add the possibility to set custom checkoutsDir

    Let's add the possibility to set custom checkoutsDir

    faced with the issue because have custom pods_install steps with moving folders Got message: [main] The folder “checkouts” doesn’t exist.

    Seems need a param for setting let checkoutsDir = derivedDataDir.appending("/SourcePackages/checkouts")

    opened by salyasev 18
Owner
Max Chuquimia
Senior iOS Engineer
Max Chuquimia
DIKit Dependency Injection Framework for Swift, inspired by KOIN.

DIKit Dependency Injection Framework for Swift, inspired by KOIN. Basically an implementation of service-locator pattern, living within the applicatio

null 95 Dec 22, 2022
Swinject is a lightweight dependency injection framework for Swift.

Swinject Swinject is a lightweight dependency injection framework for Swift. Dependency injection (DI) is a software design pattern that implements In

null 5.6k Dec 31, 2022
Dependency Injection framework for Swift (iOS/macOS/Linux)

Declarative, easy-to-use and safe Dependency Injection framework for Swift (iOS/macOS/Linux) Features Dependency declaration via property wrappers or

Scribd 684 Dec 12, 2022
Swift Ultralight Dependency Injection / Service Locator framework

Swift Ultralight Dependency Injection / Service Locator framework

Michael Long 1.9k Jan 6, 2023
Guise - An elegant, flexible, type-safe dependency resolution framework for Swift

Guise is an elegant, flexible, type-safe dependency resolution framework for Swift. Flexible dependency resolution, with optional caching Elegant, str

null 52 Oct 3, 2022
Needle - Compile-time safe Swift dependency injection framework

Needle is a dependency injection (DI) system for Swift. Unlike other DI frameworks, such as Cleanse, Swinject, Needle encourages hierarchical DI struc

Uber Open Source 1.4k Jan 3, 2023
CarbonGraph - A Swift dependency injection / lookup framework for iOS

CarbonGraph is a Swift dependency injection / lookup framework for iOS. You can

Baidu 244 Jan 4, 2023
An elegant, flexible, type-safe dependency resolution framework for Swift

Guise is an elegant, flexible, type-safe dependency resolution framework for Swift. Flexible dependency resolution, with optional caching Elegant, str

null 52 Oct 3, 2022
Container is a lightweight dependency injection framework for Swift.

Container Container is a lightweight dependency injection framework for Swift. Installation is available in the Swift Package Manager. Swift Package M

Aleksei Artemev 17 Oct 13, 2022
Corridor A Coreader-like Dependency Injection μFramework

Corridor A Coreader-like Dependency Injection μFramework Table of Contents Why | Examples | Usage | Installation | Credits & License | Why In order to

symentis GmbH 60 Nov 1, 2022
Deli is an easy-to-use Dependency Injection Container that creates DI containers

Deli is an easy-to-use Dependency Injection Container that creates DI containers with all required registrations and corresponding factories.

Jungwon An 134 Aug 10, 2022
Dip is a simple Dependency Injection Container.

Dip is a simple Dependency Injection Container. It's aimed to be as simple as possible yet p

Olivier Halligon 949 Jan 3, 2023
Tranquillity is a lightweight but powerful dependency injection library for swift.

DITranquillity Tranquillity is a lightweight but powerful dependency injection library for swift. The name "Tranquillity" laid the foundation in the b

Ivlev Alexander 393 Dec 24, 2022
Typhoon Powerful dependency injection for Cocoa and CocoaTouch.

Typhoon Powerful dependency injection for Cocoa and CocoaTouch. Lightweight, yet full-featured and super-easy to use. Pilgrim is a pure Swift successo

AppsQuick.ly 2.7k Dec 14, 2022
DIContainer Swift is an ultra-light dependency injection container made to help developers to handle dependencies easily. It works with Swift 5.1 or above.

?? DIContainer Swift It is an ultra-light dependency injection container made to help developers to handle dependencies easily. We know that handle wi

Victor Carvalho Tavernari 10 Nov 23, 2022
The Cocoa Dependency Manager.

CocoaPods: The Cocoa dependency manager CocoaPods manages dependencies for your Xcode projects. You specify the dependencies for your project in a sim

null 13.9k Dec 30, 2022
A simple, decentralized dependency manager for Cocoa

Carthage Carthage is intended to be the simplest way to add frameworks to your Cocoa application. Carthage builds your dependencies and provides you w

Carthage 14.7k Dec 29, 2022
A simple way to handle dependency injection using property wrappers

Injektion Introduction A simple way to handle dependency injection using propert

Andrew McGee 2 May 31, 2022
Reliant - Nonintrusive Objective-C Dependency Injection

Reliant Reliant is a Dependency Injection (DI) framework for Objective-C, both for OS X and iOS. Its goal is to make its use as simple as possible, wh

AppFoundry 52 Oct 14, 2022