<![CDATA[Stories by Oswaldo Rubio on Medium]]> https://medium.com/@OswaldoRubio?source=rss-4f39b75aa490------2 https://cdn-images-1.medium.com/fit/c/150/150/1*DoysEgGjhwGHlRljAkZtGw.jpeg Stories by Oswaldo Rubio on Medium https://medium.com/@OswaldoRubio?source=rss-4f39b75aa490------2 Medium Wed, 30 Oct 2024 04:00:56 GMT <![CDATA[How to use Swift Package Manager products from Cocoapods]]> https://tech.new-work.se/how-to-use-swift-package-manager-products-from-cocoapods-96f225a12a20?source=rss-4f39b75aa490------2 https://medium.com/p/96f225a12a20 Fri, 19 May 2023 09:48:48 GMT 2023-05-19T09:48:48.814Z Roughly one year ago, New Work SE’s iOS Platform Team started a proof of concept about how we could move from our existing XING iOS App, based in over one hundred Cocoapods internal libraries and other 15 external dependencies, into a clean and modern project based only in SwiftPM packages.

If you’re interested, I explained how we simplified the previous graph dependency in a previous article here in order to tackle this project in an easier way.

How to control your iOS dependencies

Now, I want to share with you, how the process of migration to SwiftPM can be easier than expected if you are an iOS developer who works in a large iOS project with:

  • a Cocoapods setup with large number of Development Pods
  • a local Swift Package where you want to move these Pods as a targets
Photo by Michał Parzuchowski on Unsplash

Which was the problem we wanted to solve?

We started defining our migration plan, which was created by using our jungle tool in a Xcode Playground, to explore the dependency graph of the project and build the required steps for that migration in a way that only modules that were dependant of already migrated modules can be also migrated. Easy.

But then, only a few days after we started the migration process, we arrived to this kind of situation you see in the image below where only one or a reduced number of modules were used in our still not migrated Pods.

Can we use our already migrated modules in the SPM Package in our still active Pods?

We asked ourselves, “Is there a way to start consuming these new migrated modules into our legacy Cocoapods Modules setup without having duplicated modules in both package managers?”

Also, we wanted to start having results as soon as possible in the final iOS App. Integrating some of these migrated libraries in the final binary would allow us to start monitoring the behaviour in our APM solution and enjoying the benefits without waiting for the complete migration and (maybe) having a hard switch to SPM process.

Our first approach was to use this pod_install hook that configures a Swift Package Manager dependency into an existing Xcode Project (the ones that Cocoapods creates for us). But then, this error was happening when we built the app:

How we solved this problem?

We didn’t want to move to SwiftPM and create a new dependency with the Xcodeproj library that is used by Cocoapods. So, why not using the new Xcodeproj (lowercased P) Swift version by Tuist to configure our Pod Xcode Project and have an easier way to properly configure these Xcode Projects.

We defined 2 differents commands for local and remote Swift packages:

import XcodeProj
import PathKit
import ArgumentParser

struct AddLocalPackageCommand: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "addLocal",
abstract: "Injects a local SPM Package"
)

@Option(help: "The Pod project directory.")
var projectPath: String

@Option(help: "The SwiftPM package directory.")
var spmPath: String

@Option(help: "The product from that package to be injected.")
var product: String

@Option(help: "The target to be configured with that dependency")
var targetName: String

func run() throws {
let projectPath = Path(projectPath)
let spmPath = Path(spmPath)
let xcodeproject = try XcodeProj(path: projectPath)
let pbxproj = xcodeproject.pbxproj
let project = pbxproj.projects.first
_ = try project?.addLocalSwiftPackage(path: spmPath, productName: product, targetName: targetName)
try xcodeproject.write(path: projectPath)}
}

struct AddRemotePackageCommand: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "addRemote",
abstract: "Injects a remote SPM Package"
)

@Option(help: "The Pod project directory.")
var projectPath: String

