This document presents how to start developing in the File Extractor project. It aims at answering following questions:
- What are main components of FileExtractor?
- What is the FileExtractor architecture?
- What are the dependencies?
- Where to find particular functionalities?
- How is FileExtractor distributed?
The basic idea behind the FileExtractor is to be the interface between Xcode build system configuration and Swift compiler. It consumes the Xcode project files such as .xcodeproj
and .xcworkspace
, finds all the source files that should be obfuscated, identifies the build configuration, dependencies that should be linked and other metadata, and writes it down in the Files.json
file.
The FileExtractor composes of two parts: Swift launcher and Ruby tool.
The launcher is a Swift command line tool responsible for launching the FileExtractor Ruby code. Its sources are in the packaging
directory. It contains the Package.swift
file for dependencies and build instructions and Sources
directory for the actual code.
The actual tool is written in Ruby. It also composes of two parts: the launcher and the code providing the functionalities. The Ruby launcher (separate from the Swift launcher) is in bin
directory. The actual functionalities for the tool are in the lib
directory.
Why do we need two launchers? Because they're meeting different needs:
-
the Swift launcher is responsible for executing the Ruby launcher with the proper Ruby interpreter. After the project is build to its distribution form, it takes the path to
lib/ruby/bin
Travelling Ruby 2.2.2 interpreter and executed the Ruby launcher with it. Please consult distribution section for more details. -
the Ruby launcher is responsible for properly linking the dependencies to the actual tool. It detects whether it's used in the development or distribution environment and sets the paths to dependencies accordingly. Then it executes the actual tool passing all the command-line arguments to it.
The actual tool from the lib
directory consists of the file-extractor.rb
file that imports the command line parsing. The command-line parsing from lib/file-extractor/command.rb
is the starting point for the tool.
Launchers, both Swift one and Ruby one, are best thought of as the part of infrastructure. Therefore when writing about the tool architecture I think of the architecture of code in the lib/file-extractor
directory.
The architecture is a simple command-line driven app that parses the execution similarly to the way that the http server would parse a request:
-
The starting point is in
lib/file-extractor/command.rb
file which checks whether the executions is valid (has all the required parameters). The most important parameter is the path under which we should expect the Xcode project. -
The second step is to identify and recognize the Xcode project files in the provided directory. If the project is in one of the supported configurations, such as the single-xcodeproj one or the Cocoapods one, then all the required data for further analysis (such as paths to the Xcode project files) is extracted. It's the responsibility of
XcodefilesDeterminer
fromlib/file-extractor/xcodefiles_determiner.rb
andXcworkspaceExtractor
fromlib/file-extractor/xcworkspace_extractor.rb
. -
The third step is to gather all the data required for the semantic analysis in the compiler tools. The
DataExtractor
fromlib/file-extractor/data_extractor.rb
is used for that. It internally calls two additional tools:XcodeprojExtractor
fromlib/file-extractor/xcodeproj_extractor.rb
andModulesExtractor
fromlib/file-extractor/modules_extractor.rb
.XcodeprojExtractor
is responsible for extracting the information from thexcodeproj
file. It usesxcodebuild
to get all the defaultXcode
settings andCocoapods/Xcodeproj
to get the information from the actual file.ModulesExtractor
is responsible for finding all the modules that are imported in the Swift source files that should be obfuscated. It usesswiftc
for that.
-
After all the data is being gathered,
DataExtractor
ensures it's consistent with the requirements of Swift compiler tools. The class responsible for the check is calledDataIntegrityChecker
fromlib/file-extractor/data_integrity_checker.rb
. If it determines that the data is not consistent, it causes the abort with error message. Otherwise the data is written to the terminal output and to file. -
If the data is consistent, the final step is to build all the required dependencies (if any were identified in the second on third step). The class responsible for it is called
DependencyBuilder
fromlib/file-extractor/dependency_builder.rb
.
There are four types of dependencies in the FileExtractor tool:
-
Swift launcher dependencies are the Swift libraries responsible for helping with parsing the command-line arguments and launching the Ruby tool. These dependencies are written in
packaging/Package.swift
. -
Ruby tool has Ruby dependencies (gems) for easier command-line management and Xcode project files parsing. They are defined in
Gemfile
. There are two groups: the production ones (actually used in the production code) and the development ones, used in the development to run tests, build and package the tool etc. -
Ruby tool has also some implicit external dependencies. It expects that in the user's terminal PATH there'll be a number of tools available. They are:
xcodebuild
xcrun
swiftc
These are all parts of Xcode command-line tools, so as long as the usual Xcode installation is available, it's path properly set viaxcode-select
and the command-line tools installed (you can do that byxcode-select --install
), everything will be provided.
-
Building and packaging has it's own set of dependencies. For the actual building we use
rake
which is defined as part of Ruby dependencies in theGemfile
. To use the proper version of dependencies, however, we need to ensure that the proper version of Ruby is used for building. We userbenv
for that. After the project if build with proper dependencies, we must ensure it's also run with the prover Ruby version. We don't want to put any restrictions on the user's machine setup, so we're using Travelling Ruby to provide our own Ruby interpreter.
-
Paths
Please consult Swift launcher atpackaging/Sources/file-extractor/main.swift
and Ruby launcher atbin/file-extractor
. -
Building and distribution
Please change theRakefile
. -
Tests
Please write them in thespec
directory. -
Command-line interface
It's defined bylib/file-extractor/command.rb
file. It usesCLAide
gem to build command-line iterface. There is an additional class calledArgumentsDecorator
that helps to provide the same interface style as LLVM command line conventions. -
Project structure identification
Please seelib/file-extractor/xcodefiles_determiner.rb
andlib/file-extractor/xcworkspace_extractor.rb
. The latter uses theXcodeproj
gem for parsing the workspace file. -
Data extraction
Please seelib/file-extractor/data_extractor.rb
for that. It useslib/file-extractor/xcodeproj_extractor.rb
for extracting data from Xcode project file andlib/file-extractor/modules_extractor.rb
to extract additional data from Swift source code files. They use theXcodeproj
gem,xcodebuild
,xcrun
andswiftc
as dependencies. -
Json file format
Please seelib/file-extractor/files_json_struct.rb
. The structs defined there are used by data extraction classes to store all the required information. -
Data verification
Please seelib/file-extractor/data_integrity_checker.rb
for that. -
Dependency building
Please consultlib/file-extractor/dependency_builder.rb
.
The tool, while being written mainly in Ruby, is being distributed in a way that should not impose any restrictions on the user. They might have even no Ruby installed on their machine. To achive it, the distribution script is written in the Rakefile
. It creates either a directory (with option DIR_ONLY=1
) of name file-extractor-1.0.0-osx
with the distribution version of the tool or the archive file-extractor-1.0.0-osx.tar.gz
for distribution. It contains all the dependencies in the right version (compatible with Ruby 2.2.2), the built Swift launcher, the Ruby interpreter from Travelling Ruby.