Skip to content

TGOS/PleaseFixCocoaPods

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 

Repository files navigation

CocoaPods

NOTE: This document doesn't provide a description of how CocoaPods currently works (unfortunately!), but how I think it should work as this would avoid so many problems I've been running into already and it would follow all recommendations of Apple and does things exactly like Apple is doing them.

Glossary

  • Modul

    A module is a reusable piece of software. There are three kind of modules in OS X:

    • Static Libraries

      They consist of a single file only, their extension is .a and they are linked into the binary at built time, that means it is not necessary to ship them together with the target (e.g. there's no need to embedded them into an application bundle).

    • Dynamic Libraries

    They consist of a single file only, their extension is .dylib and they need to be shipped together with the target as the final linking with the binary happens dynamically at runtime (they are typically embedded into the application bundle).

    • Frameworks

      Frameworks are bundles (directories appearing as a single file), consisting of at least a property list (Info.plist) and a library. They can also contain further files like headers or a module map, as well as further embedded frameworks and dynamic libraries. Their extensions is .framework. The library is usually a dynamic one, in which case the whole framework needs to be shipped together with the target, yet not necessarily all files (the library and the property list are required, as well as any embedded frameworks and libraries, the rest isn't). Frameworks can also contain a static library (that's very rare, but allowed), in that case the framework itself doesn't have to be shipped the application after linking.

CocoaPods currently supports only Static Libraries (default and only works with C/Objective-C) and Frameworks with Dynamic Libraries inside (only when requested, required for Swift). Basically every pod is a module that is linked against and possible embedded into the application you build.

  • Private Headers

    Private Headers of a pod are headers only required to build the pod itself, they are not required to later on use that pod within the target code. (Careful: Xcode also calls certain headers "private" as well, but these are not the same as the headers that CocoaPods calls private. Private headers of CocoaPods would be named "project headers" in Xcode)

  • Public Headers

    Public Headers are headers that are required to use or integrate the module into a target.

  • Source Code

    The Source Code is the code of the pod or of the target. Typically these are C (.c), Objective-C (.m) or Swift (.swift) files. For CocoaPods all files are considered source files, even header files (.h), which is technically correct as they contain source code, however when the term source code is used in the text below, it doesn't refer to header files.

  • Target

    The Target is what is being build assisted with CocoaPods. It's usually an application but in rare cases my also be a deployable library or framework.

  • Umbrella Header

    An Umbrella Header is a special public module header that includes/imports all the other public module headers. Instead of inculding/importing single public headers, one simply imports the umbrella header. By default it is named exactly like the module itself, so if the module is named MyModule, the umbrella headers would be named MyModule.h.

  • Module Map

    A Module Map is a file that is written in a Module Map Language and it contains information about the module, sub-moduls, header files of the module, exported symbols, dependencies and how all this works together. In most cases the file defines the name of a module, the umbrella header and exports all symbols (wildcard) that this header defines. One speciality of a module map is that if a module map defines an umbrella header, every attempt to import/include any header of the module will cause the compiler to import/include the umbrella header instead.

  • User Imports/Includes

    User Imports/Includes are imports/includes where the the compiler will search for headers in user search paths only. Typically these are used to import/include headers for the current target, e.g. private headers.

  • System Imports/Includes

    System Imports/Includes are imports/includes where the the compiler will search for headers in system search paths only. Typically these are used to import/include headers from other modules and/or the operation system itself.

Integrating Pods (for Developers)

If developers want to import/include a pod named XYZ, they'd typically do so by importing its umbrella header, e.g.

#import <XYZ/XYZ.h>

or in C code

#include <XYZ/XYZ.h>

As these are headers from an external project, "angle brackets" are used because they are not headers of the current target. The samples above work for both, modules that are dynamic frameworks and modules that are static libraries.

If frameworks are used, all pods should have a module map. As a result, one can also import modules using the new syntax

@import XYZ;

and in Swift one just uses

import XYZ

Writing Pods (for Pod Developers)

Pod developers should use user imports/includes without a "namespace" in all their source code and private headers. To include abc.h, they use

#import "abc.h"

or

#inlcude "abc.h"

(NOT "ModuleName/adc.h", <abc.h>, or <ModuleName/abc.h>!!!)

Quotes because the header is part of the current target and not from an external module.

However, within all public headers imports/includes must look exactly as when integrating pods! See above. That's because public headers will in fact become a part of some target and there only these kind of imports/includes are guaranteed to work. So if blah.m imports priv.h, it will use #import "priv.h" but if the public header pubA.h imports pubB.h, it must use #import <XYZ/pubB.h>, otherwise building the pod may still work but intgrating it will fail.

Pod developers don't have to write an umbrella header themselves. CocoaPods automatically creates one for them if required. The name is the name of the module itself (the pod name) and the content are import statements for all public headers of the pod.

However, if developers want to have more control about the content of the umbrella header, they can also write one themselves. Just having a public header named like the module makes CocoaPods recognize this header as an umbrella header. In that case CocoaPods may only write a module map for the developer and uses the umbrella header "as is".

If developers need even more control, they can even write the module map themselves and tell CocoaPods where to find it. In that case CocoaPods will create neither a module map nor an umbrella header and that way the umbrella header can also have a different name than the module (pod) itself, if that is desired. In that case it's all entirely up to the pod developer.

Pod developers writing their own umbrella headers MUST NOT ever import the umbrella header themselves, it is only for using the module, not for building it!

For the Developers of CocoaPods itself

When building the pod itself, all private headers must be in the users header search path in Xcode - which they are by default if they are added to the Xcode project itself.

Public headers or symlinks to public headers must exist in a directory named after the module and the parent directory of that directory must be in the system search paths to make the import scheme of above work.

E.g. you have a module XYZ with the public headers pubA.h and pubB.h and as well as the private headers privK.h and privL.h, the directory structure could look as follows:

Pods/XYZ/Pod:
  fileA.m
  fileB.m
  pubA.h
  pubB.h
  privK.h
  privL.h
  
Pods/Headers/Public/XYZ/XYZ:
  pubA.h --> ../../../XYZ/Pod/pubA.h
  pubB.h --> ../../../XYZ/Pod/pubB.h

Doing the same with the private headers is actually not required. One could do the following:

Pods/Headers/Private/XYZ:
  privK.h --> ../../../XYZ/Pod/privK.h
  privL.h --> ../../../XYZ/Pod/privL.h

and then add Pods/Headers/Private/XYZ to the users header search path, but it serves no meaningful purpose, at least not in a flat header hierarchy.

When building the pod itself, the directory Pods/Headers/Public/XYZ must be in the system header search paths, otherwise imports like

  #import <XYZ/pubA.h>
  #import <XYZ/pubB.h>

will not work, while private headers from Pods/XYZ/Pod will work automatically as long as they are part of the Pod project; which they always are! Fiddling around with USER_HEADER_SEARCH_PATHS is never required, neither when building the Pod, nor when using the Pod in a target project.

The Umbrella Header

If there is a public header named XYZ.h, this header shall be treated at the umbrella header and used unmodified. If no such header is found and umbrella header is required, CocoaPods should create a public headers XYZ.h and for every other public header add a

#import <XYZ/header.h>

Never use imports without namespace (<header.h>), user imports ("header.h"), or import anything from any other module (like importing <Foundation/Foundation.h> or similar), that is not required in an umbrella header and if one of the public header needs that, it must import the headers of other modules itself.

If a module map is not generated but provided by the pod itself, an umbrella header is not generated either. If a module map is generated, a headers named after the module is searched and if found, used as the umbrella headers. If no such headers is found, an umbrella header is generated together with the module map.

Complex Header Structures (header_mappings_dir)

Touching USER_HEADER_SEARCH_PATHS is always wrong, except for cases where header_mappings_dir is being used! If the pod wants to preserve some more complex import structure, (header_mappings_dir), make sure it is replicated in Pods/MappingsDir/XYZ and add this directory to USER_HEADER_SEARCH_PATHS:

USER_HEADER_SEARCH_PATHS = $(inherited) "$(PODDIR)/Pods/MappingsDir/XYZ"

to make sure mapping dir imports with quotes will work, e.g.

#import "foo/bar/foorbar.h"

must work. It would now work if foo, bar and foobar.h would all be added to the Pods project, as Xcode doesn't care about subdirectories.

Additional Behavior

Behavior when Pods are Static Libs

When static libraries are used, there is no need to tag any header files in pod project as "public". This will only cause them to be copied to the build dir, cluttering that directory up and causing conflicts if two different pods have an equally named public header file, as in the build dir the structure is always flat and this directory is also within the header search paths in Xcode!

Instead, when building the actual target, add the public headers of all integrated pods to the system header search path:

HEADER_SEARCH_PATHS = ($inherited) "$(PODDIR)/Pods/Headers/Public/XYZ" ...

this is enough to make #import <XYZ/header.h> imports work (e.g. from within the umbrella header), there are no name conflicts as all imports are prefixed by the module name and thus uniquely identifying one specific header, and finally linking the target against the static library from the build dir.

Targets don't need any access to private headers or implementation files of a pod. So that's really all that is required and is much nicer than the current behavior.

Copying public headers to the build dir and referencing them from there is a bad idea. As long at the pod hasn't been built, the headers are not found and Xcode may show errors for the current target. However, when building, the target will build just fine, as the pod is build first, the headers are copied and thus also found when building the target. After that no errors are shown by Xcode anymore, but the errors will return whenever the build dir is cleaned completely.

If it is desired to copy public files to build dir, e.g. for easy archiving of all build artifacts, they should be copied to a subdirectory, like include/ModuleName, and not get referenced by any Xcode build setting!

Behavior when Pods are Frameworks

When building a framework, it is really required to mark public headers as public in the Pod project to make sure Xcode copies them to the target directory, which is not the build dir, but the headers dir within the framework directory. Unlike in case of static libraries, there is no need to fiddle around with any search paths settings at all, just linking against the framework will make imports work as desired, expected and explained above.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published