Tuesday, February 7, 2023
Learning Code
  • Home
  • JavaScript
  • Java
  • Python
  • Swift
  • C++
  • C#
No Result
View All Result
  • Home
  • JavaScript
  • Java
  • Python
  • Swift
  • C++
  • C#
No Result
View All Result
Learning Code
No Result
View All Result
Home Swift

Beginner’s guide to Swift package manager command plugins

learningcode_x1mckf by learningcode_x1mckf
September 6, 2022
in Swift
0
Beginner’s guide to Swift package manager command plugins
74
SHARES
1.2k
VIEWS
Share on FacebookShare on Twitter


2022/05/16

Learn to create command plugins for the Swift Bundle Supervisor to execute customized actions utilizing SPM and different instruments.

Swift

Introduction to Swift Bundle Supervisor plugins


Initially I might like to speak just a few phrases in regards to the new SPM plugin infrastructure, that was launched within the Swift 5.6 launch. The very first proposal describes the detailed design of the plugin API with some plugin examples, that are fairly helpful. Truthfully talking I used to be a bit to lazy to rigorously learn via your complete documentation, it is fairly lengthy, however lengthy story brief, you possibly can create the next plugin sorts with the at the moment current APIs:


  • Construct instruments – may be invoked through the SPM targets
    • pre-build – runs earlier than the construct begins
    • construct – runs through the construct
  • Instructions – may be invoked through the command line
    • supply code formatting – modifies the code inside package deal
    • documentation technology – generate docs for the package deal
    • customized – person outlined intentions

For the sake of simplicity on this tutorial I am solely going to put in writing a bit in regards to the second class, aka. the command plugins. These plugins had been a bit extra attention-grabbing for me, as a result of I wished to combine my deployment workflow into SPM, so I began to experiment with the plugin API to see how laborious it’s to construct such a factor. Seems it is fairly simple, however the developer expertise it is not that good. 😅



Constructing a supply code formatting plugin

The very very first thing I wished to combine with SPM was SwiftLint, since I used to be not capable of finding a plugin implementation that I may use I began from scratch. As a place to begin I used to be utilizing the example code from the Package Manager Command Plugins proposal.


mkdir Instance
cd Instance
swift package deal init --type=library


I began with a model new package deal, utilizing the swift package deal init command, then I modified the Bundle.swift file in accordance with the documentation. I’ve additionally added SwiftLint as a package deal dependency so SPM can obtain & construct the and hopefully my customized plugin command can invoke the swiftlint executable when it’s wanted.



import PackageDescription

let package deal = Bundle(
    title: "Instance",
    platforms: [
        .macOS(.v10_15),
    ],
    merchandise: [
        .library(name: "Example", targets: ["Example"]),
        .plugin(title: "MyCommandPlugin", targets: ["MyCommandPlugin"]),
    ],
    dependencies: [
        .package(url: "https://github.com/realm/SwiftLint", branch: "master"),
    ],
    targets: [
        .target(name: "Example", dependencies: []),
        .testTarget(title: "ExampleTests", dependencies: ["Example"]),
       
        .plugin(title: "MyCommandPlugin",
                functionality: .command(
                    intent: .sourceCodeFormatting(),
                    permissions: [
                        .writeToPackageDirectory(reason: "This command reformats source files")
                    ]
                ),
                dependencies: [
                    .product(name: "swiftlint", package: "SwiftLint"),
                ]),
    ]
)


I’ve created a Plugins listing with a essential.swift file proper subsequent to the Sources folder, with the next contents.


import PackagePlugin
import Basis

@essential
struct MyCommandPlugin: CommandPlugin 
    
    func performCommand(context: PluginContext, arguments: [String]) throws 
        let software = strive context.software(named: "swiftlint")
        let toolUrl = URL(fileURLWithPath: software.path.string)
        
        for goal in context.package deal.targets 
            guard let goal = goal as? SourceModuleTarget else  proceed 

            let course of = Course of()
            course of.executableURL = toolUrl
            course of.arguments = [
                "(target.directory)",
                "--fix",
               
            ]

            strive course of.run()
            course of.waitUntilExit()
            
            if course of.terminationReason == .exit && course of.terminationStatus == 0 
                print("Formatted the supply code in (goal.listing).")
            
            else 
                let downside = "(course of.terminationReason):(course of.terminationStatus)"
                Diagnostics.error("swift-format invocation failed: (downside)")
            
        
    


