diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -464,6 +464,10 @@ "-ftest-module-file-extension argument '%0' is not of the required form " "'blockname:major:minor:hashed:user info'">; +def err_drv_extract_api_wrong_kind : Error< + "header file '%0' input '%1' does not match the type of prior input " + "in api extraction; use '-x %2' to override">; + def warn_slash_u_filename : Warning<"'/U%0' treated as the '/U' option">, InGroup>; def note_use_dashdash : Note< diff --git a/clang/include/clang/Driver/Action.h b/clang/include/clang/Driver/Action.h --- a/clang/include/clang/Driver/Action.h +++ b/clang/include/clang/Driver/Action.h @@ -59,6 +59,7 @@ PreprocessJobClass, PrecompileJobClass, HeaderModulePrecompileJobClass, + ExtractAPIJobClass, AnalyzeJobClass, MigrateJobClass, CompileJobClass, @@ -443,6 +444,19 @@ const char *getModuleName() const { return ModuleName; } }; +class ExtractAPIJobAction : public JobAction { + void anchor() override; + +public: + ExtractAPIJobAction(Action *Input, types::ID OutputType); + + static bool classof(const Action *A) { + return A->getKind() == ExtractAPIJobClass; + } + + void addHeaderInput(Action *Input) { getInputs().push_back(Input); } +}; + class AnalyzeJobAction : public JobAction { void anchor() override; diff --git a/clang/include/clang/Driver/Types.def b/clang/include/clang/Driver/Types.def --- a/clang/include/clang/Driver/Types.def +++ b/clang/include/clang/Driver/Types.def @@ -100,5 +100,5 @@ TYPE("dependencies", Dependencies, INVALID, "d", phases::Compile, phases::Backend, phases::Assemble, phases::Link) TYPE("cuda-fatbin", CUDA_FATBIN, INVALID, "fatbin", phases::Compile, phases::Backend, phases::Assemble, phases::Link) TYPE("hip-fatbin", HIP_FATBIN, INVALID, "hipfb", phases::Compile, phases::Backend, phases::Assemble, phases::Link) -TYPE("api-information", API_INFO, INVALID, "json", phases::Compile) +TYPE("api-information", API_INFO, INVALID, "json", phases::Precompile) TYPE("none", Nothing, INVALID, nullptr, phases::Compile, phases::Backend, phases::Assemble, phases::Link) diff --git a/clang/lib/Driver/Action.cpp b/clang/lib/Driver/Action.cpp --- a/clang/lib/Driver/Action.cpp +++ b/clang/lib/Driver/Action.cpp @@ -26,6 +26,8 @@ case PreprocessJobClass: return "preprocessor"; case PrecompileJobClass: return "precompiler"; case HeaderModulePrecompileJobClass: return "header-module-precompiler"; + case ExtractAPIJobClass: + return "api-extractor"; case AnalyzeJobClass: return "analyzer"; case MigrateJobClass: return "migrator"; case CompileJobClass: return "compiler"; @@ -339,6 +341,11 @@ : PrecompileJobAction(HeaderModulePrecompileJobClass, Input, OutputType), ModuleName(ModuleName) {} +void ExtractAPIJobAction::anchor() {} + +ExtractAPIJobAction::ExtractAPIJobAction(Action *Inputs, types::ID OutputType) + : JobAction(ExtractAPIJobClass, Inputs, OutputType) {} + void AnalyzeJobAction::anchor() {} AnalyzeJobAction::AnalyzeJobAction(Action *Input, types::ID OutputType) diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -59,6 +59,7 @@ #include "clang/Driver/InputInfo.h" #include "clang/Driver/Job.h" #include "clang/Driver/Options.h" +#include "clang/Driver/Phases.h" #include "clang/Driver/SanitizerArgs.h" #include "clang/Driver/Tool.h" #include "clang/Driver/ToolChain.h" @@ -334,10 +335,10 @@ FinalPhase = phases::Preprocess; // --precompile only runs up to precompilation. - } else if ((PhaseArg = DAL.getLastArg(options::OPT__precompile))) { + } else if ((PhaseArg = DAL.getLastArg(options::OPT__precompile)) || + (PhaseArg = DAL.getLastArg(options::OPT_extract_api))) { FinalPhase = phases::Precompile; - - // -{fsyntax-only,-analyze,emit-ast} only run up to the compiler. + // -{fsyntax-only,-analyze,emit-ast} only run up to the compiler. } else if ((PhaseArg = DAL.getLastArg(options::OPT_fsyntax_only)) || (PhaseArg = DAL.getLastArg(options::OPT_print_supported_cpus)) || (PhaseArg = DAL.getLastArg(options::OPT_module_file_info)) || @@ -346,8 +347,7 @@ (PhaseArg = DAL.getLastArg(options::OPT_rewrite_legacy_objc)) || (PhaseArg = DAL.getLastArg(options::OPT__migrate)) || (PhaseArg = DAL.getLastArg(options::OPT__analyze)) || - (PhaseArg = DAL.getLastArg(options::OPT_emit_ast)) || - (PhaseArg = DAL.getLastArg(options::OPT_extract_api))) { + (PhaseArg = DAL.getLastArg(options::OPT_emit_ast))) { FinalPhase = phases::Compile; // -S only runs up to the backend. @@ -3883,6 +3883,7 @@ // Construct the actions to perform. HeaderModulePrecompileJobAction *HeaderModuleAction = nullptr; + ExtractAPIJobAction *ExtractAPIAction = nullptr; ActionList LinkerInputs; ActionList MergerInputs; @@ -3943,6 +3944,12 @@ break; } + if (Phase == phases::Precompile && ExtractAPIAction) { + ExtractAPIAction->addHeaderInput(Current); + Current = nullptr; + break; + } + // Try to build the offloading actions and add the result as a dependency // to the host. if (Args.hasArg(options::OPT_fopenmp_new_driver)) @@ -3960,6 +3967,8 @@ if (auto *HMA = dyn_cast(NewCurrent)) HeaderModuleAction = HMA; + else if (auto *EAA = dyn_cast(NewCurrent)) + ExtractAPIAction = EAA; Current = NewCurrent; @@ -4203,6 +4212,10 @@ return C.MakeAction(Input, OutputTy); } case phases::Precompile: { + // API extraction should not generate an actual precompilation action. + if (Args.hasArg(options::OPT_extract_api)) + return C.MakeAction(Input, types::TY_API_INFO); + types::ID OutputTy = getPrecompiledType(Input->getType()); assert(OutputTy != types::TY_INVALID && "Cannot precompile this input type!"); @@ -4217,8 +4230,7 @@ OutputTy = types::TY_ModuleFile; } - if (Args.hasArg(options::OPT_fsyntax_only) || - Args.hasArg(options::OPT_extract_api)) { + if (Args.hasArg(options::OPT_fsyntax_only)) { // Syntax checks should not emit a PCH file OutputTy = types::TY_Nothing; } @@ -4247,7 +4259,7 @@ if (Args.hasArg(options::OPT_verify_pch)) return C.MakeAction(Input, types::TY_Nothing); if (Args.hasArg(options::OPT_extract_api)) - return C.MakeAction(Input, types::TY_API_INFO); + return C.MakeAction(Input, types::TY_API_INFO); return C.MakeAction(Input, types::TY_LLVM_BC); } case phases::Backend: { @@ -5778,7 +5790,8 @@ // And say "no" if this is not a kind of action clang understands. if (!isa(JA) && !isa(JA) && - !isa(JA) && !isa(JA)) + !isa(JA) && !isa(JA) && + !isa(JA)) return false; return true; diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp --- a/clang/lib/Driver/ToolChain.cpp +++ b/clang/lib/Driver/ToolChain.cpp @@ -359,6 +359,7 @@ case Action::PrecompileJobClass: case Action::HeaderModulePrecompileJobClass: case Action::PreprocessJobClass: + case Action::ExtractAPIJobClass: case Action::AnalyzeJobClass: case Action::MigrateJobClass: case Action::VerifyPCHJobClass: diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -28,6 +28,7 @@ #include "clang/Basic/LangOptions.h" #include "clang/Basic/ObjCRuntime.h" #include "clang/Basic/Version.h" +#include "clang/Driver/Action.h" #include "clang/Driver/Distro.h" #include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/InputInfo.h" @@ -4391,8 +4392,9 @@ // CUDA/HIP compilation may have multiple inputs (source file + results of // device-side compilations). OpenMP device jobs also take the host IR as a // second input. Module precompilation accepts a list of header files to - // include as part of the module. All other jobs are expected to have exactly - // one input. + // include as part of the module. API extraction accepts a list of header + // files whose API information is emitted in the output. All other jobs are + // expected to have exactly one input. bool IsCuda = JA.isOffloading(Action::OFK_Cuda); bool IsCudaDevice = JA.isDeviceOffloading(Action::OFK_Cuda); bool IsHIP = JA.isOffloading(Action::OFK_HIP); @@ -4400,6 +4402,7 @@ bool IsOpenMPDevice = JA.isDeviceOffloading(Action::OFK_OpenMP); bool IsOpenMPHost = JA.isHostOffloading(Action::OFK_OpenMP); bool IsHeaderModulePrecompile = isa(JA); + bool IsExtractAPI = isa(JA); bool IsDeviceOffloadAction = !(JA.isDeviceOffloading(Action::OFK_None) || JA.isDeviceOffloading(Action::OFK_Host)); bool IsUsingLTO = D.isUsingLTO(IsDeviceOffloadAction); @@ -4413,10 +4416,21 @@ }(); InputInfo HeaderModuleInput(Inputs[0].getType(), ModuleName, ModuleName); - const InputInfo &Input = - IsHeaderModulePrecompile ? HeaderModuleInput : Inputs[0]; + // Extract API doesn't have a main input file, so invent a fake one as a + // placeholder. + InputInfo ExtractAPIPlaceholderInput(Inputs[0].getType(), "extract-api", + "extract-api"); + + const InputInfo &Input = [&]() -> const InputInfo & { + if (IsHeaderModulePrecompile) + return HeaderModuleInput; + if (IsExtractAPI) + return ExtractAPIPlaceholderInput; + return Inputs[0]; + }(); InputInfoList ModuleHeaderInputs; + InputInfoList ExtractAPIInputs; InputInfoList OpenMPHostInputs; const InputInfo *CudaDeviceInput = nullptr; const InputInfo *OpenMPDeviceInput = nullptr; @@ -4432,6 +4446,14 @@ << types::getTypeName(Expected); } ModuleHeaderInputs.push_back(I); + } else if (IsExtractAPI) { + auto ExpectedInputType = ExtractAPIPlaceholderInput.getType(); + if (I.getType() != ExpectedInputType) { + D.Diag(diag::err_drv_extract_api_wrong_kind) + << I.getFilename() << types::getTypeName(I.getType()) + << types::getTypeName(ExpectedInputType); + } + ExtractAPIInputs.push_back(I); } else if ((IsCuda || IsHIP) && !CudaDeviceInput) { CudaDeviceInput = &I; } else if (IsOpenMPDevice && !OpenMPDeviceInput) { @@ -4615,6 +4637,10 @@ CmdArgs.push_back("-emit-pch"); } else if (isa(JA)) { CmdArgs.push_back("-verify-pch"); + } else if (isa(JA)) { + assert(JA.getType() == types::TY_API_INFO && + "Extract API actions must generate a API information."); + CmdArgs.push_back("-extract-api"); } else { assert((isa(JA) || isa(JA)) && "Invalid action for clang tool."); @@ -4653,8 +4679,6 @@ } else if (JA.getType() == types::TY_RewrittenLegacyObjC) { CmdArgs.push_back("-rewrite-objc"); rewriteKind = RK_Fragile; - } else if (JA.getType() == types::TY_API_INFO) { - CmdArgs.push_back("-extract-api"); } else { assert(JA.getType() == types::TY_PP_Asm && "Unexpected output type!"); } @@ -7199,6 +7223,8 @@ ArrayRef FrontendInputs = Input; if (IsHeaderModulePrecompile) FrontendInputs = ModuleHeaderInputs; + else if (IsExtractAPI) + FrontendInputs = ExtractAPIInputs; else if (Input.isNothing()) FrontendInputs = {}; diff --git a/clang/test/Driver/extract-api-multiheader-kind-diag.h b/clang/test/Driver/extract-api-multiheader-kind-diag.h new file mode 100644 --- /dev/null +++ b/clang/test/Driver/extract-api-multiheader-kind-diag.h @@ -0,0 +1,14 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: not %clang -target x86_64-unknown-unknown -extract-api %t/first-header.h -x objective-c-header %t/second-header.h 2>&1 | FileCheck %s + +// CHECK: error: header file +// CHECK-SAME: input 'objective-c-header' does not match the type of prior input in api extraction; use '-x c-header' to override + +//--- first-header.h + +void dummy_function(void); + +//--- second-header.h + +void other_dummy_function(void); diff --git a/clang/test/Driver/extract-api-multiheader.h b/clang/test/Driver/extract-api-multiheader.h new file mode 100644 --- /dev/null +++ b/clang/test/Driver/extract-api-multiheader.h @@ -0,0 +1,23 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: %clang -target x86_64-unknown-unknown -ccc-print-phases -extract-api %t/first-header.h %t/second-header.h 2> %t1 +// RUN: echo 'END' >> %t1 +// RUN: FileCheck -check-prefix EXTRACT-API-PHASES -input-file %t1 %s + +// EXTRACT-API-PHASES: 0: input +// EXTRACT-API-PHASES-SAME: , c-header +// EXTRACT-API-PHASES-NEXT: 1: preprocessor, {0}, c-header-cpp-output +// EXTRACT-API-PHASES-NEXT: 2: input +// EXTRACT-API-PHASES-SAME: , c-header +// EXTRACT-API-PHASES-NEXT: 3: preprocessor, {2}, c-header-cpp-output +// EXTRACT-API-PHASES-NEXT: 4: api-extractor, {1, 3}, api-information +// EXTRACT-API-PHASES-NOT: 5: +// EXTRACT-API-PHASES: END + +//--- first-header.h + +void dummy_function(void); + +//--- second-header.h + +void other_dummy_function(void); diff --git a/clang/test/Driver/extract-api.c b/clang/test/Driver/extract-api.h rename from clang/test/Driver/extract-api.c rename to clang/test/Driver/extract-api.h --- a/clang/test/Driver/extract-api.c +++ b/clang/test/Driver/extract-api.h @@ -3,8 +3,8 @@ // RUN: FileCheck -check-prefix EXTRACT-API-PHASES -input-file %t %s // EXTRACT-API-PHASES: 0: input, -// EXTRACT-API-PHASES: , c -// EXTRACT-API-PHASES: 1: preprocessor, {0}, cpp-output -// EXTRACT-API-PHASES: 2: compiler, {1}, api-information +// EXTRACT-API-PHASES-SAME: , c-header +// EXTRACT-API-PHASES-NEXT: 1: preprocessor, {0}, c-header-cpp-output +// EXTRACT-API-PHASES-NEXT: 2: api-extractor, {1}, api-information // EXTRACT-API-PHASES-NOT: 3: // EXTRACT-API-PHASES: END diff --git a/clang/test/SymbolGraph/global_record.c b/clang/test/SymbolGraph/global_record.c --- a/clang/test/SymbolGraph/global_record.c +++ b/clang/test/SymbolGraph/global_record.c @@ -3,7 +3,7 @@ // RUN: sed -e "s@INPUT_DIR@%/t@g" %t/reference.output.json.in >> \ // RUN: %t/reference.output.json // RUN: %clang -extract-api -target arm64-apple-macosx \ -// RUN: %t/input.c -o %t/output.json | FileCheck -allow-empty %s +// RUN: %t/input.h -o %t/output.json | FileCheck -allow-empty %s // Generator version is not consistent across test runs, normalize it. // RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \ @@ -13,7 +13,7 @@ // CHECK-NOT: error: // CHECK-NOT: warning: -//--- input.c +//--- input.h int num; /** @@ -80,7 +80,7 @@ "location": { "character": 5, "line": 1, - "uri": "file://INPUT_DIR/input.c" + "uri": "file://INPUT_DIR/input.h" }, "names": { "subHeading": [ @@ -272,7 +272,7 @@ "location": { "character": 6, "line": 9, - "uri": "file://INPUT_DIR/input.c" + "uri": "file://INPUT_DIR/input.h" }, "names": { "subHeading": [