Linting licenses

Dependencies are at the core of programming. In iOS, we often use Cocoapods to manage dependencies in our projects. But if we are not careful, we can add a dependency that is not free to use, and expose our company to legal issues later. That’s why it’s really important to verify the LICENSE files of each dependency before adding it.

But we all know that humans make mistakes, so in this article, we’ll cover how to automate the process of verifying each dependency.

Note: If you want to know more about the different kinds of licenses, go check https://choosealicense.com/.

Listing the licenses

First of all, if you want to quickly see the state of the licenses in your project, I created a gem called ADLicenselint that lists the licenses for each pod in your Podfile.

To use it, install the gem and run ad_licenselint in the directory that contains the Podfile.

Note: by default the gem prints the dependencies that have licenses not free to use, but you can print the whole list with the --all option:

> cd /path/to/Podfile
> ad_licenselint --all

+-------------------+----------------------------+----------------------------------------------------+
| Pod               | License                    | Source                                             |
+-------------------+----------------------------+----------------------------------------------------+
| Alamofire         | MIT                        | https://github.com/Alamofire/Alamofire             |
| Firebase          | Apache                     | https://github.com/firebase/firebase-ios-sdk       |
| ObjectivePGP      | BSD for non-commercial use | https://github.com/krzyzanowskim/ObjectivePGP      |
| SwiftGen          | MIT                        | https://github.com/SwiftGen/SwiftGen               |
| SwiftLint         | MIT                        | https://github.com/realm/SwiftLint                 |
+-------------------+----------------------------+----------------------------------------------------+

You can check all the other options on the github page directly.

Under the hood, the gem uses the plists generated by Cocoapods during the pod install. The plists are located here: Pods/Target\ Support\ Files/Pods-MyApp/Pods-MyApp-acknowledgements.plist.

Here is an example of one entry in the plist for the pod Alamofire:

<dict>
  <key>FooterText</key>
  <string>Copyright (c) 2014-2020 Alamofire Software Foundation (http://alamofire.org/) ...</string>
  <key>License</key>
  <string>MIT</string>
  <key>Title</key>
  <string>Alamofire</string>
  <key>Type</key>
  <string>PSGroupSpecifier</string>
</dict>

The gem simply parses this file and extracts the informations for each pod. By default, the licenses automatically validated are MIT, Apache, Apache 2.0 and BSD. Every license that is not included in this list will be considered unsafe.

Using it with fastlane

Along with the gem, you can find a fastlane plugin that can be used in your Fastfile.

First add the plugin to your Pluginfile:

# fastlane/Pluginfile
gem 'fastlane-plugin-ad_licenselint'

Then call the ad_licenselint action with your parameters:

# fastlane/Fastfile
lane :lint_pods do
  ad_licenselint(
    format: "md",       # markdown format
    path: "."           # the Podfile is in the current directory
    all: true,          # display dependencies without warnings
    only: ["Alamofire"] # only lint Alamofire
  )

  # the formatted string is in the lane_context
  summary = lane_context[:AD_LICENSE_LINT_SUMMARY]
  # the hash of the result is also in the lane context
  report = lane_context[:AD_LICENSE_LINT_REPORT]
  ...
end

The summary is a string and can be printed as is, whereas the report hash has the following structure:

{
  entries: [
    {
      pod_name: "Alamofire",
      license_content: "Copyright (c) 2014-2020 Alamofire Software Foundation (http://alamofire.org/) ...",
      license_name: "MIT",
      source_url: "https://github.com/Alamofire/Alamofire"
    },
    ...
  ]
}

Automation

Once we have access to the license linter in our lanes, we can imagine some nice use cases.

Pull request comment

Let’s say you want to post an automatic message to your pull request if you detect that a pod with a commercial license has been used.

For instance, let’s add ObjectivePGP (with a commercial license) and update ADUtils (with a MIT license) in our Podfile.

diff --git a/Podfile b/Podfile
index ef25df6..d295727 100644
--- a/Podfile
+++ b/Podfile
@@ -16,7 +16,7 @@ target 'MyApp' do
   pod 'Alamofire', '~> 5.0'
-  pod 'ADUtils', '~> 10'
+  pod 'ADUtils', '~> 11'
   pod 'Firebase/Analytics', '~> 6.20'
@@ -24,6 +24,7 @@ target 'MyApp' do
   pod 'OverlayContainer', '~> 3.3'
+  pod 'ObjectivePGP', '~> 0.15'
 end

Once the pull request is pushed to Github, here is an example of an automatically posted message:

Example of automatic comment
Example of automatic comment

With this warning, the developer can check if he really wants to use this dependency or not.

The code to automatically post this message in a pull request can be found here. In this case we use the formatted report summary available in lane_context[:AD_LICENSE_LINT_SUMMARY] after we called ad_licenselint.

Note: what is interesting here is to use the gem cocoapods-core to get the list of updated pods for a branch. This library is useful if you want to manipulate the Podfile and Podfile.lock backing models.

The following snippet shows an example of what you can do with the library:

podfile = Pod::Podfile.from_file("/path/to/Podfile")

# if you want the list of pods contained in the podfile
all_pod_names = podfile.dependencies.map(&:name)

# if you want to compare the podfile
# to the Podfile.lock from a previous revision
lockfile = Pod::Lockfile.from_file(Pathname("/path/to/Podfile.lock"))
changes = lockfile.detect_changes_with_podfile(podfile)
updated_pods = changes[:added] + changes[:changed]

Pull request review

You could also imagine posting a review directly to the Podfile in the pull request.

For instance if we add the same pod ObjectivePGP, we can create this automatic review:

Example of automatic review
Example of automatic review

The code to automatically post this review in a pull request can be found here. This example uses the report summary hash available in lane_context[:AD_LICENSE_LINT_REPORT] after we called ad_licenselint.

Note: to interact with the Github api, we use the octokit gem that is really simple to use:

# just create a client and access all the APIs
client = Octokit::Client.new(:access_token => "YOUR TOKEN")

# get all files from a pull request
files = client.pull_request_files(repo_name, pull_request_number)

# post a comment to a pull request
client.add_comment(repo_name, pull_request_number, "My comment")

# post a review to a pull request for the Podfile diff
# the method does not exist by default, so we can use `post` directly
client.post(
  "#{Octokit::Repository.path repo_name}/pulls/#{pull_request_number}/comments",
  {
    commit_id: commit_sha,
    path: 'Podfile',
    body: "My review",
    line: 12,
    side: 'RIGHT'
  }
)

Conclusion

The earlier you know a dependency is not compatible with your project, the earlier you can reject it and search for another alternative. The best option is always to go to the Github page of the pod and check the LICENSE file, but ADLicenselint has been developed to act as an extra safeguard.

The fastlane plugin along with the gem should give you all the tools you need to use it in your workflow if you want to automate the linting process.