-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Swift: teach autobuilder about SPM, CocoaPods, and Carthage #13979
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
202a4cd
6a5e539
7e36f7d
b98a966
48607e3
5cce37b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
#pragma once | ||
|
||
#include "swift/xcode-autobuilder/XcodeTarget.h" | ||
#include "swift/xcode-autobuilder/XcodeProjectParser.h" | ||
#include <filesystem> | ||
|
||
void buildTarget(Target& target, bool dryRun); | ||
void installDependencies(const ProjectStructure& target, bool dryRun); | ||
bool buildXcodeTarget(const XcodeTarget& target, bool dryRun); | ||
bool buildSwiftPackage(const std::filesystem::path& packageFile, bool dryRun); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -198,14 +198,30 @@ static std::unordered_map<std::string, TargetData> mapTargetsToWorkspace( | |
return targetMapping; | ||
} | ||
|
||
static std::vector<fs::path> collectFiles(const std::string& workingDir) { | ||
struct ProjectFiles { | ||
std::vector<fs::path> xcodeFiles; | ||
std::vector<fs::path> packageFiles; | ||
std::vector<fs::path> podfiles; | ||
std::vector<fs::path> cartfiles; | ||
}; | ||
|
||
static ProjectFiles scanWorkingDir(const std::string& workingDir) { | ||
ProjectFiles structure; | ||
fs::path workDir(workingDir); | ||
std::vector<fs::path> files; | ||
auto end = fs::recursive_directory_iterator(); | ||
for (auto it = fs::recursive_directory_iterator(workDir); it != end; ++it) { | ||
const auto& p = it->path(); | ||
if (p.filename() == "Package.swift") { | ||
files.push_back(p); | ||
structure.packageFiles.push_back(p); | ||
continue; | ||
} | ||
if (p.filename() == "Podfile") { | ||
structure.podfiles.push_back(p); | ||
continue; | ||
} | ||
if (p.filename() == "Cartfile" || p.filename() == "Cartfile.private") { | ||
structure.cartfiles.push_back(p); | ||
continue; | ||
} | ||
if (!it->is_directory()) { | ||
|
@@ -217,43 +233,31 @@ static std::vector<fs::path> collectFiles(const std::string& workingDir) { | |
continue; | ||
} | ||
if (p.extension() == ".xcodeproj" || p.extension() == ".xcworkspace") { | ||
files.push_back(p); | ||
structure.xcodeFiles.push_back(p); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe as you are anyway creating this structure with fields for the different kinds of files, you can also split There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I thought about this but decided not to as these two go hand in hand and it doesn't make much difference if we do it the other way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'd be a tad cleaner, but I'll leave it to your judgement 🙂 |
||
} | ||
} | ||
return files; | ||
return structure; | ||
} | ||
|
||
static std::unordered_map<std::string, std::vector<std::string>> collectWorkspaces( | ||
const std::string& workingDir, | ||
bool& swiftPackageEncountered) { | ||
const ProjectFiles& projectFiles) { | ||
// Here we are collecting list of all workspaces and Xcode projects corresponding to them | ||
// Projects without workspaces go into the same "empty-workspace" bucket | ||
swiftPackageEncountered = false; | ||
std::unordered_map<std::string, std::vector<std::string>> workspaces; | ||
std::unordered_set<std::string> projectsBelongingToWorkspace; | ||
std::vector<fs::path> files = collectFiles(workingDir); | ||
for (auto& path : files) { | ||
for (auto& path : projectFiles.xcodeFiles) { | ||
if (path.extension() == ".xcworkspace") { | ||
auto projects = readProjectsFromWorkspace(path.string()); | ||
for (auto& project : projects) { | ||
projectsBelongingToWorkspace.insert(project.string()); | ||
workspaces[path.string()].push_back(project.string()); | ||
} | ||
} else if (!swiftPackageEncountered && path.filename() == "Package.swift") { | ||
// a package manifest must begin with a specific header comment | ||
// see https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html | ||
static constexpr std::string_view packageHeader = "// swift-tools-version:"; | ||
std::array<char, packageHeader.size()> buffer{}; | ||
std::string_view bufferView{buffer.data(), buffer.size()}; | ||
if (std::ifstream{path}.read(buffer.data(), buffer.size()) && bufferView == packageHeader) { | ||
swiftPackageEncountered = true; | ||
} | ||
} | ||
} | ||
// Collect all projects not belonging to any workspace into a separate empty bucket | ||
for (auto& path : files) { | ||
for (auto& path : projectFiles.xcodeFiles) { | ||
if (path.extension() == ".xcodeproj") { | ||
if (projectsBelongingToWorkspace.count(path.string())) { | ||
if (projectsBelongingToWorkspace.contains(path.string())) { | ||
continue; | ||
} | ||
workspaces[std::string()].push_back(path.string()); | ||
|
@@ -262,11 +266,15 @@ static std::unordered_map<std::string, std::vector<std::string>> collectWorkspac | |
return workspaces; | ||
} | ||
|
||
Targets collectTargets(const std::string& workingDir) { | ||
Targets ret; | ||
ProjectStructure scanProjectStructure(const std::string& workingDir) { | ||
ProjectStructure ret; | ||
// Getting a list of workspaces and the project that belong to them | ||
auto workspaces = collectWorkspaces(workingDir, ret.swiftPackageEncountered); | ||
auto projectFiles = scanWorkingDir(workingDir); | ||
auto workspaces = collectWorkspaces(projectFiles); | ||
ret.xcodeEncountered = !workspaces.empty(); | ||
ret.swiftPackages = std::move(projectFiles.packageFiles); | ||
ret.podfiles = std::move(projectFiles.podfiles); | ||
ret.cartfiles = std::move(projectFiles.cartfiles); | ||
if (!ret.xcodeEncountered) { | ||
return ret; | ||
} | ||
|
@@ -278,8 +286,8 @@ Targets collectTargets(const std::string& workingDir) { | |
auto targetFilesMapping = mapTargetsToSourceFiles(workspaces); | ||
|
||
for (auto& [targetName, data] : targetMapping) { | ||
ret.targets.push_back(Target{data.workspace, data.project, targetName, data.type, | ||
targetFilesMapping[targetName]}); | ||
ret.xcodeTargets.push_back(XcodeTarget{data.workspace, data.project, targetName, data.type, | ||
targetFilesMapping[targetName]}); | ||
} | ||
return ret; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL about this
?:
GNU extension that I knew nothing about 🙂As a nit, this can be made more standard conforming using
std::string_view
, which contrary tostd::string
accepts anullptr
(and treats it equivalent to""
). Or move thegetenv
as a nestedif
, for exampleor as one single if with initialization:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first snippet is not exactly correct as we may have podfiles on linux, but won't have
pod
executable there.And I don't find the second necessarily better than what we have now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as far as I understand, the first snippet is equivalent, as the pod installation will happen only if both the conditions are met. The conditions are just evaluated in swapped order (and the env variable won't be inspected if
podfiles
is empty).What itches me here, is the use of a non-standard extension, that might also be surprising to some developers (it was a bit to me). On the other hand, we do enforce compilation with clang, so maybe the non-standardness is not such a big deal... It's just that I personally only use non-standard C++ stuff when there is no sensible alternative, which I think there is here.
But that's a nit, I'm also kinda ok with leaving this as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is as non-standard as
#pragma once
😄