Skip to content

Commit 1fdae62

Browse files
committed
[libclang] Add clang_Driver_getExternalActionsForCommand_v0
This function returns the equivalent of -### <rdar://problem/49928458> <rdar://problem/49396121>
1 parent 3e60b06 commit 1fdae62

File tree

6 files changed

+282
-0
lines changed

6 files changed

+282
-0
lines changed

clang/include/clang-c/Driver.h

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*==-- clang-c/Driver.h - A C Interface for the Clang Driver ------*- C -*-===*\
2+
|* *|
3+
|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *|
4+
|* Exceptions. *|
5+
|* See https://llvm.org/LICENSE.txt for license information. *|
6+
|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *|
7+
|* *|
8+
|*===----------------------------------------------------------------------===*|
9+
|* *|
10+
|* This header provides a C API for extracting information from the clang *|
11+
|* driver. *|
12+
|* *|
13+
\*===----------------------------------------------------------------------===*/
14+
15+
16+
#ifndef CLANG_CLANG_C_DRIVER
17+
#define CLANG_CLANG_C_DRIVER
18+
19+
#include "clang-c/Index.h"
20+
#include "clang-c/Platform.h"
21+
22+
#ifdef __cplusplus
23+
extern "C" {
24+
#endif
25+
26+
/**
27+
* Contains the command line arguments for an external action. Same format as
28+
* provided to main.
29+
*/
30+
typedef struct {
31+
/* Number of arguments in ArgV */
32+
int ArgC;
33+
/* Null terminated array of pointers to null terminated argument strings */
34+
const char **ArgV;
35+
} CXExternalAction;
36+
37+
/**
38+
* Contains the list of external actions clang would invoke.
39+
*/
40+
typedef struct {
41+
int Count;
42+
CXExternalAction **Actions;
43+
} CXExternalActionList;
44+
45+
/**
46+
* Get the external actions that the clang driver will invoke for the given
47+
* command line.
48+
*
49+
* \param ArgC number of arguments in \p ArgV.
50+
* \param ArgV array of null terminated arguments. Doesn't need to be null
51+
* terminated.
52+
* \param Environment must be null.
53+
* \param WorkingDirectory a null terminated path to the working directory to
54+
* use for this invocation. `nullptr` to use the current working directory of
55+
* the process.
56+
* \param OutDiags will be set to a \c CXDiagnosticSet if there's an error.
57+
* Must be freed by calling \c clang_disposeDiagnosticSet .
58+
* \returns A pointer to a \c CXExternalActionList on success, null on failure.
59+
* The returned \c CXExternalActionList must be freed by calling
60+
* \c clang_Driver_ExternalActionList_dispose .
61+
*/
62+
CINDEX_LINKAGE CXExternalActionList *
63+
clang_Driver_getExternalActionsForCommand_v0(int ArgC, const char **ArgV,
64+
const char **Environment,
65+
const char *WorkingDirectory,
66+
CXDiagnosticSet *OutDiags);
67+
68+
/**
69+
* Deallocate a \c CXExternalActionList
70+
*/
71+
CINDEX_LINKAGE void
72+
clang_Driver_ExternalActionList_dispose(CXExternalActionList *EAL);
73+
74+
#ifdef __cplusplus
75+
}
76+
#endif
77+
78+
#endif // CLANG_CLANG_C_DRIVER