@Option(help: "The SwiftPM package URL.")
var spmURL: String

@Option(help: "The product from that package to be injected.")
var product: String

@Option(help: "The exact version to be used.")
var version: String

@Option(help: "The target to be configured with that dependency")
var targetName: String

func run() throws {
let projectPath = Path(projectPath)
let xcodeproject = try XcodeProj(path: projectPath)
let pbxproj = xcodeproject.pbxproj
let project = pbxproj.projects.first
_ = try project?.addSwiftPackage(repositoryURL: spmURL, productName: product, versionRequirement: .exact(version), targetName: targetName)
try xcodeproject.write(path: projectPath)}
}

This way, we could include this Swift CLI tool (we called XcodeSPMI by obvious reasons) in our Package and use it after the Pod installation.

import PackageDescription

let package = Package(
name: "libraries",
products: [
.library(name: "FeatureB", type: .dynamic, targets: ["FeatureB"]),
.executable(name: "XcodeSPMI", targets: ["XcodeSPMI"]),
],
dependencies: [
.package(url: "https://github.com/tuist/XcodeProj.git", .upToNextMajor(from: "8.9.0")),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.2"),
],
targets: [
.target(name: "FeatureB", path: "FeatureB"),
.executableTarget(
name: "XcodeSPMI",
dependencies: [
.product(name: "XcodeProj", package: "XcodeProj"),
.product(name: "ArgumentParser", package: "swift-argument-parser")
])

]
)

Once we removed the dependency of FeatureB in the FeatureA’s .podspec file, this is how we inject (as a post_integrate step in the Podfile) the SPM dependency in the Cocoapods project using the previous .executable product from our Swift Package:

post_integrate do |installer|

# FeatureB
featureB_dependant = ["FeatureA"]

puts "Injecting FeatureB SPM framework into ..."
featureB_dependant.each do |project|
puts " #{project}"
`swift run --package-path libraries XcodeSPMI addLocal --project-path Pods/#{project}.xcodeproj --spm-path ../libraries/ --product FeatureB --target-name #{project}`
end
end

Is this enough? It’s for .binaryTargets but not for regular .targets as the one you can see in the example (FeatureB).

For .binaryTargets, which is the current solution we’re using for these shared modules, we are creating the .xcframework artifacts by using swift-create-xcframework.

In order to been able to remove the No such module ‘FeatureB’ error from your build log for plain .targets, there is an extra step needed during the module step. Looking at the logs we found something was not completely provided to the swift-frontend tool. The missing part was this variable you can find in the Build Setting documentation from Apple (SWIFT_INCLUDE_PATHS) which should also contain the directory where other modules can be found during that building stage.

Import Paths
Setting name: SWIFT_INCLUDE_PATHS
A list of paths to be searched by the Swift compiler for additional Swift modules.

As we can change the build settings for our development pods, that’s what we need to include in our .podspec file:

Pod::Spec.new do |s|
s.name = 'FeatureA'
s.version = '1.0.0'
s.author = 'Oswaldo Rubio'
s.license = 'commercial'
s.homepage = "https://github.com/osrufung/UsingSPMFromCPDemo"
s.source = { git: 'https://github.com/osrufung/UsingSPMFromCPDemo' }
s.summary = "#{s.name} – Version #{s.version}"
s.ios.deployment_target = '15.0'
s.swift_version = '5.7'
s.source_files = "Sources/**/*.swift"
# This resolves the missing SWIFT_INCLUDE_PATHS variable
s.pod_target_xcconfig = { 'SWIFT_INCLUDE_PATHS' => '$(inherited) ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)'}
end

Conclusion

Having this SPM injection into Cocoapods projects allow us to increase the number of Integrated modules (modules that are being linked with the final app) before finishing migrate the pending modules.

We’re having multiple benefits of this “hack”:

  • being able to remove from Cocoapods modules that are already migrated in SPM.
  • also injecting the dependency as a .binaryTarget in .xcframework format reduces the total build time spent locally and time resources and credits in the CI side.
  • Foundational modules shared in both package managers without any kind of library duplication.

