Skip to content

Conversation

kalinjul
Copy link
Contributor

@kalinjul kalinjul commented Aug 12, 2022

Checklist

  • I've run bundle exec rspec from the root directory to see all new and existing tests pass
  • I've followed the fastlane code style and run bundle exec rubocop -a to ensure the code style is valid
  • I see several green ci/circleci builds in the "All checks have passed" section of my PR (connect CircleCI to GitHub if not)
  • I've read the Contribution Guidelines
  • I've updated the documentation if necessary.

Motivation and Context

Fastlane Swift does not abort when lanes fail.
Resolves #20419
Resolves #20649

Description

This just adds a fatalError()

@google-cla

This comment was marked as outdated.

@rafaelnobrekz
Copy link

Any updates to this? I'd love to be able to exit(1) from fastlane when it fails (or even manually) to offload some shell scripts to more powerful and easier to work with lanes

@rogerluan rogerluan changed the title fail lanes on errors [Fastlane.swift] fix issue where failing lanes would exit with a success exit code Feb 10, 2024
Copy link
Member

@rogerluan rogerluan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kalinjul thanks for this PR!

Could provide steps to reproduce the issue ("how to test"), this would help us significantly!

@SvenTiigi
Copy link
Contributor

@rogerluan please find attached a small example Swift Package to reproduce the described issue:

Package.swift

// swift-tools-version: 5.9

import PackageDescription

let package = Package(
    name: "FastlaneSwift",
    platforms: [
        .macOS(.v10_13)
    ],
    products: [
        .executable(
            name: "FastlaneSwift",
            targets: ["FastlaneSwift"]
        )
    ],
    dependencies: [
        .package(
            url: "https://github.com/fastlane/fastlane",
            from: "2.219.0"
        )
    ],
    targets: [
        .executableTarget(
            name: "FastlaneSwift",
            dependencies: [
                .product(
                    name: "Fastlane",
                    package: "fastlane"
                )
            ],
            path: "Sources"
        )
    ]
)

Sources/Fastfile.swift

import Foundation
import Fastlane

@main
class Fastfile: Fastlane.LaneFile {
    
    static func main() {
        Fastlane.Main().run(with: Fastfile())
    }
    
    func exampleLane() {
        // Execute a non existing command to trigger a fastlane error
        Fastlane.sh(command: "Non_Existing_Command_Which_Should_Cause_a_Failure")
    }
    
}

Run example lane via:

$ swift run FastlaneSwift lane example

The command exits with a zero success code although the execution of the lane failed.
This can be verified by executing echo $? to retrieve the exit status code of the recently executed command.

$ swift run FastlaneSwift lane example

[...]
error encountered while executing command:
serverError
Encountered a problem: No such file or directory - Non_Existing_Command_Which_Should_Cause_a_Failure
Errno::ENOENT:
[...]

$ echo $?
0

Copy link
Collaborator

@revolter revolter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run example lane via:

$ swift run FastlaneSwift lane example

According to https://docs.fastlane.tools/getting-started/ios/fastlane-swift/, it looks like you're supposed to run it like:

fastlane example

instead.

From my tests, the latest version of fastlane correctly exits with code 1, with a nice and clean output, while these changes actually causes another crash that hides the original one:

