diff --git a/Sources/Frontend/CommonSetupGuide.swift b/Sources/Frontend/CommonSetupGuide.swift index 8fb491bca..50504f89a 100644 --- a/Sources/Frontend/CommonSetupGuide.swift +++ b/Sources/Frontend/CommonSetupGuide.swift @@ -13,7 +13,7 @@ final class CommonSetupGuide: SetupGuideHelpers { func perform() throws { print(colorize("\nAssume all 'public' declarations are in use?", .bold)) - print(colorize("?", .boldYellow) + " Choose 'Yes' if your project is a framework/library without a main application target.") + print("Choose 'Yes' if your project is a framework/library without a main application target.") configuration.retainPublic = selectBoolean() } diff --git a/Sources/Frontend/GuidedSetup.swift b/Sources/Frontend/GuidedSetup.swift index 8d0c9db5c..209e65671 100644 --- a/Sources/Frontend/GuidedSetup.swift +++ b/Sources/Frontend/GuidedSetup.swift @@ -45,8 +45,9 @@ final class GuidedSetup: SetupGuideHelpers { let kindName = select(single: projectGuides.map(\.projectKindName)) projectGuide_ = projectGuides.first { $0.projectKindName == kindName } print("") - } else { - projectGuide_ = projectGuides.first + } else if let singleGuide = projectGuides.first { + print(colorize("*", .boldGreen) + " Detected \(singleGuide.projectKindName) project") + projectGuide_ = singleGuide } guard let projectGuide = projectGuide_ else { diff --git a/Sources/Frontend/SPMProjectSetupGuide.swift b/Sources/Frontend/SPMProjectSetupGuide.swift index e7ee15577..9afa3fb7c 100644 --- a/Sources/Frontend/SPMProjectSetupGuide.swift +++ b/Sources/Frontend/SPMProjectSetupGuide.swift @@ -10,7 +10,7 @@ final class SPMProjectSetupGuide: SetupGuideHelpers, SetupGuide { } var projectKindName: String { - "Swift Project Manager" + "Swift Package" } func perform() throws -> ProjectKind { diff --git a/Sources/Shared/SetupGuide.swift b/Sources/Shared/SetupGuide.swift index 08a471001..0cdec2b88 100644 --- a/Sources/Shared/SetupGuide.swift +++ b/Sources/Shared/SetupGuide.swift @@ -33,9 +33,9 @@ open class SetupGuideHelpers { } public func select(single options: [String]) -> String { - print(colorize("?", .boldYellow) + " Type the number for the option you wish to select") display(options: options) - print(colorize("> ", .bold), terminator: "") + print(colorize("?", .boldYellow) + " Type the number for the option you wish to select") + print(colorize("=> ", .bold), terminator: "") if let strChoice = readLine(strippingNewline: true)?.trimmed, let choice = Int(strChoice) @@ -51,40 +51,32 @@ open class SetupGuideHelpers { return select(single: options) } - public func select(multiple options: [String], allowAll: Bool) -> SetupSelection { + public func select(multiple options: [String]) -> SetupSelection { var helpMsg = " Delimit choices with a single space, e.g: 1 2 3" - if allowAll { - helpMsg += ", or 'all' to select all options" - } - - print(colorize("?", .boldYellow) + helpMsg) display(options: options) - print(colorize("> ", .bold), terminator: "") + print(colorize("?", .boldYellow) + helpMsg) + print(colorize("=> ", .bold), terminator: "") if let strChoices = readLine(strippingNewline: true)?.trimmed.split(separator: " ", omittingEmptySubsequences: true) { - if allowAll, strChoices.contains("all") { - return .all(options) - } else { - var selected: [String] = [] - - for strChoice in strChoices { - if let choice = Int(strChoice), - let option = options[safe: choice - 1] - { - selected.append(option) - } else { - print(colorize("\nInvalid option: \(strChoice)\n", .boldYellow)) - return select(multiple: options, allowAll: allowAll) - } + var selected: [String] = [] + + for strChoice in strChoices { + if let choice = Int(strChoice), + let option = options[safe: choice - 1] + { + selected.append(option) + } else { + print(colorize("\nInvalid option: \(strChoice)\n", .boldYellow)) + return select(multiple: options) } - - if !selected.isEmpty { return .some(selected) } } + + if !selected.isEmpty { return .some(selected) } } print(colorize("\nInvalid input, expected a number.\n", .boldYellow)) - return select(multiple: options, allowAll: allowAll) + return select(multiple: options) } public func selectBoolean() -> Bool { @@ -92,7 +84,7 @@ open class SetupGuideHelpers { "(" + colorize("Y", .boldGreen) + ")es" + "/" + "(" + colorize("N", .boldGreen) + ")o" + - colorize(" > ", .bold), + colorize("\n=> ", .bold), terminator: "" ) diff --git a/Sources/XcodeSupport/XcodeProjectSetupGuide.swift b/Sources/XcodeSupport/XcodeProjectSetupGuide.swift index 825493f54..e8d55d354 100644 --- a/Sources/XcodeSupport/XcodeProjectSetupGuide.swift +++ b/Sources/XcodeSupport/XcodeProjectSetupGuide.swift @@ -85,12 +85,30 @@ public final class XcodeProjectSetupGuide: SetupGuideHelpers, SetupGuide { project ).map { $0 }.sorted() - print(colorize("\nSelect the schemes necessary to build your chosen targets:", .bold)) - configuration.schemes = select(multiple: schemes, allowAll: false).selectedValues - - print(colorize("\nAssume Objective-C accessible declarations are in use?", .bold)) - print(colorize("?", .boldYellow) + " Declarations exposed to the Objective-C runtime explicitly with @objc, or implicitly by inheriting NSObject will be assumed to be in use. Choose 'No' if your project is pure Swift.") - configuration.retainObjcAccessible = selectBoolean() + print(colorize("\nSelect the schemes to build:", .bold)) + print("Periphery will scan all files built by your chosen schemes.") + configuration.schemes = select(multiple: schemes).selectedValues + + print(colorize("\nDoes this project contain Objective-C code?", .bold)) + let containsObjC = selectBoolean() + + if containsObjC { + print(colorize("\nPeriphery cannot scan Objective-C code and, as a result, cannot detect Swift types referenced by Objective-C code.", .bold)) + print("To avoid false positives, you have a few options:") + let retainObjcAccessibleOption = colorize("Assume all types accessible from Objective-C are in use:", .bold) + " This includes public NSObject instances (and their subclasses), as well as any types explicitly annotated with @objc. This approach will eliminate false positives but may also result in a lot of missed unused code." + let retainObjcAnnotationOption = colorize("Assume only types annotated with @objc are in use:", .bold) + " This option may lead to false positives, but they can be easily corrected by adding the necessary @objc annotations." + let objcChoice = select(single: [ + retainObjcAccessibleOption, + retainObjcAnnotationOption, + colorize("Do nothing:", .bold) + " Do not assume any Swift types are used in Objective-C code.", + ]) + + if objcChoice == retainObjcAccessibleOption { + configuration.retainObjcAccessible = true + } else if objcChoice == retainObjcAnnotationOption { + configuration.retainObjcAnnotated = true + } + } return .xcode(projectPath: project.path) } @@ -108,6 +126,10 @@ public final class XcodeProjectSetupGuide: SetupGuideHelpers, SetupGuide { options.append("--retain-objc-accessible") } + if configuration.retainObjcAnnotated { + options.append("--retain-objc-annotated") + } + return options }