We expect to finish this migration process during this year. This is the current migration state right now and I hope to share my thoughts about the whole migration project once we finished with you.

I mentioned before we had over one hundred internal modules in our Podfile, and that’s how the migration process looks today (mid of May 2023). Some of the modules have been deleted (because some features were removed), and other ones have been migrated but still integration in the app is still not possible because some of the modules that are dependant on them are still in the pending to be migrated list.

Visual representation with percentage of migrated, integrated and pending to migrate modules
current SPM migration status in our XING iOS project

If you want to play yourself, the issue can be reproduced and is already solved in this demo project along with a really tiny SwiftPM injector .executableTarget based on XcodeProj. I hope this can be useful for your projects and please share your thoughts or problems in the comments or in the github repository.


How to use Swift Package Manager products from Cocoapods was originally published in New Work Development on Medium, where people are continuing the conversation by highlighting and responding to this story.

]]>
<![CDATA[How to control your iOS dependencies]]> https://tech.new-work.se/how-to-control-your-ios-dependencies-7690cc7b1c40?source=rss-4f39b75aa490------2 https://medium.com/p/7690cc7b1c40 Thu, 15 Sep 2022 13:05:49 GMT 2022-09-15T13:05:49.332Z Measuring Cocoapod based Projects complexity

In large-scale projects (we have one of these at XING) with more than 30 iOS engineers collaborating in different feature-oriented teams, we have the requirement to build, test, and run our iOS App in isolation in a single mobile monolithic repository (monorepo). That’s the reason we divided the app in Cocoapods development pods trying to minimize the dependencies between them as much as possible.

The way you decide to divide and connect all these pods requires you to have a good strategy and discipline about which dependencies are allowed or in which targets as there are no boundaries or rules that prevent you to create what you want.

That’s normally easy when you only have a few pods, but this can be really hard when you have hundreds of them and the impact this can have is not always easy to measure.

a guy with a computer measuring things

This article wants to explain how we as an iOS Platform Team at XING are solving the dependency issues and share with you the tools we use for that.

What’s the problem?

The problem we want to solve is to avoid taking modularization and architectural decisions, that most of the times are small changes, without being conscious of the global impact these changes have on the full project:

  • Increased build time
  • Reduction of module isolation
  • Creation of a more coupled dependency graph

So, these are the solutions we want to provide to solve this problem:

  • to have a scalable solution for project modularization that can support increasing the number of modules, but maintain a stable complexity along the time
  • to provide the iOS community the tools to analyse, compare and decide about good modularity designs based on numbers, not only personal perceptions or beliefs in a simple and consistent way

First approach

We already have some internal tools to generate the graph dependency using the .podspec.json format that Cocopods has, which allows us to see those relations in Graphviz format, so our first idea was to only count the number of modules we have in every graph version.

Let’s see one easy example with a hypothetical app with 3 feature modules and a single library that could contain code needed in some of these 3 modules.

comparison between 2 graphs with different complexity

This can be applied also at the feature level, giving us a rough idea about how many modules or dependencies are needed to build that feature module. The problem with this approach is that it doesn’t express only one of the 2 graphs has a better design.

The one on the left could resolve the dependency between the features and the library through dependency injection that the main app provides or bridge solutions for inter-feature dependency. You can read more about some of the Dependency Injection solutions we have used in our own project in this article.

Second approach

We wanted to have a better representation of this complexity change, and this is how we discover Predicting Subsystem Failures using Dependency Graph Complexities research document from Microsoft which identifies that complexity relation with the failure rate in really large systems. There are also other studies like this one for Google Chrome that also relates the vulnerabilities with the architecture complexity.

They mention multiple metrics there, but all of them are based on the one from Thomas McCabe Cyclomatic Complexity metric, which can be defined in the following way.

