This document is about Jazzy's Objective-C documentation generation.
It's intended for users who are having problems after trying to follow the examples in the README. It gives some solutions to common problems and explains how the system works to help users work through uncommon problems.
- How it works
- Common problems:
Jazzy uses libclang
to generate docs for Objective-C projects. You can think
of this as running some parts of the clang
compiler against a header file.
Jazzy refers to this header file as the umbrella header but it does not have
to be the umbrella header from a Clang module: it's just the header file that
includes everything to be documented.
This means there are two problems to solve:
- What
clang
flags are required; and - How to pass them to the tools.
Jazzy has two modes here: a smart mode that covers 90% of projects and a direct mode where the user provides all the flags.
Important: Jazzy does not use any Objective-C build settings from your Xcode project or
Package.swift
. If your project needs special settings such as#define
s then you need to repeat those in the Jazzy invocation.
Passing a basic set of clang
flags looks like this:
jazzy ...
--objc
--build-tool-arguments
--objc,MyProject/MyProject.h,--,-x,objective-c,
-isysroot,$(xcrun --show-sdk-path),
-I,$(pwd),
-fmodules
The --build-tool-arguments
are arguments to sourcekitten
. Everything after
the --
are the clang
flags that will be used with the header file given
before the --
.
You can try these flags outside of Jazzy's environment:
clang -c -x objective-c -isysroot $(xcrun --show-sdk-path) -I $(pwd) MyProject/MyProject.h -fmodules
(The -c
stops clang
from trying to link an executable.)
This is a good method of experimenting with compiler flags to get a working build without getting bogged down in the Jazzy and SourceKitten layers.
The smart mode takes the variable parts of the basic set of flags and maps them from Jazzy flags:
jazzy ...
--objc
--umbrella-header MyProject/MyProject.h
--framework-root $(pwd)
[--sdk <sdk name>]
The --umbrella-header
maps directly to the file passed to sourcekitten
.
The --framework-root
is used for the -I
include path, as well as every
directory beneath it, recursively. So if your project structure looks like:
MyProject/
Sources/
Main/
Extension/
... and you pass --framework-root MyProject
, then the -I
flags passed to
clang
are -I MyProject -I MyProject/Sources -I MyProject/Sources/Main -I MyProject/Sources/Extension
. This feature helps some projects resolve
#include
directives.
Finally the --sdk
option is passed through instead of the default macosx
to
find the SDK.
Do not mix modes. For example do not set both --umbrella-header
and
--build-tool-arguments
. Jazzy does not flag this as an error for
historical compatibility reasons, but the results are at best confusing.
For example fatal error: module 'UIKit' not found
.
This means Jazzy is using the wrong SDK: the default is for macOS which does
not have UIKit
. Use --sdk iphonesimulator
.
For example fatal error: 'MyModule/SomeHeader.h' file not found
.
This means clang
is not being passed the right flags to resolve a #include
/
#import
.
Start by finding the header file in the filesystem. You might be able to fix
the problem just by adding extra -I <path>
flags.
Usually though the problem is that Xcode has done something clever that needs to be worked around or replicated.
Xcode uses technology including Clang header maps to let files be found using
lines like #import <ModuleName/Header.h>
even when there is no such
filesystem directory.
To make the Jazzy build work you need to make these #include
s work. The
solution depends on your project structure. Some suggestions in rough order
of complexity:
-
Use symlinks to create an equivalent directory tree. For example if
Header.h
is insideSources/include
then symlink that directory toModuleName
and pass-I $(pwd)
. -
Copy/link your header files into a throwaway directory tree that matches the required structure and is used just for building docs.
-
Create a 'docs header file' separate to the framework's regular umbrella header that contains only filesystem-correct
#import
s. -
If you are happy to build the framework project before generating docs and all the problematic paths have the form
ModuleName/PublicHeader.h
then haveclang
resolve those includes to the built framework by passing-F <path of directory containing ModuleName.framework>
. -
If you are happy to build the project before generating docs then you may be able to use the header maps Xcode has generated. Check the build log in Xcode to find them and the invocation syntax.
-
Manually construct an equivalent header map. This is complex not least because Apple does not make tools or proper documentation available. An open-source starting point.
For example ...open4.rb:49:in `exec': Argument list too long - [...]/bin/sourcekitten (Errno::E2BIG)
Can also manifest as 'generally weird' errors such as sourcekitten
just
crashing and fatal error: could not build module 'Foundation'
.
This means --framework-root
is trying to add too many include directories:
there are too many subdirectories of its parameter. If you cannot change this
to something more specific that works then you need to use Jazzy's
direct mode to pass in the correct directories.
If you write an enum case with a doc comment followed by an enum case without a doc comment, then both get the same doc comment.
This seems to be a bug in libclang
. The only workaround is to add the missing
doc comment.
This usually means the clang
flags are malformed in a way that is ignored by
libclang
but not by the Swift Objective-C importer.
One easy way to accidentally do this is passing -I
without a path, for
example --build-tool-flags ...,-I,-I,Headers
,....
This also sometimes happens if you are frequently switching back and forth between some Swift / Xcode versions -- it's a bug somewhere in the Apple tools. The bad state goes away with time / reboot.
Jazzy finds the Swift version of an Objective-C API using the SourceKit
source.request.editor.open.interface.header
request on the header file that
contains the declaration, passing in the same clang
flags used for the
umbrella header. See the code.
This means that each header file needs to compile standalone, without any implicit dependencies. For example:
MyModule.h // umbrella, includes MyClass.h then Utils.h
MyClass.h // @interface MyClass ... @end
Utils.h // void my_function( MyClass * myClass);
Here, Utils.h
has an implicit dependency on MyClass.h
that is normally
satisfied by the include order of MyModule.h
. One fix that allows Utils.h
to compile standalone is to add @class MyClass;
.
The NS_SWIFT_NAME
macro is mostly used to give an Objective-C API a
different name in Swift. There are no known problems with this part.
The macro can also be used to change the 'structure' of an API, for example make a C global function appear as a member function in Swift, or make a C class appear as a nested type in Swift.
Jazzy doesn't understand or communicate these structural changes: you'll need to explain it in doc comments.