[19:31:06]: ▸ FastlaneRunner/LaneFileProtocol.swift:155: Fatal error: Aborting run due to lane failure
[19:31:06]: ▸ sh: line 1: 78180 Trace/BPT trap: 5       ./fastlane/FastlaneRunner lane testCrash swiftServerPort 2000 > /dev/null
#<Thread:0x0000000104edc600 /Users/revolt/Development/GitHub/fastlane/fastlane/fastlane/lib/fastlane/swift_lane_manager.rb:98 run> terminated with exception (report_on_exception is true):
/Users/revolt/Development/GitHub/fastlane/fastlane/fastlane_core/lib/fastlane_core/ui/interface.rb:153:in `shell_error!': Exit status of command './fastlane/FastlaneRunner lane testCrash swiftServerPort 2000 > /dev/null' was 133 instead of 0. (FastlaneCore::Interface::FastlaneShellError)
FastlaneRunner/LaneFileProtocol.swift:155: Fatal error: Aborting run due to lane failure
sh: line 1: 78180 Trace/BPT trap: 5       ./fastlane/FastlaneRunner lane testCrash swiftServerPort 2000 > /dev/null
        from /Users/revolt/Development/GitHub/fastlane/fastlane/fastlane_core/lib/fastlane_core/ui/ui.rb:17:in `method_missing'
        from /Users/revolt/Development/GitHub/fastlane/fastlane/fastlane/lib/fastlane/helper/sh_helper.rb:80:in `sh_control_output'
        from /Users/revolt/Development/GitHub/fastlane/fastlane/fastlane/lib/fastlane/helper/sh_helper.rb:12:in `sh'
        from /Users/revolt/Development/GitHub/fastlane/fastlane/fastlane/lib/fastlane/swift_lane_manager.rb:103:in `block in cruise_swift_lane_in_thread'

Please try using the documented command (or, if you're using Bundler, then use bundle exec fastlane example instead), and let me know what happens.

@SvenTiigi
Copy link
Contributor

SvenTiigi commented Oct 4, 2024

@revolter I think this PR references the "Get Started (SPM) (Beta)" section which describes in Step 5 that you can build your Swift Package via swift build and run the executable via myExecutable lane <lane> to run the lane aka. swift run myExecutable lane <lane>

The invocation via fastlane <lane> is needed when you are making use of the FastlaneSwiftRunner.xcodeproj which gets initialized if you are running fastlane init swift.

Since the PR #20563 is merged, this problem can be fixed by manually overriding the onError lifecycle function to exit the program with a bad status code.

Of course this code shouldn't be mandatory as it would be better if Fastlane.Swift exits with the correct status code when executed via the Swift CLI (swift run ...)

import Foundation
import Fastlane

@main
class Fastfile: Fastlane.LaneFile {
    
    static func main() {
        Fastlane.Main().run(with: Fastfile())
    }
    
    func exampleLane() {
        // Execute a non existing command to trigger a fastlane error
        Fastlane.sh(command: "Non_Existing_Command_Which_Should_Cause_a_Failure")
    }
    
    open override func onError(
        currentLane: String,
        errorInfo: String,
        errorClass: String?,
        errorMessage: String?
    ) {
        super.onError(
            currentLane: currentLane,
            errorInfo: errorInfo,
            errorClass: errorClass,
            errorMessage: errorMessage
        )
        Foundation.exit(Foundation.EXIT_FAILURE)
    }
    
}

@revolter
Copy link
Collaborator

revolter commented Oct 4, 2024

Thanks for the clarification @SvenTiigi 🙏🏻

What I worry about is that these changes are not be backwards compatible. So merging this PR would make a handful of people happy, but potentially make many more people unhappy. I found these releases that had some sort of migration notes: https://github.com/fastlane/fastlane/releases/tag/1.30.0, https://github.com/fastlane/fastlane/releases/tag/2.4.0, https://github.com/fastlane/fastlane/releases/tag/2.188.0 (cc @joshdholtz).

Since I never used Fastlane.swift, would someone more experienced be so kind to test:

@SvenTiigi
Copy link
Contributor

SvenTiigi commented Oct 5, 2024

I think one way to ensure that this PR does not introduce any kind of of backwards incompatibility or regression the required exit code should be wrapped in an #if SWIFT_PACKAGE preprocessor macro.

This way the code to exit the program (Swift executable) with a bad status code in case of a failure only gets executed in the context of a Swift Package and not in the case of the Xcode Project (FastlaneSwiftRunner).

// Call all methods that need to be called before we start calling lanes.
fastfileInstance.beforeAll(with: lane)

// We need to catch all possible errors here and display a nice message.
_ = fastfileInstance.perform(NSSelectorFromString(laneMethod), with: parameters)

#if SWIFT_PACKAGE
// Call only on error
if LaneFile.onErrorCalled.contains(lane) {
    // TODO: Logging needed? `log(message "...")`
    Foundation.exit(Foundation.EXIT_FAILURE)
}
#endif

// Call only on success.
if !LaneFile.onErrorCalled.contains(lane) {
    fastfileInstance.afterAll(with: lane)
}

log(message: "Done running lane: \(lane) 🚀")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fastlane swift: exit code 0 even when lane fails Fastlane swift: exit code 0 even when lane fails
7 participants