The snippet above ought to find the swiftlint software utilizing the plugins context then it will iterate via the obtainable package deal targets, filter out non source-module targets and format solely these targets that accommodates precise Swift supply recordsdata. The method object ought to merely invoke the underlying software, we are able to wait till the kid (swiftlint invocation) course of exists and hopefully we’re good to go. 🤞


Replace: kalKarmaDev informed me that it’s doable to cross the --in-process-sourcekit argument to SwiftLint, this may repair the underlying difficulty and the supply recordsdata are literally fastened.


I wished to checklist the obtainable plugins & run my supply code linter / formatter utilizing the next shell instructions, however sadly looks as if the swiftlint invocation half failed for some unusual purpose.




swift package deal plugin --list
    swift package deal format-source-code #will not work, wants entry to supply recordsdata
    swift package deal --allow-writing-to-package-directory format-source-code



Looks as if there’s an issue with the exit code of the invoked swiftlint course of, so I eliminated the success examine from the plugin supply to see if that is inflicting the difficulty or not additionally tried to print out the executable command to debug the underlying downside.


import PackagePlugin
import Basis

@essential
struct MyCommandPlugin: CommandPlugin 
    
    func performCommand(context: PluginContext, arguments: [String]) throws 
        let software = strive context.software(named: "swiftlint")
        let toolUrl = URL(fileURLWithPath: software.path.string)
        
        for goal in context.package deal.targets 
            guard let goal = goal as? SourceModuleTarget else  proceed 

            let course of = Course of()
            course of.executableURL = toolUrl
            course of.arguments = [
                "(target.directory)",
                "--fix",
            ]

            print(toolUrl.path, course of.arguments!.joined(separator: " "))

            strive course of.run()
            course of.waitUntilExit()
        
    


Deliberately made a small “mistake” within the Instance.swift supply file, so I can see if the swiftlint –fix command will remedy this difficulty or not. 🤔


public struct Instance 
    public non-public(set) var textual content = "Hi there, World!"

    public init() 
        let xxx :Int = 123
    


Seems, once I run the plugin through the Process invocation, nothing occurs, however once I enter the next code manually into the shell, it simply works.


/Customers/tib/Instance/.construct/arm64-apple-macosx/debug/swiftlint /Customers/tib/Instance/Assessments/Instance --fix
/Customers/tib/Instance/.construct/arm64-apple-macosx/debug/swiftlint /Customers/tib/Instance/Assessments/ExampleTests --fix


All proper, so we undoubtedly have an issue right here… I attempted to get the usual output message and error message from the operating course of, looks as if swiftlint runs, however one thing within the SPM infrastructure blocks the code adjustments within the package deal. After a number of hours of debugging I made a decision to offer a shot to swift-format, as a result of that is what the official docs counsel. 🤷‍♂️



import PackageDescription

