Add support for adding build tool plugins to targets (#1374)

* Add support for adding build tool plugins to targets

* Added Plugin validation

* Added some tests

* Limited the minimum version to 5.7 Swift

* Update .gitignore

Co-authored-by: freddi(Yuki Aki) <freddi-kit@users.noreply.github.com>

* Update CHANGELOG.md

Co-authored-by: freddi(Yuki Aki) <freddi-kit@users.noreply.github.com>

* Update CHANGELOG.md

Co-authored-by: freddi(Yuki Aki) <freddi-kit@users.noreply.github.com>

* Update Docs/ProjectSpec.md

Co-authored-by: freddi(Yuki Aki) <freddi-kit@users.noreply.github.com>

* Added a fixture for testing plugins

* Update CHANGELOG.md

* Installed the release version of XcodeProj

---------

Co-authored-by: freddi(Yuki Aki) <freddi-kit@users.noreply.github.com>
This commit is contained in:
BarredEwe 2023-08-16 15:22:16 +03:00 committed by GitHub
parent 2ef94c9910
commit d8d5457f48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 323 additions and 96 deletions

View File

@ -6,6 +6,10 @@
- Fixed source file `includes` not working when no paths were found #1337 @shnhrrsn
### Added
- Added support for adding `Build Tool Plug-ins` to targets #1374 @BarredEwe
## 2.36.1
### Fixed

View File

@ -27,6 +27,7 @@ You can also use environment variables in your configuration file, by using `${S
- [Dependency](#dependency)
- [Config Files](#config-files)
- [Plist](#plist)
- [Build Tool Plug-ins](#build-tool-plug-ins)
- [Build Script](#build-script)
- [Build Rule](#build-rule)
- [Target Scheme](#target-scheme)
@ -381,6 +382,7 @@ Settings are merged in the following order: `groups`, `base`, `configs` (simple
- [ ] **directlyEmbedCarthageDependencies**: **Bool** - If this is `true` Carthage framework dependencies will be embedded using an `Embed Frameworks` build phase instead of the `copy-frameworks` script. Defaults to `true` for all targets except iOS/tvOS/watchOS Applications.
- [ ] **requiresObjCLinking**: **Bool** - If this is `true` any targets that link to this target will have `-ObjC` added to their `OTHER_LDFLAGS`. This is required if a static library has any categories or extensions on Objective-C code. See [this guide](https://pewpewthespells.com/blog/objc_linker_flags.html#objc) for more details. Defaults to `true` if `type` is `library.static`. If you are 100% sure you don't have categories or extensions on Objective-C code (pure Swift with no use of Foundation/UIKit) you can set this to `false`, otherwise it's best to leave it alone.
- [ ] **onlyCopyFilesOnInstall**: **Bool**  If this is `true`, the `Embed Frameworks` and `Embed App Extensions` (if available) build phases will have the "Copy only when installing" chekbox checked. Defaults to `false`.
- [ ] **buildToolPlugins**: **[[Build Tool Plug-ins](#build-tool-plug-ins)]** - Commands for the build system that run automatically *during* the build.
- [ ] **preBuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *before* any other build phases
- [ ] **postCompileScripts**: **[[Build Script](#build-script)]** - Build scripts that run after the Compile Sources phase
- [ ] **postBuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *after* any other build phases
@ -695,6 +697,36 @@ targets:
com.apple.security.application-groups: group.com.app
```
### Build Tool Plug-ins
To add `Build Tool Plug-ins`, you need to add information about plugins to [Target](#target):
- **buildToolPlugins**: List of plugins to connect to the target
Each plugin includes information:
- [x] **plugin**: **String** - plugin name
- [x] **package**: **String** - the name of the package that contains the plugin
Сonnect the plugin to the desired target:
```yaml
targets:
App:
buildToolPlugins:
- plugin: MyPlugin
package: MyPackage
```
Don't forget to add a package containing the plugin we need:
```yaml
packages:
MyPackage:
url: https://github.com/MyPackage
from: 1.3.0
```
### Build Script
Run script build phases can be added at 3 different points in the build:
@ -854,6 +886,7 @@ This is used to override settings or run build scripts in specific targets
- [x] **targets**: **[String]** - The list of target names to include as target dependencies
- [ ] **configFiles**: **[Config Files](#config-files)** - `.xcconfig` files per config
- [ ] **settings**: **[Settings](#settings)** - Target specific build settings.
- [ ] **buildToolPlugins**: **[[Build Tool Plug-ins](#build-tool-plug-ins)]** - Commands for the build system that run automatically *during* the build
- [ ] **buildScripts**: **[[Build Script](#build-script)]** - Build scripts to run
- [ ] **scheme**: **[Target Scheme](#target-scheme)** - Generated scheme
- [ ] **attributes**: **[String: Any]** - This sets values in the project `TargetAttributes`. It is merged with `attributes` from the project and anything automatically added by XcodeGen, with any duplicate values being override by values specified here

View File

@ -204,3 +204,20 @@ targets:
dependencies:
- framework: Vendor/MyFramework.framework
```
# Build Tool Plug-ins
XCodeGen supports working with [Swift Package Plug-ins](https://github.com/apple/swift-package-manager/blob/main/Documentation/Plugins.md#using-a-package-plugin).
To use plugins, you need to specify in your target which plugin you want to connect, and don't forget to connect the package to target.
```yaml
packages:
Prefire:
url: https://github.com/BarredEwe/Prefire
from: 1.3.0
targets:
App:
buildToolPlugins:
- plugin: PrefirePlaybookPlugin
package: Prefire
```

View File

@ -1,97 +1,95 @@
{
"object": {
"pins": [
{
"package": "AEXML",
"repositoryURL": "https://github.com/tadija/AEXML.git",
"state": {
"branch": null,
"revision": "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3",
"version": "4.6.1"
}
},
{
"package": "GraphViz",
"repositoryURL": "https://github.com/SwiftDocOrg/GraphViz.git",
"state": {
"branch": null,
"revision": "70bebcf4597b9ce33e19816d6bbd4ba9b7bdf038",
"version": "0.2.0"
}
},
{
"package": "JSONUtilities",
"repositoryURL": "https://github.com/yonaskolb/JSONUtilities.git",
"state": {
"branch": null,
"revision": "128d2ffc22467f69569ef8ff971683e2393191a0",
"version": "4.2.0"
}
},
{
"package": "PathKit",
"repositoryURL": "https://github.com/kylef/PathKit.git",
"state": {
"branch": null,
"revision": "3bfd2737b700b9a36565a8c94f4ad2b050a5e574",
"version": "1.0.1"
}
},
{
"package": "Rainbow",
"repositoryURL": "https://github.com/onevcat/Rainbow.git",
"state": {
"branch": null,
"revision": "626c3d4b6b55354b4af3aa309f998fae9b31a3d9",
"version": "3.2.0"
}
},
{
"package": "Spectre",
"repositoryURL": "https://github.com/kylef/Spectre.git",
"state": {
"branch": null,
"revision": "26cc5e9ae0947092c7139ef7ba612e34646086c7",
"version": "0.10.1"
}
},
{
"package": "SwiftCLI",
"repositoryURL": "https://github.com/jakeheis/SwiftCLI.git",
"state": {
"branch": null,
"revision": "2e949055d9797c1a6bddcda0e58dada16cc8e970",
"version": "6.0.3"
}
},
{
"package": "Version",
"repositoryURL": "https://github.com/mxcl/Version",
"state": {
"branch": null,
"revision": "a94b48f36763c05629fc102837398505032dead9",
"version": "2.0.0"
}
},
{
"package": "XcodeProj",
"repositoryURL": "https://github.com/tuist/XcodeProj.git",
"state": {
"branch": null,
"revision": "c4d5f9d7f789dd944222be95938810947561e559",
"version": "8.12.0"
}
},
{
"package": "Yams",
"repositoryURL": "https://github.com/jpsim/Yams.git",
"state": {
"branch": null,
"revision": "01835dc202670b5bb90d07f3eae41867e9ed29f6",
"version": "5.0.1"
}
"pins" : [
{
"identity" : "aexml",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tadija/AEXML.git",
"state" : {
"revision" : "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3",
"version" : "4.6.1"
}
]
},
"version": 1
},
{
"identity" : "graphviz",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SwiftDocOrg/GraphViz.git",
"state" : {
"revision" : "70bebcf4597b9ce33e19816d6bbd4ba9b7bdf038",
"version" : "0.2.0"
}
},
{
"identity" : "jsonutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/yonaskolb/JSONUtilities.git",
"state" : {
"revision" : "128d2ffc22467f69569ef8ff971683e2393191a0",
"version" : "4.2.0"
}
},
{
"identity" : "pathkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kylef/PathKit.git",
"state" : {
"revision" : "3bfd2737b700b9a36565a8c94f4ad2b050a5e574",
"version" : "1.0.1"
}
},
{
"identity" : "rainbow",
"kind" : "remoteSourceControl",
"location" : "https://github.com/onevcat/Rainbow.git",
"state" : {
"revision" : "626c3d4b6b55354b4af3aa309f998fae9b31a3d9",
"version" : "3.2.0"
}
},
{
"identity" : "spectre",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kylef/Spectre.git",
"state" : {
"revision" : "26cc5e9ae0947092c7139ef7ba612e34646086c7",
"version" : "0.10.1"
}
},
{
"identity" : "swiftcli",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jakeheis/SwiftCLI.git",
"state" : {
"revision" : "2e949055d9797c1a6bddcda0e58dada16cc8e970",
"version" : "6.0.3"
}
},
{
"identity" : "version",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mxcl/Version",
"state" : {
"revision" : "a94b48f36763c05629fc102837398505032dead9",
"version" : "2.0.0"
}
},
{
"identity" : "xcodeproj",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tuist/XcodeProj.git",
"state" : {
"revision" : "6e60fb55271c80f83a186c9b1b4982fd991cfc0a",
"version" : "8.13.0"
}
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/Yams.git",
"state" : {
"revision" : "01835dc202670b5bb90d07f3eae41867e9ed29f6",
"version" : "5.0.1"
}
}
],
"version" : 2
}

View File

@ -16,7 +16,7 @@ let package = Package(
.package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "4.2.0"),
.package(url: "https://github.com/kylef/Spectre.git", from: "0.9.2"),
.package(url: "https://github.com/onevcat/Rainbow.git", from: "3.0.0"),
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.12.0"),
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.13.0"),
.package(url: "https://github.com/jakeheis/SwiftCLI.git", from: "6.0.3"),
.package(url: "https://github.com/mxcl/Version", from: "2.0.0"),
.package(url: "https://github.com/SwiftDocOrg/GraphViz.git", exact: "0.2.0"),

View File

@ -0,0 +1,58 @@
import Foundation
import JSONUtilities
/// Specifies the use of a plug-in product in a target.
public struct BuildToolPlugin: Equatable {
/// The name of the plug-in target.
public var plugin: String
/// The name of the package that defines the plug-in target.
public var package: String
public init(
plugin: String,
package: String
) {
self.plugin = plugin
self.package = package
}
}
extension BuildToolPlugin: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
if let plugin: String = jsonDictionary.json(atKeyPath: "plugin") {
self.plugin = plugin
} else {
throw SpecParsingError.invalidDependency(jsonDictionary)
}
if let package: String = jsonDictionary.json(atKeyPath: "package") {
self.package = package
} else {
throw SpecParsingError.invalidDependency(jsonDictionary)
}
}
}
extension BuildToolPlugin {
public var uniqueID: String {
return "\(plugin)/\(package)"
}
}
extension BuildToolPlugin: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(plugin)
hasher.combine(package)
}
}
extension BuildToolPlugin: JSONEncodable {
public func toJSONValue() -> Any {
[
"plugin": plugin,
"package": package
]
}
}

View File

@ -174,6 +174,12 @@ extension Project {
errors.append(.invalidTargetSource(target: target.name, source: sourcePath.string))
}
}
for buildToolPlugin in target.buildToolPlugins {
if packages[buildToolPlugin.package] == nil {
errors.append(.invalidPluginPackageReference(plugin: buildToolPlugin.plugin, package: buildToolPlugin.package))
}
}
}
for projectReference in projectReferences {

View File

@ -36,6 +36,7 @@ public struct SpecValidationError: Error, CustomStringConvertible {
case invalidTestPlan(TestPlan)
case multipleDefaultTestPlans
case duplicateDependencies(target: String, dependencyReference: String)
case invalidPluginPackageReference(plugin: String, package: String)
public var description: String {
switch self {
@ -91,6 +92,8 @@ public struct SpecValidationError: Error, CustomStringConvertible {
return "Your test plans contain more than one default test plan"
case let .duplicateDependencies(target, dependencyReference):
return "Target \(target.quoted) has the dependency \(dependencyReference.quoted) multiple times"
case let .invalidPluginPackageReference(plugin, package):
return "Plugin \(plugin) has invalide package reference \(package)"
}
}
}

View File

@ -46,6 +46,7 @@ public struct Target: ProjectTarget {
public var directlyEmbedCarthageDependencies: Bool?
public var requiresObjCLinking: Bool?
public var preBuildScripts: [BuildScript]
public var buildToolPlugins: [BuildToolPlugin]
public var postCompileScripts: [BuildScript]
public var postBuildScripts: [BuildScript]
public var buildRules: [BuildRule]
@ -89,6 +90,7 @@ public struct Target: ProjectTarget {
directlyEmbedCarthageDependencies: Bool? = nil,
requiresObjCLinking: Bool? = nil,
preBuildScripts: [BuildScript] = [],
buildToolPlugins: [BuildToolPlugin] = [],
postCompileScripts: [BuildScript] = [],
postBuildScripts: [BuildScript] = [],
buildRules: [BuildRule] = [],
@ -113,6 +115,7 @@ public struct Target: ProjectTarget {
self.directlyEmbedCarthageDependencies = directlyEmbedCarthageDependencies
self.requiresObjCLinking = requiresObjCLinking
self.preBuildScripts = preBuildScripts
self.buildToolPlugins = buildToolPlugins
self.postCompileScripts = postCompileScripts
self.postBuildScripts = postBuildScripts
self.buildRules = buildRules
@ -223,6 +226,7 @@ extension Target: Equatable {
lhs.entitlements == rhs.entitlements &&
lhs.dependencies == rhs.dependencies &&
lhs.preBuildScripts == rhs.preBuildScripts &&
lhs.buildToolPlugins == rhs.buildToolPlugins &&
lhs.postCompileScripts == rhs.postCompileScripts &&
lhs.postBuildScripts == rhs.postBuildScripts &&
lhs.buildRules == rhs.buildRules &&
@ -312,7 +316,13 @@ extension Target: NamedJSONDictionaryConvertible {
return platforms.contains(platform)
}
}
if jsonDictionary["buildToolPlugins"] == nil {
buildToolPlugins = []
} else {
self.buildToolPlugins = try jsonDictionary.json(atKeyPath: "buildToolPlugins", invalidItemBehaviour: .fail)
}
if jsonDictionary["info"] != nil {
info = try jsonDictionary.json(atKeyPath: "info") as Plist
}
@ -348,6 +358,7 @@ extension Target: JSONEncodable {
"dependencies": dependencies.map { $0.toJSONValue() },
"postCompileScripts": postCompileScripts.map { $0.toJSONValue() },
"prebuildScripts": preBuildScripts.map { $0.toJSONValue() },
"buildToolPlugins": buildToolPlugins.map { $0.toJSONValue() },
"postbuildScripts": postBuildScripts.map { $0.toJSONValue() },
"buildRules": buildRules.map { $0.toJSONValue() },
"deploymentTarget": deploymentTarget?.deploymentTarget,

View File

@ -1032,6 +1032,23 @@ public class PBXProjGenerator {
carthageFrameworksToEmbed = carthageFrameworksToEmbed.uniqued()
// Adding `Build Tools Plug-ins` as a dependency to the target
for buildToolPlugin in target.buildToolPlugins {
let packageReference = packageReferences[buildToolPlugin.package]
if packageReference == nil, !localPackageReferences.contains(buildToolPlugin.package) {
continue
}
let packageDependency = addObject(
XCSwiftPackageProductDependency(productName: buildToolPlugin.plugin, package: packageReference, isPlugin: true)
)
let targetDependency = addObject(
PBXTargetDependency(product: packageDependency)
)
dependencies.append(targetDependency)
}
var buildPhases: [PBXBuildPhase] = []
func getBuildFilesForSourceFiles(_ sourceFiles: [SourceFile]) -> [PBXBuildFile] {

View File

@ -181,6 +181,7 @@
);
dependencies = (
078202CF7B08B66ACF7FEC23 /* PBXTargetDependency */,
E157C6348B8AD6A28C706801 /* PBXTargetDependency */,
);
name = App;
packageProductDependencies = (
@ -214,6 +215,7 @@
mainGroup = 218F6C96DF9E182F526258CF;
packageReferences = (
5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */,
348C81C327DB1710B742C370 /* XCRemoteSwiftPackageReference "Prefire" */,
E3887F3CB2C069E70D98092F /* XCRemoteSwiftPackageReference "SwiftRoaring" */,
);
projectDirPath = "";
@ -306,6 +308,10 @@
isa = PBXTargetDependency;
productRef = C816AEB28ED71C3C47F31B98 /* SwiftRoaringDynamic */;
};
E157C6348B8AD6A28C706801 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = DC47EF1BFBBD751E3C1C95E3 /* PrefirePlaybookPlugin */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@ -562,6 +568,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
348C81C327DB1710B742C370 /* XCRemoteSwiftPackageReference "Prefire" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/BarredEwe/Prefire";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.4.1;
};
};
5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/yonaskolb/Codability";
@ -604,6 +618,11 @@
package = E3887F3CB2C069E70D98092F /* XCRemoteSwiftPackageReference "SwiftRoaring" */;
productName = SwiftRoaringDynamic;
};
DC47EF1BFBBD751E3C1C95E3 /* PrefirePlaybookPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = 348C81C327DB1710B742C370 /* XCRemoteSwiftPackageReference "Prefire" */;
productName = "plugin:PrefirePlaybookPlugin";
};
DC73B269C8B0C0BF6912842C /* SwiftRoaringDynamic */ = {
isa = XCSwiftPackageProductDependency;
package = E3887F3CB2C069E70D98092F /* XCRemoteSwiftPackageReference "SwiftRoaring" */;

View File

@ -6,6 +6,9 @@ packages:
SwiftRoaring:
url: https://github.com/piotte13/SwiftRoaring
majorVersion: 1.0.4
Prefire:
url: https://github.com/BarredEwe/Prefire
majorVersion: 1.4.1
XcodeGen:
path: ../../.. #XcodeGen itself
group: SPM
@ -18,6 +21,9 @@ targets:
testTargets:
- package: XcodeGen/XcodeGenKitTests
- Tests
buildToolPlugins:
- plugin: PrefirePlaybookPlugin
package: Prefire
dependencies:
- package: Codability
weak: true

View File

@ -457,6 +457,37 @@ class ProjectSpecTests: XCTestCase {
try expectValidationError(project, .multipleDefaultTestPlans)
}
$0.it("fails on packages has not plugin packge reference") {
var project = baseProject
project.targets = [
Target(
name: "target",
type: .application,
platform: .iOS,
buildToolPlugins: [
BuildToolPlugin(plugin: "plugin", package: "package")
]
)
]
try expectValidationError(project, .invalidPluginPackageReference(plugin: "plugin", package: "package"))
}
$0.it("allow on packages has plugin packge reference") {
var project = baseProject
project.packages["package"] = .remote(url: "url", versionRequirement: .branch("branch"))
project.targets = [
Target(
name: "target",
type: .application,
platform: .iOS,
buildToolPlugins: [
BuildToolPlugin(plugin: "plugin", package: "package")
]
)
]
try expectNoValidationError(project, .invalidPluginPackageReference(plugin: "plugin", package: "package"))
}
}
}
@ -509,6 +540,7 @@ class ProjectSpecTests: XCTestCase {
runOnlyWhenInstalling: true,
showEnvVars: true,
basedOnDependencyAnalysis: false)],
buildToolPlugins: [BuildToolPlugin(plugin: "plugin", package: "Yams")],
postCompileScripts: [BuildScript(script: .path("cmd.sh"),
name: "Bar script",
inputFiles: ["foo"],

View File

@ -1510,6 +1510,29 @@ class SpecLoadingTests: XCTestCase {
try expect(scheme.run) == runAction
}
$0.it("parses buildToolPlugins") {
var target = validTarget
let buildToolPlugins: [[String: Any]] = [
[
"plugin": "FirstPlugin",
"package": "FirstPackage"
],
[
"plugin": "SecondPlugin",
"package": "SecondPackage"
]
]
target["buildToolPlugins"] = buildToolPlugins
let expectedBuildToolPlugins = [
BuildToolPlugin(plugin: "FirstPlugin", package: "FirstPackage"),
BuildToolPlugin(plugin: "SecondPlugin", package: "SecondPackage")
]
let parsedTarget = try Target(name: "test", jsonDictionary: target)
try expect(parsedTarget.buildToolPlugins) == expectedBuildToolPlugins
}
}
}