Skip to content

Commit

Permalink
[InstallAPI] Add --extra* and --exclude* cli options for header input (
Browse files Browse the repository at this point in the history
…#86522)

InstallAPI takes a json list of headers that is typically generated from
a build system like Xcode based on a project's attributes. Sometimes,
maintainers may want to alter this for tapi input. Using e.g.
`--extra-public-headers`, users can manipulate what headers will be used
for TBD file generation.
  • Loading branch information
cyndyishida committed Mar 25, 2024
1 parent 3e3f0c3 commit 2d40f17
Show file tree
Hide file tree
Showing 23 changed files with 3,930 additions and 2 deletions.
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticInstallAPIKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ let CategoryName = "Command line" in {
def err_cannot_write_file : Error<"cannot write file '%0': %1">;
def err_no_install_name : Error<"no install name specified: add -install_name <path>">;
def err_no_output_file: Error<"no output file specified">;
def err_no_such_header_file : Error<"no such %select{public|private|project}1 header file: '%0'">;
def warn_no_such_excluded_header_file : Warning<"no such excluded %select{public|private}0 header file: '%1'">, InGroup<InstallAPIViolation>;
def warn_glob_did_not_match: Warning<"glob '%0' did not match any header file">, InGroup<InstallAPIViolation>;
} // end of command line category.

let CategoryName = "Verification" in {
Expand Down
54 changes: 52 additions & 2 deletions clang/include/clang/InstallAPI/HeaderFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#ifndef LLVM_CLANG_INSTALLAPI_HEADERFILE_H
#define LLVM_CLANG_INSTALLAPI_HEADERFILE_H

#include "clang/Basic/FileManager.h"
#include "clang/Basic/LangStandard.h"
#include "clang/InstallAPI/MachO.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Regex.h"
Expand Down Expand Up @@ -56,6 +58,10 @@ class HeaderFile {
std::string IncludeName;
/// Supported language mode for header.
std::optional<clang::Language> Language;
/// Exclude header file from processing.
bool Excluded{false};
/// Add header file to processing.
bool Extra{false};

public:
HeaderFile() = delete;
Expand All @@ -71,17 +77,48 @@ class HeaderFile {
StringRef getIncludeName() const { return IncludeName; }
StringRef getPath() const { return FullPath; }

void setExtra(bool V = true) { Extra = V; }
void setExcluded(bool V = true) { Excluded = V; }
bool isExtra() const { return Extra; }
bool isExcluded() const { return Excluded; }

bool useIncludeName() const {
return Type != HeaderType::Project && !IncludeName.empty();
}

bool operator==(const HeaderFile &Other) const {
return std::tie(Type, FullPath, IncludeName, Language) ==
return std::tie(Type, FullPath, IncludeName, Language, Excluded, Extra) ==
std::tie(Other.Type, Other.FullPath, Other.IncludeName,
Other.Language);
Other.Language, Other.Excluded, Other.Extra);
}
};

/// Glob that represents a pattern of header files to retreive.
class HeaderGlob {
private:
std::string GlobString;
llvm::Regex Rule;
HeaderType Type;
bool FoundMatch{false};

public:
HeaderGlob(StringRef GlobString, llvm::Regex &&, HeaderType Type);

/// Create a header glob from string for the header access level.
static llvm::Expected<std::unique_ptr<HeaderGlob>>
create(StringRef GlobString, HeaderType Type);

/// Query if provided header matches glob.
bool match(const HeaderFile &Header);

/// Query if a header was matched in the glob, used primarily for error
/// reporting.
bool didMatch() { return FoundMatch; }

/// Provide back input glob string.
StringRef str() { return GlobString; }
};

/// Assemble expected way header will be included by clients.
/// As in what maps inside the brackets of `#include <IncludeName.h>`
/// For example,
Expand All @@ -93,6 +130,19 @@ class HeaderFile {
std::optional<std::string> createIncludeHeaderName(const StringRef FullPath);
using HeaderSeq = std::vector<HeaderFile>;

/// Determine if Path is a header file.
/// It does not touch the file system.
///
/// \param Path File path to file.
bool isHeaderFile(StringRef Path);

/// Given input directory, collect all header files.
///
/// \param FM FileManager for finding input files.
/// \param Directory Path to directory file.
llvm::Expected<PathSeq> enumerateFiles(clang::FileManager &FM,
StringRef Directory);

} // namespace clang::installapi

#endif // LLVM_CLANG_INSTALLAPI_HEADERFILE_H
1 change: 1 addition & 0 deletions clang/include/clang/InstallAPI/MachO.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ using SymbolSet = llvm::MachO::SymbolSet;
using SimpleSymbol = llvm::MachO::SimpleSymbol;
using FileType = llvm::MachO::FileType;
using PackedVersion = llvm::MachO::PackedVersion;
using PathSeq = llvm::MachO::PathSeq;
using Target = llvm::MachO::Target;
using TargetList = llvm::MachO::TargetList;

Expand Down
2 changes: 2 additions & 0 deletions clang/lib/InstallAPI/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ std::unique_ptr<MemoryBuffer> createInputBuffer(InstallAPIContext &Ctx) {
SmallString<4096> Contents;
raw_svector_ostream OS(Contents);
for (const HeaderFile &H : Ctx.InputHeaders) {
if (H.isExcluded())
continue;
if (H.getType() != Ctx.Type)
continue;
if (Ctx.LangMode == Language::C || Ctx.LangMode == Language::CXX)
Expand Down
51 changes: 51 additions & 0 deletions clang/lib/InstallAPI/HeaderFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "clang/InstallAPI/HeaderFile.h"
#include "llvm/TextAPI/Utils.h"

using namespace llvm;
namespace clang::installapi {
Expand Down Expand Up @@ -34,4 +35,54 @@ std::optional<std::string> createIncludeHeaderName(const StringRef FullPath) {
return Matches[1].drop_front(Matches[1].rfind('/') + 1).str() + "/" +
Matches[3].str();
}

bool isHeaderFile(StringRef Path) {
return StringSwitch<bool>(sys::path::extension(Path))
.Cases(".h", ".H", ".hh", ".hpp", ".hxx", true)
.Default(false);
}

llvm::Expected<PathSeq> enumerateFiles(FileManager &FM, StringRef Directory) {
PathSeq Files;
std::error_code EC;
auto &FS = FM.getVirtualFileSystem();
for (llvm::vfs::recursive_directory_iterator i(FS, Directory, EC), ie;
i != ie; i.increment(EC)) {
if (EC)
return errorCodeToError(EC);

// Skip files that do not exist. This usually happens for broken symlinks.
if (FS.status(i->path()) == std::errc::no_such_file_or_directory)
continue;

StringRef Path = i->path();
if (isHeaderFile(Path))
Files.emplace_back(Path);
}

return Files;
}

HeaderGlob::HeaderGlob(StringRef GlobString, Regex &&Rule, HeaderType Type)
: GlobString(GlobString), Rule(std::move(Rule)), Type(Type) {}

bool HeaderGlob::match(const HeaderFile &Header) {
if (Header.getType() != Type)
return false;

bool Match = Rule.match(Header.getPath());
if (Match)
FoundMatch = true;
return Match;
}

Expected<std::unique_ptr<HeaderGlob>> HeaderGlob::create(StringRef GlobString,
HeaderType Type) {
auto Rule = MachO::createRegexFromGlob(GlobString);
if (!Rule)
return Rule.takeError();

return std::make_unique<HeaderGlob>(GlobString, std::move(*Rule), Type);
}

} // namespace clang::installapi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extern int extraGlobalAPI1;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extern int extraGlobalAPI2;
103 changes: 103 additions & 0 deletions clang/test/InstallAPI/Inputs/Simple/Simple.framework/Headers/Basic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#import <Foundation/Foundation.h>

// Basic class with no super class
@interface Basic1
@end

@interface Basic2 : NSObject
@end

@interface Basic3 : NSObject
@property BOOL property1;
@property(readonly) BOOL property2;
@property(getter=isProperty3) BOOL property3;
@property BOOL dynamicProp;
@end

@interface Basic4 : NSObject {
@public
BOOL ivar1;
@protected
BOOL ivar2;
@package
BOOL ivar3;
@private
BOOL ivar4;
}
@end

__attribute__((visibility("hidden"))) @interface Basic4_1 : NSObject {
@public
BOOL ivar1;
@protected
BOOL ivar2;
@package
BOOL ivar3;
@private
BOOL ivar4;
}
@end

@interface Basic4_2 : NSObject {
@private
BOOL ivar4;
@package
BOOL ivar3;
@protected
BOOL ivar2;
@public
BOOL ivar1;
}
@end

@interface Basic5 : NSObject
+ (void)aClassMethod;
- (void)anInstanceMethod;
@end

@interface Basic6 : NSObject
@end

@interface Basic6 () {
@public
BOOL ivar1;
}
@property BOOL property1;
- (void)anInstanceMethodFromAnExtension;
@end

@interface Basic6 (Foo)
@property BOOL property2;
- (void)anInstanceMethodFromACategory;
@end

__attribute__((visibility("hidden")))
@interface Basic7 : NSObject
@end

@interface Basic7 ()
- (void) anInstanceMethodFromAnHiddenExtension;
@end

@interface Basic8 : NSObject
+ (void)useSameName;
@end

// Classes and protocols can have the same name. For now they would only clash
// in the selector map if the protocl starts with '_'.
@protocol _A
- (void)aMethod;
@end

@interface A : NSObject
- (void)aMethod NS_AVAILABLE(10_11, 9_0);
- (void)bMethod NS_UNAVAILABLE;
@end

@interface Basic9 : NSObject
@property(readonly) BOOL aProperty NS_AVAILABLE(10_10, 8_0);
@end

@interface Basic9 (deprecated)
@property(readwrite) BOOL aProperty NS_DEPRECATED_MAC(10_8, 10_10);
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#import <Foundation/Foundation.h>

// Sub-class an external defined ObjC Class.
@interface ExternalManagedObject : NSManagedObject
- (void)foo;
@end

// Add category to external defined ObjC Class.
@interface NSManagedObject (Simple)
- (int)supportsSimple;
@end

// CoreData Accessors are dynamically generated and have no implementation.
@interface ExternalManagedObject (CoreDataGeneratedAccessors)
- (void)addChildObject:(ExternalManagedObject *)value;
- (void)removeChildObject:(ExternalManagedObject *)value;
- (void)addChild:(NSSet *)values;
- (void)removeChild:(NSSet *)values;
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#import <Foundation/Foundation.h>

// Useless forward declaration. This is used for testing.
@class FooBar;
@protocol FooProtocol;

@protocol ForwardProcotol;

// Test public global.
extern int publicGlobalVariable;

// Test weak public global.
extern int weakPublicGlobalVariable __attribute__((weak));

// Test public ObjC class
@interface Simple : NSObject
@end

__attribute__((objc_exception))
@interface Base : NSObject
@end

@interface SubClass : Base
@end

@protocol BaseProtocol
- (void) baseMethod;
@end

NS_AVAILABLE(10_11, 9_0)
@protocol FooProtocol <BaseProtocol>
- (void) protocolMethod;
@end

@protocol BarProtocol
- (void) barMethod;
@end

@interface FooClass <FooProtocol, BarProtocol>
@end

// Create an empty category conforms to a forward declared protocol.
// <rdar://problem/35605892>
@interface FooClass (Test) <ForwardProcotol>
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extern int otherFrameworkAPI;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Test private global variable.
extern int privateGlobalVariable;

// Test weak private global.
extern int weakPrivateGlobalVariable __attribute__((weak));
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Test private global variable.
extern int otherFrameworkSPI;

0 comments on commit 2d40f17

Please sign in to comment.