“Mathematically, the cyclomatic complexity of a structured program is defined with reference to the control-flow graph of the program, a directed graph containing the basic blocks of the program, with an edge between two basic blocks if control may pass from the first to the second.” (“Cyclomatic complexity — Wikipedia”)

Although normally that’s something used for measuring the complexity of source code flows, as you can see in the previous research studies, can be applied also to component and system graphs like our iOS project.

We compare our complexity using the following two variants or the formula where |E| is the number of edges, |V| is the number of nodes, and |P| is the number of weakly connected components (1 for our case).

  • Simple complexity:
formula: edges minus number of nodes plus weakly connected components
  • MultiEdge complexity
formula: multi-edges minus number of nodes plus weakly connected components

In our case, we decided to use multi-edge complexity because this allows us to see the transitive dependencies that, although you’re not declaring in the .podspec file, you can potentially use.

Jungle; How did we measure our dependency complexity?

We want to share with you Jungle; a tool we at XING have created to parse our dependency graph, and also report the complexity to the developer or push it into our Grafana dashboard for historical analysis.

You can clone it from https://github.com/xing/jungle and collaborate to provide missing features and also if you’re already using Mint, you can install it directly with:

$ mint install xing/jungle

Now let’s see a practical usage and how we can use jungle to decide where it should be the best place to introduce a new third-party dependency in our XING iOS Xcode project.

Depending on our particular project, this could be a list of possible options:

  • APIClient: the library used for all the network-related stuff
  • LibraryBridge: library without dependencies but used abroad all the project
  • FoundationModule: few dependencies, but also used in most of the libraries
  • Feature: the feature module that needs that dependency

The most basic command jungle support is compare, which compares our current complexity against the one in the main branch or HEAD reference.

Also, we show the moduleCount, which is as you can suspect, the number of modules used at that point in the history.

$ jungle compare                                                                                             
[
{
"modules" : 125,
"complexity" : 21066,
"name" : "Current",
"moduleCount" : 125
},
{
"modules" : 125,
"complexity" : 21066,
"name" : "HEAD",
"moduleCount" : 125
},
{
"modules" : 125,
"complexity" : 21066,
"name" : "main",
"moduleCount" : 125
}
]

Let’s iterate on each of the libraries and see the increment of complexity against the main branch for every changed graph. These are the numbers using the following formula:

(current.complexity / main.complexity -1) * 100
  • APIClient: 22026/21066 -1 = +4.5%
  • LibraryBridge: 23360/21066 -1 = +10.8%
  • FoundationModule: 23022/21066–1 = +9.3%
  • Feature: 21127/21066–1 = 0.3%

So, it seems that at least for this case, introducing this dependency only in the feature module has a lower impact on the project complexity. I’m not saying this should be a general rule, because each case could be different, that dependency could be required in more modules, or maybe that feature module is a dependency of other modules. But at least, we can compare and choose which one is the best option.

Creating the graphs

We love the graphs! And although for bigger changes a number is more pragmatic than this, you’ll want to have a more visual representation of your dependencies. That’s something you can get only with this command and graphviz tool:

$ jungle graph --pod SUPI | dot -Tpng -o graph.png && open graph.png
This is an example of one of our features in our XING iOS App with zero complexity.

Showing historical data

Another command you can use to retrieve previous history metrics is history. That’s really fast and this only retrieves the commits where Podfile.lock file has been changed. Every Podspec change modifies this file and this is our source of truth for the graph dependency generation.

$ jungle history . --since '6 days ago' --output-format json
[
{
"revision" : "3151882a333",
"author" : "Anonymous",
"timestamp" : "2022-08-10T13:55:00+02:00",
"complexity" : 21065,
"message" : "a previous commit",
"moduleCount" : 121
},
{
"revision" : "Current",
"timestamp" : "Now",
"complexity" : 22025,
"moduleCount" : 121
}
]

Focusing on a particular Pod

Checking the impact in the global App is nice, but also you can focus on a particular module and filter this historic or current complexity information only for a particular pod (author and commit information has been removed):