let package deal = Bundle(
    title: "Instance",
    platforms: [
        .macOS(.v10_15),
    ],
    merchandise: [
        .library(name: "Example", targets: ["Example"]),
        .plugin(title: "MyCommandPlugin", targets: ["MyCommandPlugin"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-format", exact: "0.50600.1"),
    ],
    targets: [
        .target(name: "Example", dependencies: []),
        .testTarget(title: "ExampleTests", dependencies: ["Example"]),
       
        .plugin(title: "MyCommandPlugin",
                functionality: .command(
                    intent: .sourceCodeFormatting(),
                    permissions: [
                        .writeToPackageDirectory(reason: "This command reformats source files")
                    ]
                ),
                dependencies: [
                    .product(name: "swift-format", package: "swift-format"),
                ]),
    ]
)


Modified each the Bundle.swift file and the plugin supply code, to make it work with swift-format.


import PackagePlugin
import Basis

@essential
struct MyCommandPlugin: CommandPlugin 
    
    func performCommand(context: PluginContext, arguments: [String]) throws 
        let swiftFormatTool = strive context.software(named: "swift-format")
        let swiftFormatExec = URL(fileURLWithPath: swiftFormatTool.path.string)

        
        for goal in context.package deal.targets 
            guard let goal = goal as? SourceModuleTarget else  proceed 

            let course of = Course of()
            course of.executableURL = swiftFormatExec
            course of.arguments = [

                "--in-place",
                "--recursive",
                "(target.directory)",
            ]
            strive course of.run()
            course of.waitUntilExit()

            if course of.terminationReason == .exit && course of.terminationStatus == 0 
                print("Formatted the supply code in (goal.listing).")
            
            else 
                let downside = "(course of.terminationReason):(course of.terminationStatus)"
                Diagnostics.error("swift-format invocation failed: (downside)")
            
        
    


I attempted to run once more the very same package deal plugin command to format my supply recordsdata, however this time swift-format was doing the code formatting as an alternative of swiftlint.


swift package deal --allow-writing-to-package-directory format-source-code
// ... loading dependencies
Construct full! (6.38s)
Formatted the supply code in /Customers/tib/Linter/Assessments/ExampleTests.
Formatted the supply code in /Customers/tib/Linter/Sources/Instance.


Labored like a attraction, my Instance.swift file was fastened and the : was on the left aspect… 🎊


public struct Instance 
    public non-public(set) var textual content = "Hi there, World!"

    public init() 
        let xxx: Int = 123
    


Yeah, I’ve made some progress, however it took me numerous time to debug this difficulty and I do not like the truth that I’ve to fiddle with processes to invoke different instruments… my intestine tells me that SwiftLint just isn’t following the usual shell exit standing codes and that is inflicting some points, possibly it is spawning little one processes and that is the issue, I actually do not know however I do not wished to waste extra time on this difficulty, however I wished to maneuver ahead with the opposite class. 😅




Integrating the DocC plugin with SPM


As a primary step I added some dummy feedback to my Instance library to have the ability to see one thing within the generated documentation, nothing fancy just a few one-liners. 📖



public struct Instance 

    
    public non-public(set) var textual content = "Hi there, World!"
    
    
    public init() 
        let xxx: Int = 123
    


I found that Apple has an official DocC plugin, so I added it as a dependency to my challenge.



import PackageDescription

let package deal = Bundle(
    title: "Instance",
    platforms: [
        .macOS(.v10_15),
    ],
    merchandise: [
        .library(name: "Example", targets: ["Example"]),
        .plugin(title: "MyCommandPlugin", targets: ["MyCommandPlugin"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-format", exact: "0.50600.1"),
        .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),

    ],
    targets: [
        .target(name: "Example", dependencies: []),
        .testTarget(title: "ExampleTests", dependencies: ["Example"]),
       
        .plugin(title: "MyCommandPlugin",
                functionality: .command(
                    intent: .sourceCodeFormatting(),
                    permissions: [
                        .writeToPackageDirectory(reason: "This command reformats source files")
                    ]
                ),
                dependencies: [
                    .product(name: "swift-format", package: "swift-format"),
                ]),
    ]
)


Two new plugin instructions had been obtainable after I executed the plugin checklist command.


swift package deal plugin --list




Tried to run the primary one, and fortuitously the doccarchive file was generated. 😊


swift package deal generate-documentation





Additionally tried to preview the documentation, there was a notice in regards to the --disable-sandbox flag within the output, so I merely added it to my authentic command and…


swift package deal preview-documentation 

swift package deal --disable-sandbox preview-documentation


Magic. It labored and my documentation was obtainable. Now that is how plugins ought to work, I cherished this expertise and I actually hope that an increasing number of official plugins are coming quickly. 😍




Constructing a customized intent command plugin


I wished to construct a small executable goal with some bundled assets and see if a plugin can deploy the executable binary with the assets. This may very well be very helpful once I deploy feather apps, I’ve a number of module bundles there and now I’ve to manually copy every thing… 🙈



import PackageDescription

let package deal = Bundle(
    title: "Instance",
    platforms: [
        .macOS(.v10_15),
    ],
    merchandise: [
        .library(name: "Example", targets: ["Example"]),
        .executable(title: "MyExample", targets: ["MyExample"]),
        .plugin(title: "MyCommandPlugin", targets: ["MyCommandPlugin"]),
        .plugin(title: "MyDistCommandPlugin", targets: ["MyDistCommandPlugin"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-format", exact: "0.50600.1"),
        .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),

    ],
    targets: [
        .executableTarget(name: "MyExample",
                          resources: [
                            .copy("Resources"),
                          ], plugins: [
                            
                          ]),
        .goal(title: "Instance", dependencies: []),
        .testTarget(title: "ExampleTests", dependencies: ["Example"]),
       
        .plugin(title: "MyCommandPlugin",
                functionality: .command(
                    intent: .sourceCodeFormatting(),
                    permissions: [
                        .writeToPackageDirectory(reason: "This command reformats source files")
                    ]
                ),
                dependencies: [
                    .product(name: "swift-format", package: "swift-format"),
                ]),
        
        .plugin(title: "MyDistCommandPlugin",
                functionality: .command(
                    intent: .customized(verb: "dist", description: "Create dist archive"),
                    permissions: [
                        .writeToPackageDirectory(reason: "This command deploys the executable")
                    ]
                ),
                dependencies: [
                ]),
    ]
)


As a primary step I created a brand new executable goal referred to as MyExample and a brand new MyDistCommandPlugin with a customized verb. Contained in the Sources/MyExample/Sources folder I’ve positioned a easy check.json file with the next contents.



    "success": true


The essential.swift file of the MyExample goal seems like this. It simply validates that the useful resource file is on the market and it merely decodes the contents of it and prints every thing to the usual output. 👍


import Basis

guard let jsonFile = Bundle.module.url(forResource: "Sources/check", withExtension: "json") else 
    fatalError("Bundle file not discovered")

let jsonData = strive Knowledge(contentsOf: jsonFile)

struct Json: Codable 
    let success: Bool


let json = strive JSONDecoder().decode(Json.self, from: jsonData)

print("Is success?", json.success)


Contained in the Plugins folder I’ve created a essential.swift file beneath the MyDistCommandPlugin folder.


import PackagePlugin
import Basis

@essential
struct MyDistCommandPlugin: CommandPlugin 
    
    func performCommand(context: PluginContext, arguments: [String]) throws 
        
        
    


Now I used to be in a position to re-run the swift package deal plugin --list command and the dist verb appeared within the checklist of obtainable instructions. Now the one query is: how can we get the artifacts out of the construct listing? Luckily the 3rd example of the instructions proposal is kind of related.


import PackagePlugin
import Basis

@essential
struct MyDistCommandPlugin: CommandPlugin 
    
    func performCommand(context: PluginContext, arguments: [String]) throws 
        let cpTool = strive context.software(named: "cp")
        let cpToolURL = URL(fileURLWithPath: cpTool.path.string)

        let outcome = strive packageManager.construct(.product("MyExample"), parameters: .init(configuration: .launch, logging: .concise))
        guard outcome.succeeded else 
            fatalError("could not construct product")
        
        guard let executable = outcome.builtArtifacts.first(the place :  $0.type == .executable ) else 
            fatalError("could not discover executable")
        
        
        let course of = strive Course of.run(cpToolURL, arguments: [
            executable.path.string,
            context.package.directory.string,
        ])
        course of.waitUntilExit()

        let exeUrl = URL(fileURLWithPath: executable.path.string).deletingLastPathComponent()
        let bundles = strive FileManager.default.contentsOfDirectory(atPath: exeUrl.path).filter  $0.hasSuffix(".bundle") 

        for bundle in bundles 
            let course of = strive Course of.run(cpToolURL, arguments: ["-R",
                                                                    exeUrl.appendingPathComponent(bundle).path,
                                                                    context.package.directory.string,
                                                                ])
            course of.waitUntilExit()
        
    


So the one downside was that I used to be not in a position to get again the bundled assets, so I had to make use of the URL of the executable file, drop the final path element and skim the contents of that listing utilizing the FileManager to get again the .bundle packages inside that folder.


Sadly the builtArtifacts property solely returns the executables and libraries. I actually hope that we will get help for bundles as nicely sooner or later so this hacky answer may be prevented for good. Anyway it really works simply fantastic, however nonetheless it is a hack, so use it rigorously. ⚠️


swift package deal --allow-writing-to-package-directory dist
./MyExample 


I used to be in a position to run my customized dist command with out additional points, after all you should utilize extra arguments to customise your plugin or add extra flexibility, the examples within the proposal are just about okay, however it’s fairly unlucky that there isn’t any official documentation for Swift package deal supervisor plugins simply but. 😕



Conclusion

Studying about command plugins was enjoyable, however to start with it was annoying as a result of I anticipated a bit higher developer expertise concerning the software invocation APIs. In abstract I can say that that is just the start. It is identical to the async / await and actors addition to the Swift language. The characteristic itself is there, it is principally able to go, however not many builders are utilizing it each day. These items would require time and hopefully we will see much more plugins afterward… 💪







Source link

You might also like

The abstract Vapor service factory design pattern

SwiftNIO tutorial – The echo server

Introducing – Vapor cheatsheet – The.Swift.Dev.

Share30Tweet19
learningcode_x1mckf

learningcode_x1mckf

Recommended For You

The abstract Vapor service factory design pattern

by learningcode_x1mckf
February 1, 2023
0
The abstract Vapor service factory design pattern

I've written a number of articles about manufacturing unit design patterns on my weblog and this time I might like to speak a couple of particular one, which...

Read more

SwiftNIO tutorial – The echo server

by learningcode_x1mckf
January 27, 2023
0
SwiftNIO tutorial – The echo server

Intoducing SwiftNIO In the event you used a excessive degree net framework, corresponding to Vapor, up to now, you would possibly had some interplay with occasion loops...

Read more

Introducing – Vapor cheatsheet – The.Swift.Dev.

by learningcode_x1mckf
January 23, 2023
0
Introducing – Vapor cheatsheet – The.Swift.Dev.

Out there on Gumroad Thanks for supporting my work by buying the cheatsheet. 🙏 Download now A whole Vapor framework reference for novices. greater than...

Read more

iCloud Programming Tutorial for iOS: An Introduction

by learningcode_x1mckf
January 18, 2023
0
iCloud Programming Tutorial for iOS: An Introduction

Editor’s observe: This week, we work with Ziad Tamim once more to provide you an introduction of iCloud programming. You’ll learn to save and retrieve knowledge from iCloud.On...

Read more

Easy multipart file upload for Swift

by learningcode_x1mckf
January 18, 2023
0
Easy multipart file upload for Swift

I imagine that you've got already heard in regards to the well-known multipart-data add method that everybody likes to add recordsdata and submit type knowledge, but when not,...

Read more
Next Post
Time limit for notify – JavaScript – SitePoint Forums

forEach not working as expected in NodeJs - JavaScript - SitePoint Forums

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Related News

8 Java frameworks for embedded development

8 Java frameworks for embedded development

September 5, 2022
Top Game Engines for C++ to use in 2022

Top Game Engines for C++ to use in 2022

September 13, 2022
Time limit for notify – JavaScript – SitePoint Forums

Resizing images using database and dropdowns – JavaScript – SitePoint Forums

November 28, 2022

Browse by Category

  • C#
  • C++
  • Java
  • JavaScript
  • Python
  • Swift

RECENT POSTS

  • JobRunr, the Java Scheduler Library, Released Version 6.0 – InfoQ.com
  • An Introduction to Lodash and Its Benefits for JavaScript Developers – MUO – MakeUseOf
  • "Used properly, Python is not slower than C++" – eFinancialCareers (US)

CATEGORIES

  • C#
  • C++
  • Java
  • JavaScript
  • Python
  • Swift

© 2022 Copyright Learning Code

No Result
View All Result
  • Home
  • JavaScript
  • Java
  • Python
  • Swift
  • C++
  • C#

© 2022 Copyright Learning Code

Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?