clang/tools/libclang/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ set(SOURCES
2020
CXStoredDiagnostic.cpp
2121
CXString.cpp
2222
CXType.cpp
23+
Driver.cpp
2324
Indexing.cpp
2425
FatalErrorHandler.cpp
2526

clang/tools/libclang/Driver.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//===- Driver.cpp - A C Interface for the Clang Driver --------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file provides a C API for extracting information from the clang driver.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "clang-c/Driver.h"
14+
15+
#include "CIndexDiagnostic.h"
16+
17+
#include "clang/Driver/Compilation.h"
18+
#include "clang/Driver/Driver.h"
19+
#include "clang/Driver/DriverDiagnostic.h"
20+
#include "clang/Frontend/CompilerInstance.h"
21+
#include "llvm/ADT/ArrayRef.h"
22+
23+
using namespace clang;
24+
25+
class CXDiagnosticSetDiagnosticConsumer : public DiagnosticConsumer {
26+
SmallVector<StoredDiagnostic, 4> Errors;
27+
public:
28+
29+
void HandleDiagnostic(DiagnosticsEngine::Level level,
30+
const Diagnostic &Info) override {
31+
if (level >= DiagnosticsEngine::Error)
32+
Errors.push_back(StoredDiagnostic(level, Info));
33+
}
34+
35+
CXDiagnosticSet getDiagnosticSet() {
36+
return cxdiag::createStoredDiags(Errors, LangOptions());
37+
}
38+
};
39+
40+
CXExternalActionList *
41+
clang_Driver_getExternalActionsForCommand_v0(int ArgC, const char **ArgV,
42+
const char **Environment,
43+
const char *WorkingDirectory,
44+
CXDiagnosticSet *OutDiags) {
45+
if (OutDiags)
46+
*OutDiags = nullptr;
47+
48+
// Non empty environments are not currently supported.
49+
if (Environment)
50+
return nullptr;
51+
52+
// ArgV must at least include the compiler executable name.
53+
if (ArgC < 1)
54+
return nullptr;
55+
56+
CXDiagnosticSetDiagnosticConsumer DiagConsumer;
57+
auto Diags = CompilerInstance::createDiagnostics(new DiagnosticOptions,
58+
&DiagConsumer, false);
59+
60+
// Use createPhysicalFileSystem instead of getRealFileSystem so that
61+
// setCurrentWorkingDirectory doesn't change the working directory of the
62+
// process.
63+
std::unique_ptr<llvm::vfs::FileSystem> VFS =
64+
llvm::vfs::createPhysicalFileSystem();
65+
if (WorkingDirectory)
66+
if (std::error_code EC =
67+
VFS->setCurrentWorkingDirectory(WorkingDirectory)) {
68+
Diags->Report(diag::err_drv_unable_to_set_working_directory) <<
69+
WorkingDirectory;
70+
if (OutDiags)
71+
*OutDiags = DiagConsumer.getDiagnosticSet();
72+
return nullptr;
73+
}
74+
75+
driver::Driver TheDriver(ArgV[0], llvm::sys::getDefaultTargetTriple(), *Diags,
76+
VFS.release());
77+
TheDriver.setCheckInputsExist(false);
78+
std::unique_ptr<driver::Compilation> C(
79+
TheDriver.BuildCompilation(llvm::makeArrayRef(ArgV, ArgC)));
80+
if (!C) {
81+
if (OutDiags)
82+
*OutDiags = DiagConsumer.getDiagnosticSet();
83+
return nullptr;
84+
}
85+
86+
const driver::JobList &Jobs = C->getJobs();
87+
CXExternalAction **Actions = new CXExternalAction *[Jobs.size()];
88+
int AI = 0;
89+
for (auto &&J : Jobs) {
90+
// First calculate the total space we'll need for this action's arguments.
91+
llvm::opt::ArgStringList Args = J.getArguments();
92+
Args.insert(Args.begin(), J.getExecutable());
93+
int ArgSpace = (Args.size() + 1) * sizeof(const char *);
94+
for (auto &&Arg : Args)
95+
ArgSpace += strlen(Arg) + 1; // Null terminator
96+
97+
// Tail allocate the space for the strings.
98+
auto Action =
99+
new ((CXExternalAction *)malloc(sizeof(CXExternalAction) + ArgSpace))
100+
CXExternalAction;
101+
Action->ArgC = Args.size();
102+
Action->ArgV = reinterpret_cast<const char **>(Action + 1);
103+
Action->ArgV[Args.size()] = nullptr;
104+
char *StrTable = ((char *)Action) + sizeof(CXExternalAction) +
105+
(Args.size() + 1) * sizeof(const char *);
106+
int I = 0;
107+
for (auto &&Arg : Args) {
108+
Action->ArgV[I++] = strcpy(StrTable, Arg);
109+
StrTable += strlen(Arg) + 1;
110+
}
111+
Actions[AI++] = Action;
112+
}
113+
114+
return new CXExternalActionList{(int)Jobs.size(), Actions};
115+
}
116+
117+
void clang_Driver_ExternalActionList_dispose(CXExternalActionList *EAL) {
118+
if (!EAL)
119+
return;
120+
121+
for (int I = 0; I < EAL->Count; ++I)
122+
free(EAL->Actions[I]);
123+
delete[] EAL->Actions;
124+
delete EAL;
125+
}

clang/tools/libclang/libclang.exports

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ clang_Cursor_isObjCOptional
4949
clang_Cursor_isVariadic
5050
clang_Cursor_getModule
5151
clang_Cursor_getStorageClass
52+
clang_Driver_ExternalActionList_dispose
53+
clang_Driver_getExternalActionsForCommand_v0
5254
clang_File_isEqual
5355
clang_File_tryGetRealPathName
5456
clang_Module_getASTFile

clang/unittests/libclang/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_clang_unittest(libclangTests
22
LibclangTest.cpp
3+
DriverTest.cpp
34
)
45

56
target_link_libraries(libclangTests
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//===---- DriverTest.cpp --------------------------------------------------===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "clang-c/Driver.h"
11+
#include "llvm/ADT/STLExtras.h"
12+
#include "llvm/Support/Debug.h"
13+
#include "gtest/gtest.h"
14+
15+
#define DEBUG_TYPE "driver-test"
16+
17+
TEST(DriverTests, Basic) {
18+
const char *ArgV[] = {"clang", "-w", "t.cpp", "-o", "t.ll"};
19+
20+
CXExternalActionList *EAL = clang_Driver_getExternalActionsForCommand_v0(
21+
GTEST_ARRAY_SIZE_(ArgV), ArgV, nullptr, nullptr, nullptr);
22+
ASSERT_NE(EAL, nullptr);
23+
ASSERT_EQ(EAL->Count, 2);
24+
auto *CompileAction = EAL->Actions[0];
25+
ASSERT_GE(CompileAction->ArgC, 2);
26+
EXPECT_STREQ(CompileAction->ArgV[0], "clang");
27+
EXPECT_STREQ(CompileAction->ArgV[1], "-cc1");
28+
29+
clang_Driver_ExternalActionList_dispose(EAL);
30+
}
31+
32+
TEST(DriverTests, WorkingDirectory) {
33+
const char *ArgV[] = {"clang", "-c", "t.cpp", "-o", "t.o"};
34+
35+
CXExternalActionList *EAL = clang_Driver_getExternalActionsForCommand_v0(
36+
GTEST_ARRAY_SIZE_(ArgV), ArgV, nullptr, "/", nullptr);
37+
ASSERT_NE(EAL, nullptr);
38+
ASSERT_EQ(EAL->Count, 1);
39+
auto *CompileAction = EAL->Actions[0];
40+
41+
const char **FDCD = std::find(CompileAction->ArgV, CompileAction->ArgV +
42+
CompileAction->ArgC,
43+
llvm::StringRef("-fdebug-compilation-dir"));
44+
ASSERT_NE(FDCD, CompileAction->ArgV + CompileAction->ArgC);
45+
ASSERT_NE(FDCD + 1, CompileAction->ArgV + CompileAction->ArgC);
46+
EXPECT_STREQ(*(FDCD + 1), "/");
47+
48+
clang_Driver_ExternalActionList_dispose(EAL);
49+
}
50+
51+
TEST(DriverTests, Diagnostics) {
52+
const char *ArgV[] = {"clang", "-c", "nosuchfile.cpp", "-o", "t.o"};
53+
54+
CXExternalActionList *EAL = clang_Driver_getExternalActionsForCommand_v0(
55+
GTEST_ARRAY_SIZE_(ArgV), ArgV, nullptr, "/no/such/working/dir", nullptr);
56+
EXPECT_EQ(nullptr, EAL);
57+
clang_Driver_ExternalActionList_dispose(EAL);
58+
59+
CXDiagnosticSet Diags;
60+
EAL = clang_Driver_getExternalActionsForCommand_v0(
61+
GTEST_ARRAY_SIZE_(ArgV), ArgV, nullptr, "/no/such/working/dir", &Diags);
62+
EXPECT_EQ(nullptr, EAL);
63+
ASSERT_NE(nullptr, Diags);
64+
65+
unsigned NumDiags = clang_getNumDiagnosticsInSet(Diags);
66+
ASSERT_EQ(1u, NumDiags);
67+
CXDiagnostic Diag = clang_getDiagnosticInSet(Diags, 0);
68+
CXString Str = clang_formatDiagnostic(Diag, 0);
69+
EXPECT_STREQ(clang_getCString(Str),
70+
"error: unable to set working directory: /no/such/working/dir");
71+
clang_disposeString(Str);
72+
73+
clang_disposeDiagnosticSet(Diags);
74+
clang_Driver_ExternalActionList_dispose(EAL);
75+
}

0 commit comments

Comments
 (0)