$ jungle history --pod XNGProfile --since '4 months ago'
2022-05-09T12:41:59+02:00;0b21c9ff90d;52;1844
2022-05-10T09:31:08+02:00;f55ccdd6145;45;804
2022-05-11T13:37:49+02:00;916cd5ed5aa;44;682
...
..
2022-09-05T16:10:30+02:00;a600ff922db;33;304

As you can see in the last column, this has been a really intensive 4 months period for this module where we have reduced the complexity from 1844 to 304.

Applying Jungle on other open-source iOS Apps

We want to share with you also how this tool works also with other open source projects or even with your projects. The only condition we need (at this moment) is to have a Podfile.lock in your git repository.

This is the Complexity vs. ModuleCount graph for the popular Wordpress App (you can check the source code here):

$ jungle history — since ‘last 2 years’ > wordpress.csv
last 2 years graph for the Wordpress project

Seems something weird happened on 2021.06… 🤔 but we can use this command to check the details and see this commit increased not only the number of pod dependencies but also the complexity.

a detailed image with the commit that increase the complexity in the Wordpress project

Our experience

At this point, and looking at the numbers for the last 3 years in the iOS monorepo repository, we can say that even though the number of modules (on the bottom) has been always increasing, the complexity has been under control in the last year. And now, using these tools and having these numbers in our Grafana dashboard we can be sure this will be in the future too.

a graph for the dependency complexity and the number of modules in the last 3 years

This reduction in the complexity was something we did intentionally because we detected build times were increasing lately but now with jungle we can confirm which actions had a real impact and we can quantify it and see even the commit that produced changes in the project complexity and take decisions based on that.

That being said that we want to continue focusing on including these checks at Pull Request level, giving the developer relevant information to continue improving the modularisation design and also, improving the tool to support other package managers like the Swift Package Manager. If you want to contribute, please check the project and give us feedback.

Also, I want to say really thank you to Shammi Didla and Andreas Koslowski for all the support, knowledge, code, and hours expended on these complexity concepts and tools that I’m showing you here. ❤️


How to control your iOS dependencies was originally published in New Work Development on Medium, where people are continuing the conversation by highlighting and responding to this story.

]]>
<![CDATA[Working from home + VPN tip]]> https://medium.com/@OswaldoRubio/test-f6b12c54a1b3?source=rss-4f39b75aa490------2 https://medium.com/p/f6b12c54a1b3 Thu, 30 Jan 2020 17:21:57 GMT 2020-01-31T06:44:19.138Z How to make your VPN connection let you working from home

The problem

Probably, you’re another developer working from home, and using a better Internet connection that you probably have in your corporate LAN.

That’s my speedtest for Lowi (a lowcost ISP provider in Spain)

The problem is when your client, normally, requires you to use a VPN connection when you’re out of office. 🤷🏻

  • Unstable audio/video connection in your meetings
  • Low bandwith for another downloads (from public hosts)

The solution

What I want is to choose, when to use the VPN connection for an specific IP/hostname (provided by my client) or when use my gateway for all the other internet addresses to avoid these kind of issues.

Using VPN for specific websites/IPs only

This article was what I was looking since a long time ago:

Using VPN for specific websites/IPs only

So, if you use a OpenVPN client like TunnelBlick this is what you need in your configuration file:

route-nopull
route 192.168.1.100
route 192.168.1.101

This first line prevents to forward ALL the traffic to use the VPN connection. The next ones, create a rule for each IP address you want to go through the VPN gateway.

As you’re indicating IP addresses and not hostnames, you’ll need to change your /etc/hosts file. This can be easily managed using apps like https://github.com/oldj/SwitchHosts

With this, you can switch your host file easily.

And this is how I resolved this issue. Not the best way, because you need to add each internal address into the VPN config file and the host file, but it will work if you don’t need too much different addresses in your internal Corporative network.

]]>