swift-sh icon indicating copy to clipboard operation
swift-sh copied to clipboard

Added support for @main attribute based scripts

Open IgorMuzyka opened this issue 4 weeks ago • 0 comments

Fixes #163. Added Examples/async-main-count-lines which is just this example from swift-argument-parser with #!/user/bin/swift sh at the top:

#!/user/bin/swift sh
import Foundation
import ArgumentParser // apple/swift-argument-parser ~> 1.4.0

/// example borrowed from [here](https://swiftpackageindex.com/apple/swift-argument-parser/1.4.0/documentation/argumentparser/asyncparsablecommand)
@main
struct CountLines: AsyncParsableCommand {
    @Argument(transform: URL.init(fileURLWithPath:))
    var inputFile: URL
    mutating func run() async throws {
        let fileHandle = try FileHandle(forReadingFrom: inputFile)
        let lineCount = try await fileHandle.bytes.lines.reduce(into: 0)
            { count, _ in count += 1 }
        print(lineCount)
    }
}

Added let mainStyle: ExecutableTargetMainStyle property to Script:

/// wheter the script has @main
public enum ExecutableTargetMainStyle {
    /// script has @main, script source file cannot be named main.swift, as this would be compilation error
    case mainAttribute
    /// script doesn't have @main, and it's ok to have main.swift file as script source
    case topLevelCode
}

To achieve successful compilation with @main attribute needed to change swift-tools-version to 5.5, and swap .target for .executableTarget. Also as comment mentions we can not have main.swift as source when we have @main, so the resulting script source filename is changed to Root.swift. Also we cannot have #! not in main.swift so it's filtered out when Script's mainStyle is .mainAttribute.

Tested this by running swift-sh target in Xcode 15.4 and passing two input arguments which are both absolute path to added example. Didn't test stdin based input.

All of the aforementioned changes only happen when we found a line containing @main and decided that Script mainStyle is .mainAttribute.

@helje5 suggested to have following change to shebang line #!/usr/bin/swift sh #@main, but i found it unnecessary, and currently check if script uses @main like this:

/// line has `@main`, line is not a comment.
line.contains("@main") && !(line.contains("//") || line.contains("/*"))

Unfortunately linking file as was previously isn't an option when a file has @main as only main.swift is allowed to have shebang #! and we need to remove that line in order for script having @main to compile successfully. So @main attribute and main.swift are mutually exclusive.

IgorMuzyka avatar Jun 04 '24 14:06 IgorMuzyka