swift-argument-parser icon indicating copy to clipboard operation
swift-argument-parser copied to clipboard

@Flag properties that begin with "v" do not work.

Open soconnor-florio opened this issue 3 years ago • 8 comments

I had a "verbose" flag for log output, but it was getting completely ignored and failed silently.

ArgumentParser version: 1.0.2 Swift version:

swift-driver version: 1.26.9 Apple Swift version 5.5.1 (swiftlang-1300.0.31.4 clang-1300.0.29.6)
Target: arm64-apple-macosx12.0

Checklist

  • [ ] If possible, I've reproduced the issue using the main branch of this package
  • [x] I've searched for existing GitHub issues

Steps to Reproduce

Create your own ParsableCommand with a flag such as:

struct MyCommand: ParsableCommand {

   // other boilerplate here.

    @Flag(name: .shortAndLong, help: "Whether to enable verbose logging.")
    var verbose = false
    
    mutating func run() throws {
      print("Verbose Logging Enabled: \(self.verbose)") // is ALWAYS false.
    
       // the rest of what you do here.
    }
}

If you include this flag in your command line arguments, it is not parsed as expected.

Expected behavior

The Flag should be set to true when you swift run MyCommand --verbose

Stretch goal: The compiler should fail or complain or warn about your variable name.

Actual behavior

The argument --verbose is ignored and "fails silently"

Workaround

make a custom flag name that doesn't begin with "v". In this case "fatlog" did the trick.

soconnor-florio avatar Dec 22 '21 13:12 soconnor-florio

I can't reproduce this, either with a preexisting project or a brand new one. 🤔

Wevah avatar Dec 28 '21 22:12 Wevah

I can't reproduce this either, and we have a variety of v-prefixed flags in the tests. Could you include some more information about how this command is configured? Are you using the @main attribute or e.g. calling parseAsRoot()?

natecook1000 avatar Jan 02 '22 19:01 natecook1000

I set up my command line tool as a Swift package. Could it have to do with that?

import PackageDescription

let package = Package(
    name: "Locobit",
    platforms: [.macOS(.v11)],
    products: [
        .executable(name: "locobit", targets: ["LocobitTool"])
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .executableTarget(
            name: "LocobitTool",
            dependencies: ["LocobitCore"]),
        .target(
            name: "LocobitCore",
            dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser")
            ]),
        .testTarget(
            name: "LocobitCoreTests",
            dependencies: ["LocobitCore"],
            resources: [
                .copy("Fixtures/Localization.zip"),
                .copy("Fixtures/Source.zip"),
                .copy("Fixtures/TestLocalizable.strings"),
                .copy("Fixtures/TestSourceFile.sweeft"), // yes, it's called .sweeft.  Look at the comments in that file for the reason for that.
            ]
        ),
    ],
    swiftLanguageVersions: [.v5]
)

Then the tool executable looks like this:

// main.swift
import LocobitCore

CommandLineTool.main()
// CommandLineTool.swift
import Foundation

public final class CommandLineTool {
    public static func main(_ arguments: [String]? = nil) {
        LocobitCommand.main(arguments)
    }
}

Then the part of the ParsableCommand that's likely relevant:

import ArgumentParser
import Foundation

struct LocobitCommand: ParsableCommand {
   // other boilerplate, configuration, other options and flags here.
   
   @Flag(name: .customLong("fatlog", withSingleDash: true), help: "Whether to enable verbose logging.")
    var verboseLogging = false
    
    mutating func run() throws {
        
        logger.info("Locobit v\(Locobit.version)\n")

        logger.isVerbose = self.verboseLogging
        
        let configuration = try createConfiguration()
        
        try runLocobit(with: configuration)
        
        throw ExitCode.success
    }
}

You can see that when I make it a custom long flag, it will work. Otherwise it won't. At least not when you run the executable (i.e. from the command line). It took a while to track down as tests weren't finding it as it wasn't testing via the executable, but testing the LocobitCore target.

soconnor-florio avatar Jan 03 '22 08:01 soconnor-florio

The issue is most likely because swift run is eating the --verbose flag

rauhul avatar Jan 10 '22 03:01 rauhul

repro.zip

➜  repro swift run repro
[3/3] Build complete!
Verbose Logging Enabled: false
➜  repro swift run repro --verbose
/usr/bin/xcrun --sdk macosx --show-sdk-platform-path
/usr/bin/xcrun --sdk macosx --find xctest
/usr/bin/xcrun --sdk macosx --show-sdk-platform-path
/usr/bin/xcrun --sdk macosx --show-sdk-platform-path
/usr/bin/xcrun --sdk iphoneos --show-sdk-platform-path
/usr/bin/xcrun --sdk appletvos --show-sdk-platform-path
/usr/bin/xcrun --sdk watchos --show-sdk-platform-path
[0/0] Build complete!
Verbose Logging Enabled: false
➜  repro .build/debug/repro --verbose
Verbose Logging Enabled: true

rauhul avatar Jan 10 '22 03:01 rauhul

If that's the case, does inserting a -- work? E.g.,

swift run -- MyCommand --verbose

Wevah avatar Jan 10 '22 05:01 Wevah

@soconnor-florio Recent releases have fixed the behavior that @rauhul described above — are you able to reproduce this behavior with a Swift 5.6 toolchain?

natecook1000 avatar Mar 15 '22 15:03 natecook1000

When I update my toolchain I can investigate

soconnor-florio avatar Mar 28 '22 07:03 soconnor-florio