Skip to content

Commit 202a4cd

Browse files
committed
Swift: teach autobuilder about SPM, CocoaPods, and Carthage
1 parent 41a527c commit 202a4cd

File tree

7 files changed

+148
-67
lines changed

7 files changed

+148
-67
lines changed

swift/tools/autobuild.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/bin/bash
22

33
if [[ "$OSTYPE" == "darwin"* ]]; then
4+
export CODEQL_SWIFT_CARTHAGE_EXEC=`which carthage`
5+
export CODEQL_SWIFT_POD_EXEC=`which pod`
46
exec "${CODEQL_EXTRACTOR_SWIFT_ROOT}/tools/${CODEQL_PLATFORM}/xcode-autobuilder"
57
else
68
exec "${CODEQL_EXTRACTOR_SWIFT_ROOT}/tools/${CODEQL_PLATFORM}/autobuilder-incompatible-os"

swift/xcode-autobuilder/XcodeBuildRunner.cpp

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,22 @@ static bool exec(const std::vector<std::string>& argv) {
5151
return true;
5252
}
5353

54-
void buildTarget(Target& target, bool dryRun) {
54+
static bool run_build_command(const std::vector<std::string>& argv, bool dryRun) {
55+
if (dryRun) {
56+
std::cout << absl::StrJoin(argv, " ") << "\n";
57+
} else {
58+
if (!exec(argv)) {
59+
DIAGNOSE_ERROR(buildCommandFailed,
60+
"`autobuild` failed to run the build command:\n\n```\n{}\n```",
61+
absl::StrJoin(argv, " "));
62+
codeql::Log::flush();
63+
return false;
64+
}
65+
}
66+
return true;
67+
}
68+
69+
bool buildXcodeTarget(XcodeTarget& target, bool dryRun) {
5570
std::vector<std::string> argv({"/usr/bin/xcodebuild", "build"});
5671
if (!target.workspace.empty()) {
5772
argv.push_back("-workspace");
@@ -65,16 +80,40 @@ void buildTarget(Target& target, bool dryRun) {
6580
argv.push_back(target.name);
6681
argv.push_back("CODE_SIGNING_REQUIRED=NO");
6782
argv.push_back("CODE_SIGNING_ALLOWED=NO");
83+
return run_build_command(argv, dryRun);
84+
}
6885

69-
if (dryRun) {
70-
std::cout << absl::StrJoin(argv, " ") << "\n";
71-
} else {
72-
if (!exec(argv)) {
73-
DIAGNOSE_ERROR(buildCommandFailed,
74-
"`autobuild` failed to run the detected build command:\n\n```\n{}\n```",
75-
absl::StrJoin(argv, " "));
76-
codeql::Log::flush();
77-
exit(1);
86+
bool buildSwiftPackage(const std::filesystem::path& packageFile, bool dryRun) {
87+
std::vector<std::string> argv(
88+
{"/usr/bin/swift", "build", "--package-path", packageFile.parent_path()});
89+
return run_build_command(argv, dryRun);
90+
}
91+
92+
static void pod_install(const std::string& pod, const std::filesystem::path& podfile, bool dryRun) {
93+
std::vector<std::string> argv(
94+
{pod, "install", "--project-directory=" + podfile.parent_path().string()});
95+
run_build_command(argv, dryRun);
96+
}
97+
98+
static void carthage_install(const std::string& carthage,
99+
const std::filesystem::path& podfile,
100+
bool dryRun) {
101+
std::vector<std::string> argv(
102+
{carthage, "bootstrap", "--project-directory", podfile.parent_path()});
103+
run_build_command(argv, dryRun);
104+
}
105+
106+
void installDependencies(ProjectStructure& target, bool dryRun) {
107+
auto pod = std::string(getenv("CODEQL_SWIFT_POD_EXEC"));
108+
auto carthage = std::string(getenv("CODEQL_SWIFT_CARTHAGE_EXEC"));
109+
if (!pod.empty() && !target.podfiles.empty()) {
110+
for (auto& podfile : target.podfiles) {
111+
pod_install(pod, podfile, dryRun);
112+
}
113+
}
114+
if (!carthage.empty() && !target.cartfiles.empty()) {
115+
for (auto& cartfile : target.cartfiles) {
116+
carthage_install(carthage, cartfile, dryRun);
78117
}
79118
}
80119
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#pragma once
22

33
#include "swift/xcode-autobuilder/XcodeTarget.h"
4+
#include "swift/xcode-autobuilder/XcodeProjectParser.h"
5+
#include <filesystem>
46

5-
void buildTarget(Target& target, bool dryRun);
7+
void installDependencies(ProjectStructure& target, bool dryRun);
8+
bool buildXcodeTarget(XcodeTarget& target, bool dryRun);
9+
bool buildSwiftPackage(const std::filesystem::path& packageFile, bool dryRun);

swift/xcode-autobuilder/XcodeProjectParser.cpp

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,30 @@ static std::unordered_map<std::string, TargetData> mapTargetsToWorkspace(
198198
return targetMapping;
199199
}
200200

201-
static std::vector<fs::path> collectFiles(const std::string& workingDir) {
201+
struct ProjectFiles {
202+
std::vector<fs::path> xcodeFiles;
203+
std::vector<fs::path> packageFiles;
204+
std::vector<fs::path> podfiles;
205+
std::vector<fs::path> cartfiles;
206+
};
207+
208+
static ProjectFiles scanWorkingDir(const std::string& workingDir) {
209+
ProjectFiles structure;
202210
fs::path workDir(workingDir);
203211
std::vector<fs::path> files;
204212
auto end = fs::recursive_directory_iterator();
205213
for (auto it = fs::recursive_directory_iterator(workDir); it != end; ++it) {
206214
const auto& p = it->path();
207215
if (p.filename() == "Package.swift") {
208-
files.push_back(p);
216+
structure.packageFiles.push_back(p);
217+
continue;
218+
}
219+
if (p.filename() == "Podfile") {
220+
structure.podfiles.push_back(p);
221+
continue;
222+
}
223+
if (p.filename() == "Cartfile" || p.filename() == "Cartfile.private") {
224+
structure.cartfiles.push_back(p);
209225
continue;
210226
}
211227
if (!it->is_directory()) {
@@ -217,41 +233,29 @@ static std::vector<fs::path> collectFiles(const std::string& workingDir) {
217233
continue;
218234
}
219235
if (p.extension() == ".xcodeproj" || p.extension() == ".xcworkspace") {
220-
files.push_back(p);
236+
structure.xcodeFiles.push_back(p);
221237
}
222238
}
223-
return files;
239+
return structure;
224240
}
225241

226242
static std::unordered_map<std::string, std::vector<std::string>> collectWorkspaces(
227-
const std::string& workingDir,
228-
bool& swiftPackageEncountered) {
243+
const ProjectFiles& projectFiles) {
229244
// Here we are collecting list of all workspaces and Xcode projects corresponding to them
230245
// Projects without workspaces go into the same "empty-workspace" bucket
231-
swiftPackageEncountered = false;
232246
std::unordered_map<std::string, std::vector<std::string>> workspaces;
233247
std::unordered_set<std::string> projectsBelongingToWorkspace;
234-
std::vector<fs::path> files = collectFiles(workingDir);
235-
for (auto& path : files) {
248+
for (auto& path : projectFiles.xcodeFiles) {
236249
if (path.extension() == ".xcworkspace") {
237250
auto projects = readProjectsFromWorkspace(path.string());
238251
for (auto& project : projects) {
239252
projectsBelongingToWorkspace.insert(project.string());
240253
workspaces[path.string()].push_back(project.string());
241254
}
242-
} else if (!swiftPackageEncountered && path.filename() == "Package.swift") {
243-
// a package manifest must begin with a specific header comment
244-
// see https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html
245-
static constexpr std::string_view packageHeader = "// swift-tools-version:";
246-
std::array<char, packageHeader.size()> buffer{};
247-
std::string_view bufferView{buffer.data(), buffer.size()};
248-
if (std::ifstream{path}.read(buffer.data(), buffer.size()) && bufferView == packageHeader) {
249-
swiftPackageEncountered = true;
250-
}
251255
}
252256
}
253257
// Collect all projects not belonging to any workspace into a separate empty bucket
254-
for (auto& path : files) {
258+
for (auto& path : projectFiles.xcodeFiles) {
255259
if (path.extension() == ".xcodeproj") {
256260
if (projectsBelongingToWorkspace.count(path.string())) {
257261
continue;
@@ -262,11 +266,15 @@ static std::unordered_map<std::string, std::vector<std::string>> collectWorkspac
262266
return workspaces;
263267
}
264268

265-
Targets collectTargets(const std::string& workingDir) {
266-
Targets ret;
269+
ProjectStructure scanProjectStructure(const std::string& workingDir) {
270+
ProjectStructure ret;
267271
// Getting a list of workspaces and the project that belong to them
268-
auto workspaces = collectWorkspaces(workingDir, ret.swiftPackageEncountered);
272+
auto projectFiles = scanWorkingDir(workingDir);
273+
auto workspaces = collectWorkspaces(projectFiles);
269274
ret.xcodeEncountered = !workspaces.empty();
275+
ret.swiftPackages = std::move(projectFiles.packageFiles);
276+
ret.podfiles = std::move(projectFiles.podfiles);
277+
ret.cartfiles = std::move(projectFiles.cartfiles);
270278
if (!ret.xcodeEncountered) {
271279
return ret;
272280
}
@@ -278,8 +286,8 @@ Targets collectTargets(const std::string& workingDir) {
278286
auto targetFilesMapping = mapTargetsToSourceFiles(workspaces);
279287

280288
for (auto& [targetName, data] : targetMapping) {
281-
ret.targets.push_back(Target{data.workspace, data.project, targetName, data.type,
282-
targetFilesMapping[targetName]});
289+
ret.xcodeTargets.push_back(XcodeTarget{data.workspace, data.project, targetName, data.type,
290+
targetFilesMapping[targetName]});
283291
}
284292
return ret;
285293
}

swift/xcode-autobuilder/XcodeProjectParser.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
#include "swift/xcode-autobuilder/XcodeTarget.h"
44
#include <vector>
55
#include <string>
6+
#include <filesystem>
67

7-
struct Targets {
8-
std::vector<Target> targets;
8+
struct ProjectStructure {
9+
std::vector<XcodeTarget> xcodeTargets;
910
bool xcodeEncountered;
10-
bool swiftPackageEncountered;
11+
// Swift Package Manager support
12+
std::vector<std::filesystem::path> swiftPackages;
13+
// CocoaPods support
14+
std::vector<std::filesystem::path> podfiles;
15+
// Carthage support
16+
std::vector<std::filesystem::path> cartfiles;
1117
};
1218

13-
Targets collectTargets(const std::string& workingDir);
19+
ProjectStructure scanProjectStructure(const std::string& workingDir);

swift/xcode-autobuilder/XcodeTarget.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
#include <string>
44
#include <binlog/adapt_struct.hpp>
55

6-
struct Target {
6+
struct XcodeTarget {
77
std::string workspace;
88
std::string project;
99
std::string name;
1010
std::string type;
1111
size_t fileCount;
1212
};
1313

14-
BINLOG_ADAPT_STRUCT(Target, workspace, project, name, type, fileCount);
14+
BINLOG_ADAPT_STRUCT(XcodeTarget, workspace, project, name, type, fileCount);

swift/xcode-autobuilder/xcode-autobuilder.cpp

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@ constexpr codeql::SwiftDiagnostic noSwiftTarget{
2424
.action = "To analyze a custom set of source files, set up a [manual build "
2525
"command][1].\n\n[1]: " MANUAL_BUILD_COMMAND_HELP_LINK};
2626

27-
constexpr codeql::SwiftDiagnostic spmNotSupported{
28-
.id = "spm-not-supported",
29-
.name = "Swift Package Manager is not supported",
30-
.action = "Swift Package Manager builds are not currently supported by `autobuild`. Set up a "
31-
"[manual build command][1].\n\n[1]: " MANUAL_BUILD_COMMAND_HELP_LINK};
32-
3327
static codeql::Logger& logger() {
3428
static codeql::Logger ret{"main"};
3529
return ret;
@@ -44,38 +38,63 @@ static bool endsWith(std::string_view s, std::string_view suffix) {
4438
return s.size() >= suffix.size() && s.substr(s.size() - suffix.size()) == suffix;
4539
}
4640

47-
static bool isNonSwiftOrTestTarget(const Target& t) {
41+
static bool isNonSwiftOrTestTarget(const XcodeTarget& t) {
4842
return t.fileCount == 0 || t.type == uiTest || t.type == unitTest ||
4943
// unknown target types can be legitimate, let's do a name-based heuristic then
5044
(t.type == unknownType && (endsWith(t.name, "Tests") || endsWith(t.name, "Test")));
5145
}
5246

53-
static void autobuild(const CLIArgs& args) {
54-
auto collected = collectTargets(args.workingDir);
55-
auto& targets = collected.targets;
56-
for (const auto& t : targets) {
47+
static void buildSwiftPackages(const std::vector<std::filesystem::path>& swiftPackages,
48+
bool dryRun) {
49+
auto any_successful =
50+
std::any_of(std::begin(swiftPackages), std::end(swiftPackages), [&](auto& packageFile) {
51+
LOG_INFO("Building Swift package: {}", packageFile);
52+
return buildSwiftPackage(packageFile, dryRun);
53+
});
54+
if (!any_successful) {
55+
codeql::Log::flush();
56+
exit(1);
57+
}
58+
}
59+
60+
static bool autobuild(const CLIArgs& args) {
61+
auto structure = scanProjectStructure(args.workingDir);
62+
auto& xcodeTargets = structure.xcodeTargets;
63+
auto& swiftPackages = structure.swiftPackages;
64+
for (const auto& t : xcodeTargets) {
5765
LOG_INFO("{}", t);
5866
}
5967
// Filter out targets that are tests or have no swift source files
60-
targets.erase(std::remove_if(std::begin(targets), std::end(targets), isNonSwiftOrTestTarget),
61-
std::end(targets));
68+
xcodeTargets.erase(
69+
std::remove_if(std::begin(xcodeTargets), std::end(xcodeTargets), isNonSwiftOrTestTarget),
70+
std::end(xcodeTargets));
6271

6372
// Sort targets by the amount of files in each
64-
std::sort(std::begin(targets), std::end(targets),
65-
[](Target& lhs, Target& rhs) { return lhs.fileCount > rhs.fileCount; });
66-
if ((!collected.xcodeEncountered || targets.empty()) && collected.swiftPackageEncountered) {
67-
DIAGNOSE_ERROR(spmNotSupported,
68-
"A Swift package was detected, but no viable Xcode target was found.");
69-
} else if (!collected.xcodeEncountered) {
70-
DIAGNOSE_ERROR(noProjectFound, "`autobuild` could not detect an Xcode project or workspace.");
71-
} else if (targets.empty()) {
73+
std::sort(std::begin(xcodeTargets), std::end(xcodeTargets),
74+
[](XcodeTarget& lhs, XcodeTarget& rhs) { return lhs.fileCount > rhs.fileCount; });
75+
76+
if (structure.xcodeEncountered && xcodeTargets.empty() && swiftPackages.empty()) {
77+
// Report error only when there are no Xcode targets and no Swift packages
7278
DIAGNOSE_ERROR(noSwiftTarget, "All targets found within Xcode projects or workspaces either "
7379
"contain no Swift source files, or are tests.");
74-
} else {
75-
LOG_INFO("Selected {}", targets.front());
76-
buildTarget(targets.front(), args.dryRun);
77-
return;
80+
return false;
81+
} else if (!structure.xcodeEncountered && swiftPackages.empty()) {
82+
DIAGNOSE_ERROR(noProjectFound,
83+
"`autobuild` could not detect an Xcode project or workspace or Swift package");
84+
return false;
85+
} else if (!xcodeTargets.empty()) {
86+
LOG_INFO("Building Xcode target: {}", xcodeTargets.front());
87+
installDependencies(structure, args.dryRun);
88+
auto buildSucceeded = buildXcodeTarget(xcodeTargets.front(), args.dryRun);
89+
// If build failed, try to build Swift packages
90+
if (!buildSucceeded && !swiftPackages.empty()) {
91+
buildSwiftPackages(swiftPackages, args.dryRun);
92+
}
93+
return buildSucceeded;
94+
} else if (!swiftPackages.empty()) {
95+
buildSwiftPackages(swiftPackages, args.dryRun);
7896
}
97+
return true;
7998
}
8099

81100
static CLIArgs parseCLIArgs(int argc, char** argv) {
@@ -96,7 +115,10 @@ static CLIArgs parseCLIArgs(int argc, char** argv) {
96115

97116
int main(int argc, char** argv) {
98117
auto args = parseCLIArgs(argc, argv);
99-
autobuild(args);
118+
auto success = autobuild(args);
100119
codeql::Log::flush();
120+
if (!success) {
121+
return 1;
122+
}
101123
return 0;
102124
}

0 commit comments

Comments
 (0)