diff --git a/.github/matrix.json b/.github/matrix.json new file mode 100644 index 0000000..2013505 --- /dev/null +++ b/.github/matrix.json @@ -0,0 +1,6 @@ +{ + "xcode_version": [ + "15.4", + "15.2" + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b77f13d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: CI +on: + push: + branches: main + paths: + - '**.swift' + - '.github/workflows/ci.yml' + pull_request: {} +concurrency: + group: ${{ github.head_ref }}-${{ github.workflow }} + cancel-in-progress: true +jobs: + generate-matrix: + name: Generate matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + - id: set-matrix + run: | + matrix=$(cat .github/matrix.json | jq -c .) + echo "matrix=$matrix" >> $GITHUB_OUTPUT + test: + name: Test + needs: generate-matrix + runs-on: macOS-14 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + env: + DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode_version }}.app/Contents/Developer + steps: + - uses: actions/checkout@v4 + - name: Cache SPM build directory + uses: actions/cache@v4 + env: + cache-name: swiftpm + with: + path: .build + key: ${{ runner.os }}-${{ github.job }}-${{ matrix.xcode_version }}-${{ env.cache-name }}-${{ hashFiles('**/Package.swift') }} + restore-keys: | + ${{ runner.os }}-${{ github.job }}-${{ matrix.xcode_version }}-${{ env.cache-name }}- + ${{ runner.os }}-${{ github.job }}-${{ matrix.xcode_version }}- + ${{ runner.os }}-${{ github.job }}- + ${{ runner.os }}- + # - name: Cache Homebrew modules + # uses: actions/cache@v4 + # env: + # cache-name: brew + # with: + # path: | + # ~/Library/Caches/Homebrew/downloads + # key: ${{ runner.os }}-${{ github.job }}-${{ matrix.xcode_version }}-${{ env.cache-name }}-${{ hashFiles('**/Package.swift') }} + # restore-keys: | + # ${{ runner.os }}-${{ github.job }}-${{ matrix.xcode_version }}-${{ env.cache-name }}- + # ${{ runner.os }}-${{ github.job }}-${{ matrix.xcode_version }}- + # ${{ runner.os }}-${{ github.job }}- + # ${{ runner.os }}- + - name: Disable SwiftLint Plugin + run: sed -i -e 's/.*SwiftLint.*//g' Package.swift + - name: Install LLVM + env: + HOMEBREW_NO_AUTO_UPDATE: 1 + run: brew install llvm + - name: Activate clang module + run: | + swift package resolve + sudo swift .build/checkouts/ClangSwift/utils/make-pkgconfig.swift + - name: Test + run: swift test diff --git a/Package.swift b/Package.swift index 368e1dc..56f4d04 100644 --- a/Package.swift +++ b/Package.swift @@ -54,6 +54,16 @@ let package = Package( ] ), .target(name: "Util"), + .testTarget( + name: "SBHCCoreTests", + dependencies: ["SBHCCore"], + resources: [.copy("Resources")] + ), + .testTarget( + name: "SBSCCoreTests", + dependencies: ["SBSCCore"], + resources: [.copy("Resources")] + ), ] ) diff --git a/Tests/SBHCCoreTests/Resources/Xcode.h b/Tests/SBHCCoreTests/Resources/Xcode.h new file mode 100644 index 0000000..686df30 --- /dev/null +++ b/Tests/SBHCCoreTests/Resources/Xcode.h @@ -0,0 +1,317 @@ +/* + * Xcode.h + */ + +#import +#import + + +@class XcodeApplication, XcodeDocument, XcodeWindow, XcodeFileDocument, XcodeTextDocument, XcodeSourceDocument, XcodeWorkspaceDocument, XcodeSchemeActionResult, XcodeSchemeActionIssue, XcodeBuildError, XcodeBuildWarning, XcodeAnalyzerIssue, XcodeTestFailure, XcodeScheme, XcodeRunDestination, XcodeDevice, XcodeBuildConfiguration, XcodeProject, XcodeBuildSetting, XcodeResolvedBuildSetting, XcodeTarget; + +enum XcodeSaveOptions { + XcodeSaveOptionsYes = 'yes ' /* Save the file. */, + XcodeSaveOptionsNo = 'no ' /* Do not save the file. */, + XcodeSaveOptionsAsk = 'ask ' /* Ask the user whether or not to save the file. */ +}; +typedef enum XcodeSaveOptions XcodeSaveOptions; + +// The status of a scheme action result object. +enum XcodeSchemeActionResultStatus { + XcodeSchemeActionResultStatusNotYetStarted = 'srsn' /* The action has not yet started. */, + XcodeSchemeActionResultStatusRunning = 'srsr' /* The action is in progress. */, + XcodeSchemeActionResultStatusCancelled = 'srsc' /* The action was cancelled. */, + XcodeSchemeActionResultStatusFailed = 'srsf' /* The action ran but did not complete successfully. */, + XcodeSchemeActionResultStatusErrorOccurred = 'srse' /* The action was not able to run due to an error. */, + XcodeSchemeActionResultStatusSucceeded = 'srss' /* The action succeeded. */ +}; +typedef enum XcodeSchemeActionResultStatus XcodeSchemeActionResultStatus; + +@protocol XcodeGenericMethods + +- (void) closeSaving:(XcodeSaveOptions)saving savingIn:(NSURL *)savingIn; // Close a document. +- (void) delete; // Delete an object. +- (void) moveTo:(SBObject *)to; // Move an object to a new location. +- (XcodeSchemeActionResult *) build; // Invoke the "build" scheme action. This command should be sent to a workspace document. The build will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. +- (XcodeSchemeActionResult *) clean; // Invoke the "clean" scheme action. This command should be sent to a workspace document. The clean will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. +- (void) stop; // Stop the active scheme action, if one is running. This command should be sent to a workspace document. This command does not wait for the action to stop. +- (XcodeSchemeActionResult *) runWithCommandLineArguments:(id)withCommandLineArguments withEnvironmentVariables:(id)withEnvironmentVariables; // Invoke the "run" scheme action. This command should be sent to a workspace document. The run action will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. +- (XcodeSchemeActionResult *) testWithCommandLineArguments:(id)withCommandLineArguments withEnvironmentVariables:(id)withEnvironmentVariables; // Invoke the "test" scheme action. This command should be sent to a workspace document. The test action will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. +- (void) attachToProcessIdentifier:(NSInteger)toProcessIdentifier suspended:(BOOL)suspended; // Start a new debugging session in the workspace. This command should be sent to a workspace document. This command does not wait for the action to complete. +- (XcodeSchemeActionResult *) debugScheme:(NSString *)scheme runDestinationSpecifier:(NSString *)runDestinationSpecifier skipBuilding:(BOOL)skipBuilding commandLineArguments:(id)commandLineArguments environmentVariables:(id)environmentVariables; // Start a debugging session using the "run" or "run without building" scheme action. This command should be sent to a workspace document. If no scheme is specified, the action will be performed using the workspace document's current active scheme. If no run destination is specified, the active run destination will be used. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. + +@end + + + +/* + * Standard Suite + */ + +// The application's top-level scripting object. +@interface XcodeApplication : SBApplication + +- (SBElementArray *) documents; +- (SBElementArray *) windows; + +@property (copy, readonly) NSString *name; // The name of the application. +@property (readonly) BOOL frontmost; // Is this the active application? +@property (copy, readonly) NSString *version; // The version number of the application. + +- (id) open:(id)x; // Open a document. +- (void) quitSaving:(XcodeSaveOptions)saving; // Quit the application. +- (BOOL) exists:(id)x; // Verify that an object exists. +- (XcodeWorkspaceDocument *) createTemporaryDebuggingWorkspace; // Create a new temporary debugging workspace. + +@end + +// A document. +@interface XcodeDocument : SBObject + +@property (copy, readonly) NSString *name; // Its name. +@property (readonly) BOOL modified; // Has it been modified since the last save? +@property (copy, readonly) NSURL *file; // Its location on disk, if it has one. + + +@end + +// A window. +@interface XcodeWindow : SBObject + +@property (copy, readonly) NSString *name; // The title of the window. +- (NSInteger) id; // The unique identifier of the window. +@property NSInteger index; // The index of the window, ordered front to back. +@property NSRect bounds; // The bounding rectangle of the window. +@property (readonly) BOOL closeable; // Does the window have a close button? +@property (readonly) BOOL miniaturizable; // Does the window have a minimize button? +@property BOOL miniaturized; // Is the window minimized right now? +@property (readonly) BOOL resizable; // Can the window be resized? +@property BOOL visible; // Is the window visible right now? +@property (readonly) BOOL zoomable; // Does the window have a zoom button? +@property BOOL zoomed; // Is the window zoomed right now? +@property (copy, readonly) XcodeDocument *document; // The document whose contents are displayed in the window. + + +@end + + + +/* + * Xcode Application Suite + */ + +// The Xcode application. +@interface XcodeApplication (XcodeApplicationSuite) + +- (SBElementArray *) fileDocuments; +- (SBElementArray *) sourceDocuments; +- (SBElementArray *) workspaceDocuments; + +@property (copy) XcodeWorkspaceDocument *activeWorkspaceDocument; // The active workspace document in Xcode. + +@end + + + +/* + * Xcode Document Suite + */ + +// An Xcode-compatible document. +@interface XcodeDocument (XcodeDocumentSuite) + +@property (copy) NSString *path; // The document's path. + +@end + +// A document that represents a file on disk. It also provides access to the window it appears in. +@interface XcodeFileDocument : XcodeDocument + + +@end + +// A document that represents a text file on disk. It also provides access to the window it appears in. +@interface XcodeTextDocument : XcodeFileDocument + +@property (copy) NSArray *selectedCharacterRange; // The first and last character positions in the selection. +@property (copy) NSArray *selectedParagraphRange; // The first and last paragraph positions that contain the selection. +@property (copy) NSString *text; // The text of the text file referenced. +@property BOOL notifiesWhenClosing; // Should Xcode notify other apps when this document is closed? + + +@end + +// A document that represents a source file on disk. It also provides access to the window it appears in. +@interface XcodeSourceDocument : XcodeTextDocument + + +@end + +// A document that represents a workspace on disk. Workspaces are the top-level container for almost all objects and commands in Xcode. +@interface XcodeWorkspaceDocument : XcodeDocument + +- (SBElementArray *) projects; +- (SBElementArray *) schemes; +- (SBElementArray *) runDestinations; + +@property BOOL loaded; // Whether the workspace document has finsished loading after being opened. Messages sent to a workspace document before it has loaded will result in errors. +@property (copy) XcodeScheme *activeScheme; // The workspace's scheme that will be used for scheme actions. +@property (copy) XcodeRunDestination *activeRunDestination; // The workspace's run destination that will be used for scheme actions. +@property (copy) XcodeSchemeActionResult *lastSchemeActionResult; // The scheme action result for the last scheme action command issued to the workspace document. +@property (copy, readonly) NSURL *file; // The workspace document's location on disk, if it has one. + + +@end + + + +/* + * Xcode Scheme Suite + */ + +// An object describing the result of performing a scheme action command. +@interface XcodeSchemeActionResult : SBObject + +- (SBElementArray *) buildErrors; +- (SBElementArray *) buildWarnings; +- (SBElementArray *) analyzerIssues; +- (SBElementArray *) testFailures; + +- (NSString *) id; // The unique identifier for the scheme. +@property (readonly) BOOL completed; // Whether this scheme action has completed (sucessfully or otherwise) or not. +@property XcodeSchemeActionResultStatus status; // Indicates the status of the scheme action. +@property (copy) NSString *errorMessage; // If the result's status is "error occurred", this will be the error message; otherwise, this will be "missing value". +@property (copy) NSString *buildLog; // If this scheme action performed a build, this will be the text of the build log. + + +@end + +// An issue (like an error or warning) generated by a scheme action. +@interface XcodeSchemeActionIssue : SBObject + +@property (copy) NSString *message; // The text of the issue. +@property (copy) NSString *filePath; // The file path where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. +@property NSInteger startingLineNumber; // The starting line number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. +@property NSInteger endingLineNumber; // The ending line number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. +@property NSInteger startingColumnNumber; // The starting column number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. +@property NSInteger endingColumnNumber; // The ending column number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + + +@end + +// An error generated by a build. +@interface XcodeBuildError : XcodeSchemeActionIssue + + +@end + +// A warning generated by a build. +@interface XcodeBuildWarning : XcodeSchemeActionIssue + + +@end + +// A warning generated by the static analyzer. +@interface XcodeAnalyzerIssue : XcodeSchemeActionIssue + + +@end + +// A failure from a test. +@interface XcodeTestFailure : XcodeSchemeActionIssue + + +@end + +// A set of parameters for building, testing, launching or distributing the products of a workspace. +@interface XcodeScheme : SBObject + +@property (copy, readonly) NSString *name; // The name of the scheme. +- (NSString *) id; // The unique identifier for the scheme. + + +@end + +// An object which specifies parameters such as the device and architecture for which to perform a scheme action. +@interface XcodeRunDestination : SBObject + +@property (copy, readonly) NSString *name; // The name of the run destination, as displayed in Xcode's interface. +@property (copy, readonly) NSString *architecture; // The architecture for which this run destination results in execution. +@property (copy, readonly) NSString *platform; // The identifier of the platform which this run destination targets, such as "macosx", "iphoneos", "iphonesimulator", etc . +@property (copy, readonly) XcodeDevice *device; // The physical or virtual device which this run destination targets. +@property (copy, readonly) XcodeDevice *companionDevice; // If the run destination's device has a companion (e.g. a paired watch for a phone) which it will use, this is that device. + + +@end + +// A device which can be used as the target for a scheme action, as part of a run destination. +@interface XcodeDevice : SBObject + +@property (copy, readonly) NSString *name; // The name of the device. +@property (copy, readonly) NSString *deviceIdentifier; // A stable identifier for the device, as shown in Xcode's "Devices" window. +@property (copy, readonly) NSString *operatingSystemVersion; // The version of the operating system installed on the device which this run destination targets. +@property (copy, readonly) NSString *deviceModel; // The model of device (e.g. "iPad Air") which this run destination targets. +@property (readonly) BOOL generic; // Whether this run destination is generic instead of representing a specific device. Most destinations are not generic, but a generic destination (such as "Any iOS Device") will be available for some platforms if no physical devices are connected. + + +@end + + + +/* + * Xcode Project Suite + */ + +// A set of build settings for a target or project. Each target in a project has the same named build configurations as the project. +@interface XcodeBuildConfiguration : SBObject + +- (SBElementArray *) buildSettings; +- (SBElementArray *) resolvedBuildSettings; + +- (NSString *) id; // The unique identifier for the build configuration. +@property (copy, readonly) NSString *name; // The name of the build configuration. + + +@end + +// An Xcode project. Projects represent project files on disk and are always open in the context of a workspace document. +@interface XcodeProject : SBObject + +- (SBElementArray *) buildConfigurations; +- (SBElementArray *) targets; + +@property (copy, readonly) NSString *name; // The name of the project +- (NSString *) id; // The unique identifier for the project. + + +@end + +// A setting that controls how products are built. +@interface XcodeBuildSetting : SBObject + +@property (copy) NSString *name; // The unlocalized build setting name (e.g. DSTROOT). +@property (copy) NSString *value; // A string value for the build setting. + + +@end + +// An object that represents a resolved value for a build setting. +@interface XcodeResolvedBuildSetting : SBObject + +@property (copy) NSString *name; // The unlocalized build setting name (e.g. DSTROOT). +@property (copy) NSString *value; // A string value for the build setting. + + +@end + +// A target is a blueprint for building a product. Targets inherit build settings from their project if not overridden in the target. +@interface XcodeTarget : SBObject + +- (SBElementArray *) buildConfigurations; + +@property (copy) NSString *name; // The name of this target. +- (NSString *) id; // The unique identifier for the target. +@property (copy, readonly) XcodeProject *project; // The project that contains this target + + +@end + diff --git a/Tests/SBHCCoreTests/Resources/Xcode.swift b/Tests/SBHCCoreTests/Resources/Xcode.swift new file mode 100644 index 0000000..6cb8651 --- /dev/null +++ b/Tests/SBHCCoreTests/Resources/Xcode.swift @@ -0,0 +1,283 @@ +import AppKit +import ScriptingBridge + +@objc public protocol SBObjectProtocol: NSObjectProtocol { + func get() -> Any? +} + +@objc public protocol SBApplicationProtocol: SBObjectProtocol { + func activate() + + var delegate: SBApplicationDelegate? { get set } + var isRunning: Bool { get } +} + +// MARK: XcodeSaveOptions +@objc public enum XcodeSaveOptions : AEKeyword { + case yes = 0x79657320 /* 'yes ' */ + case no = 0x6e6f2020 /* 'no ' */ + case ask = 0x61736b20 /* 'ask ' */ +} + +// MARK: XcodeSchemeActionResultStatus +@objc public enum XcodeSchemeActionResultStatus : AEKeyword { + case notYetStarted = 0x7372736e /* 'srsn' */ + case running = 0x73727372 /* 'srsr' */ + case cancelled = 0x73727363 /* 'srsc' */ + case failed = 0x73727366 /* 'srsf' */ + case errorOccurred = 0x73727365 /* 'srse' */ + case succeeded = 0x73727373 /* 'srss' */ +} + + +// MARK: XcodeGenericMethods +@objc public protocol XcodeGenericMethods { + @objc optional func closeSaving(_ saving: XcodeSaveOptions, savingIn: URL) // Close a document. + @objc optional func delete() // Delete an object. + @objc optional func moveTo(_ to: SBObject) // Move an object to a new location. + @objc optional func build() -> XcodeSchemeActionResult // Invoke the "build" scheme action. This command should be sent to a workspace document. The build will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. + @objc optional func clean() -> XcodeSchemeActionResult // Invoke the "clean" scheme action. This command should be sent to a workspace document. The clean will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. + @objc optional func stop() // Stop the active scheme action, if one is running. This command should be sent to a workspace document. This command does not wait for the action to stop. + @objc optional func runWithCommandLineArguments(_ withCommandLineArguments: Any, withEnvironmentVariables: Any) -> XcodeSchemeActionResult // Invoke the "run" scheme action. This command should be sent to a workspace document. The run action will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. + @objc optional func testWithCommandLineArguments(_ withCommandLineArguments: Any, withEnvironmentVariables: Any) -> XcodeSchemeActionResult // Invoke the "test" scheme action. This command should be sent to a workspace document. The test action will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. + @objc optional func attachToProcessIdentifier(_ toProcessIdentifier: Int, suspended: Bool) // Start a new debugging session in the workspace. This command should be sent to a workspace document. This command does not wait for the action to complete. + @objc optional func debugScheme(_ scheme: String, runDestinationSpecifier: String, skipBuilding: Bool, commandLineArguments: Any, environmentVariables: Any) -> XcodeSchemeActionResult // Start a debugging session using the "run" or "run without building" scheme action. This command should be sent to a workspace document. If no scheme is specified, the action will be performed using the workspace document's current active scheme. If no run destination is specified, the active run destination will be used. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result. +} + + +// MARK: XcodeApplication +@objc public protocol XcodeApplication: SBApplicationProtocol { + @objc optional func documents() -> SBElementArray + @objc optional func windows() -> SBElementArray + + @objc optional var name: String { get } // The name of the application. + @objc optional var frontmost: Bool { get } // Is this the active application? + @objc optional var version: String { get } // The version number of the application. + + @objc optional func open(_ x: Any) -> Any // Open a document. + @objc optional func quitSaving(_ saving: XcodeSaveOptions) // Quit the application. + @objc optional func exists(_ x: Any) -> Bool // Verify that an object exists. + @objc optional func createTemporaryDebuggingWorkspace() -> XcodeWorkspaceDocument // Create a new temporary debugging workspace. + @objc optional func fileDocuments() -> SBElementArray + @objc optional func sourceDocuments() -> SBElementArray + @objc optional func workspaceDocuments() -> SBElementArray + + @objc optional var activeWorkspaceDocument: XcodeWorkspaceDocument { get } // The active workspace document in Xcode. + + @objc optional func setActiveWorkspaceDocument(_ activeWorkspaceDocument: XcodeWorkspaceDocument) // The active workspace document in Xcode. +} +extension SBApplication: XcodeApplication {} + +// MARK: XcodeDocument +@objc public protocol XcodeDocument: SBObjectProtocol, XcodeGenericMethods { + @objc optional var name: String { get } // Its name. + @objc optional var modified: Bool { get } // Has it been modified since the last save? + @objc optional var file: URL { get } // Its location on disk, if it has one. + @objc optional var path: String { get } // The document's path. + + @objc optional func setPath(_ path: String) // The document's path. +} +extension SBObject: XcodeDocument {} + +// MARK: XcodeWindow +@objc public protocol XcodeWindow: SBObjectProtocol, XcodeGenericMethods { + @objc optional var name: String { get } // The title of the window. + + @objc optional func id() -> Int // The unique identifier of the window. + + @objc optional var index: Int { get } // The index of the window, ordered front to back. + @objc optional var bounds: NSRect { get } // The bounding rectangle of the window. + @objc optional var closeable: Bool { get } // Does the window have a close button? + @objc optional var miniaturizable: Bool { get } // Does the window have a minimize button? + @objc optional var miniaturized: Bool { get } // Is the window minimized right now? + @objc optional var resizable: Bool { get } // Can the window be resized? + @objc optional var visible: Bool { get } // Is the window visible right now? + @objc optional var zoomable: Bool { get } // Does the window have a zoom button? + @objc optional var zoomed: Bool { get } // Is the window zoomed right now? + @objc optional var document: XcodeDocument { get } // The document whose contents are displayed in the window. + + @objc optional func setIndex(_ index: Int) // The index of the window, ordered front to back. + @objc optional func setBounds(_ bounds: NSRect) // The bounding rectangle of the window. + @objc optional func setMiniaturized(_ miniaturized: Bool) // Is the window minimized right now? + @objc optional func setVisible(_ visible: Bool) // Is the window visible right now? + @objc optional func setZoomed(_ zoomed: Bool) // Is the window zoomed right now? +} +extension SBObject: XcodeWindow {} + +// MARK: XcodeFileDocument +@objc public protocol XcodeFileDocument: XcodeDocument {} +extension SBObject: XcodeFileDocument {} + +// MARK: XcodeTextDocument +@objc public protocol XcodeTextDocument: XcodeFileDocument { + @objc optional var selectedCharacterRange: [NSNumber] { get } // The first and last character positions in the selection. + @objc optional var selectedParagraphRange: [NSNumber] { get } // The first and last paragraph positions that contain the selection. + @objc optional var text: String { get } // The text of the text file referenced. + @objc optional var notifiesWhenClosing: Bool { get } // Should Xcode notify other apps when this document is closed? + + @objc optional func setSelectedCharacterRange(_ selectedCharacterRange: [NSNumber]) // The first and last character positions in the selection. + @objc optional func setSelectedParagraphRange(_ selectedParagraphRange: [NSNumber]) // The first and last paragraph positions that contain the selection. + @objc optional func setText(_ text: String) // The text of the text file referenced. + @objc optional func setNotifiesWhenClosing(_ notifiesWhenClosing: Bool) // Should Xcode notify other apps when this document is closed? +} +extension SBObject: XcodeTextDocument {} + +// MARK: XcodeSourceDocument +@objc public protocol XcodeSourceDocument: XcodeTextDocument {} +extension SBObject: XcodeSourceDocument {} + +// MARK: XcodeWorkspaceDocument +@objc public protocol XcodeWorkspaceDocument: XcodeDocument { + @objc optional func projects() -> SBElementArray + @objc optional func schemes() -> SBElementArray + @objc optional func runDestinations() -> SBElementArray + + @objc optional var loaded: Bool { get } // Whether the workspace document has finsished loading after being opened. Messages sent to a workspace document before it has loaded will result in errors. + @objc optional var activeScheme: XcodeScheme { get } // The workspace's scheme that will be used for scheme actions. + @objc optional var activeRunDestination: XcodeRunDestination { get } // The workspace's run destination that will be used for scheme actions. + @objc optional var lastSchemeActionResult: XcodeSchemeActionResult { get } // The scheme action result for the last scheme action command issued to the workspace document. + @objc optional var file: URL { get } // The workspace document's location on disk, if it has one. + + @objc optional func setLoaded(_ loaded: Bool) // Whether the workspace document has finsished loading after being opened. Messages sent to a workspace document before it has loaded will result in errors. + @objc optional func setActiveScheme(_ activeScheme: XcodeScheme) // The workspace's scheme that will be used for scheme actions. + @objc optional func setActiveRunDestination(_ activeRunDestination: XcodeRunDestination) // The workspace's run destination that will be used for scheme actions. + @objc optional func setLastSchemeActionResult(_ lastSchemeActionResult: XcodeSchemeActionResult) // The scheme action result for the last scheme action command issued to the workspace document. +} +extension SBObject: XcodeWorkspaceDocument {} + +// MARK: XcodeSchemeActionResult +@objc public protocol XcodeSchemeActionResult: SBObjectProtocol, XcodeGenericMethods { + @objc optional func buildErrors() -> SBElementArray + @objc optional func buildWarnings() -> SBElementArray + @objc optional func analyzerIssues() -> SBElementArray + @objc optional func testFailures() -> SBElementArray + @objc optional func id() -> String // The unique identifier for the scheme. + + @objc optional var completed: Bool { get } // Whether this scheme action has completed (sucessfully or otherwise) or not. + @objc optional var status: XcodeSchemeActionResultStatus { get } // Indicates the status of the scheme action. + @objc optional var errorMessage: String { get } // If the result's status is "error occurred", this will be the error message; otherwise, this will be "missing value". + @objc optional var buildLog: String { get } // If this scheme action performed a build, this will be the text of the build log. + + @objc optional func setStatus(_ status: XcodeSchemeActionResultStatus) // Indicates the status of the scheme action. + @objc optional func setErrorMessage(_ errorMessage: String) // If the result's status is "error occurred", this will be the error message; otherwise, this will be "missing value". + @objc optional func setBuildLog(_ buildLog: String) // If this scheme action performed a build, this will be the text of the build log. +} +extension SBObject: XcodeSchemeActionResult {} + +// MARK: XcodeSchemeActionIssue +@objc public protocol XcodeSchemeActionIssue: SBObjectProtocol, XcodeGenericMethods { + @objc optional var message: String { get } // The text of the issue. + @objc optional var filePath: String { get } // The file path where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + @objc optional var startingLineNumber: Int { get } // The starting line number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + @objc optional var endingLineNumber: Int { get } // The ending line number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + @objc optional var startingColumnNumber: Int { get } // The starting column number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + @objc optional var endingColumnNumber: Int { get } // The ending column number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + + @objc optional func setMessage(_ message: String) // The text of the issue. + @objc optional func setFilePath(_ filePath: String) // The file path where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + @objc optional func setStartingLineNumber(_ startingLineNumber: Int) // The starting line number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + @objc optional func setEndingLineNumber(_ endingLineNumber: Int) // The ending line number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + @objc optional func setStartingColumnNumber(_ startingColumnNumber: Int) // The starting column number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. + @objc optional func setEndingColumnNumber(_ endingColumnNumber: Int) // The ending column number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file. +} +extension SBObject: XcodeSchemeActionIssue {} + +// MARK: XcodeBuildError +@objc public protocol XcodeBuildError: XcodeSchemeActionIssue {} +extension SBObject: XcodeBuildError {} + +// MARK: XcodeBuildWarning +@objc public protocol XcodeBuildWarning: XcodeSchemeActionIssue {} +extension SBObject: XcodeBuildWarning {} + +// MARK: XcodeAnalyzerIssue +@objc public protocol XcodeAnalyzerIssue: XcodeSchemeActionIssue {} +extension SBObject: XcodeAnalyzerIssue {} + +// MARK: XcodeTestFailure +@objc public protocol XcodeTestFailure: XcodeSchemeActionIssue {} +extension SBObject: XcodeTestFailure {} + +// MARK: XcodeScheme +@objc public protocol XcodeScheme: SBObjectProtocol, XcodeGenericMethods { + @objc optional var name: String { get } // The name of the scheme. + + @objc optional func id() -> String // The unique identifier for the scheme. +} +extension SBObject: XcodeScheme {} + +// MARK: XcodeRunDestination +@objc public protocol XcodeRunDestination: SBObjectProtocol, XcodeGenericMethods { + @objc optional var name: String { get } // The name of the run destination, as displayed in Xcode's interface. + @objc optional var architecture: String { get } // The architecture for which this run destination results in execution. + @objc optional var platform: String { get } // The identifier of the platform which this run destination targets, such as "macosx", "iphoneos", "iphonesimulator", etc . + @objc optional var device: XcodeDevice { get } // The physical or virtual device which this run destination targets. + @objc optional var companionDevice: XcodeDevice { get } // If the run destination's device has a companion (e.g. a paired watch for a phone) which it will use, this is that device. +} +extension SBObject: XcodeRunDestination {} + +// MARK: XcodeDevice +@objc public protocol XcodeDevice: SBObjectProtocol, XcodeGenericMethods { + @objc optional var name: String { get } // The name of the device. + @objc optional var deviceIdentifier: String { get } // A stable identifier for the device, as shown in Xcode's "Devices" window. + @objc optional var operatingSystemVersion: String { get } // The version of the operating system installed on the device which this run destination targets. + @objc optional var deviceModel: String { get } // The model of device (e.g. "iPad Air") which this run destination targets. + @objc optional var generic: Bool { get } // Whether this run destination is generic instead of representing a specific device. Most destinations are not generic, but a generic destination (such as "Any iOS Device") will be available for some platforms if no physical devices are connected. +} +extension SBObject: XcodeDevice {} + +// MARK: XcodeBuildConfiguration +@objc public protocol XcodeBuildConfiguration: SBObjectProtocol, XcodeGenericMethods { + @objc optional func buildSettings() -> SBElementArray + @objc optional func resolvedBuildSettings() -> SBElementArray + @objc optional func id() -> String // The unique identifier for the build configuration. + + @objc optional var name: String { get } // The name of the build configuration. +} +extension SBObject: XcodeBuildConfiguration {} + +// MARK: XcodeProject +@objc public protocol XcodeProject: SBObjectProtocol, XcodeGenericMethods { + @objc optional func buildConfigurations() -> SBElementArray + @objc optional func targets() -> SBElementArray + + @objc optional var name: String { get } // The name of the project + + @objc optional func id() -> String // The unique identifier for the project. +} +extension SBObject: XcodeProject {} + +// MARK: XcodeBuildSetting +@objc public protocol XcodeBuildSetting: SBObjectProtocol, XcodeGenericMethods { + @objc optional var name: String { get } // The unlocalized build setting name (e.g. DSTROOT). + @objc optional var value: String { get } // A string value for the build setting. + + @objc optional func setName(_ name: String) // The unlocalized build setting name (e.g. DSTROOT). + @objc optional func setValue(_ value: String) // A string value for the build setting. +} +extension SBObject: XcodeBuildSetting {} + +// MARK: XcodeResolvedBuildSetting +@objc public protocol XcodeResolvedBuildSetting: SBObjectProtocol, XcodeGenericMethods { + @objc optional var name: String { get } // The unlocalized build setting name (e.g. DSTROOT). + @objc optional var value: String { get } // A string value for the build setting. + + @objc optional func setName(_ name: String) // The unlocalized build setting name (e.g. DSTROOT). + @objc optional func setValue(_ value: String) // A string value for the build setting. +} +extension SBObject: XcodeResolvedBuildSetting {} + +// MARK: XcodeTarget +@objc public protocol XcodeTarget: SBObjectProtocol, XcodeGenericMethods { + @objc optional func buildConfigurations() -> SBElementArray + + @objc optional var name: String { get } // The name of this target. + + @objc optional func id() -> String // The unique identifier for the target. + + @objc optional var project: XcodeProject { get } // The project that contains this target + + @objc optional func setName(_ name: String) // The name of this target. +} +extension SBObject: XcodeTarget {} + diff --git a/Tests/SBHCCoreTests/SBHeaderProcessorTests.swift b/Tests/SBHCCoreTests/SBHeaderProcessorTests.swift new file mode 100644 index 0000000..3072dc6 --- /dev/null +++ b/Tests/SBHCCoreTests/SBHeaderProcessorTests.swift @@ -0,0 +1,16 @@ +import XCTest +@testable import SBHCCore + +final class SBHeaderProcessorTests: XCTestCase { + private var processor: SBHeaderProcessor! + + func testEmitSwift() throws { + let headerFileURL = try XCTUnwrap(Bundle.module.url(forResource: "Xcode", withExtension: "h", subdirectory: "Resources")) + let swiftFileURL = try XCTUnwrap(Bundle.module.url(forResource: "Xcode", withExtension: "swift", subdirectory: "Resources")) + processor = try .init(headerFileUrl: headerFileURL) + try processor.emitSwift() + + let expected = String(decoding: try Data(contentsOf: swiftFileURL), as: UTF8.self) + XCTAssertEqual(processor.output, expected) + } +} diff --git a/Tests/SBSCCoreTests/Resources/Xcode.sdef b/Tests/SBSCCoreTests/Resources/Xcode.sdef new file mode 100644 index 0000000..a5de139 --- /dev/null +++ b/Tests/SBSCCoreTests/Resources/Xcode.sdef @@ -0,0 +1,559 @@ + + + + + + + + + + + + + + + + + + + + + + +

KNOWN ISSUE: The open command in Xcode sometimes fails to return the opened document. It is recommended to ignore the result of the open command and instead find the opened document in the application's documents.

+ + ]]> + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

EXAMPLE SCRIPT: Open a project and wait for its workspace document to load.

+

open "/Users/myuser/Desktop/MyProject.xcodeproj"

+

set workspaceDocument to workspace document "MyProject.xcodeproj"

+

-- Wait for the workspace document to load with a 60 second timeout

+

repeat 120 times

+

if loaded of workspaceDocument is true then

+

exit repeat

+

end if +

delay 0.5

+

end repeat

+

if loaded of workspaceDocument is false then

+

error "Xcode workspace did not finish loading within timeout."

+

end if

+ + ]]> + +
+
+
+ + + + + + + + +

EXAMPLE SCRIPT: Perform a build and wait for completion.

+

set actionResult to build workspace document 1

+

repeat

+

if completed of actionResult is true then

+

exit repeat

+

end if

+

delay 0.5

+

end repeat

+ + ]]> + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Tests/SBSCCoreTests/Resources/XcodeScripting.swift b/Tests/SBSCCoreTests/Resources/XcodeScripting.swift new file mode 100644 index 0000000..73ec2c8 --- /dev/null +++ b/Tests/SBSCCoreTests/Resources/XcodeScripting.swift @@ -0,0 +1,23 @@ +public enum XcodeScripting: String { + case analyzerIssue = "analyzer issue" + case application = "application" + case buildConfiguration = "build configuration" + case buildError = "build error" + case buildSetting = "build setting" + case buildWarning = "build warning" + case device = "device" + case document = "document" + case fileDocument = "file document" + case project = "project" + case resolvedBuildSetting = "resolved build setting" + case runDestination = "run destination" + case schemeActionIssue = "scheme action issue" + case schemeActionResult = "scheme action result" + case scheme = "scheme" + case sourceDocument = "source document" + case target = "target" + case testFailure = "test failure" + case textDocument = "text document" + case window = "window" + case workspaceDocument = "workspace document" +} \ No newline at end of file diff --git a/Tests/SBSCCoreTests/SBScriptingProcessorTests.swift b/Tests/SBSCCoreTests/SBScriptingProcessorTests.swift new file mode 100644 index 0000000..b03f6f5 --- /dev/null +++ b/Tests/SBSCCoreTests/SBScriptingProcessorTests.swift @@ -0,0 +1,16 @@ +import XCTest +@testable import SBSCCore + +final class SBScriptingProcessorTests: XCTestCase { + private var processor: SBScriptingProcessor! + + func testProcess() throws { + let sdefFileURL = try XCTUnwrap(Bundle.module.url(forResource: "Xcode", withExtension: "sdef", subdirectory: "Resources")) + let swiftFileURL = try XCTUnwrap(Bundle.module.url(forResource: "XcodeScripting", withExtension: "swift", subdirectory: "Resources")) + processor = .init(sdefFileUrl: sdefFileURL) + try processor.process() + + let expected = String(decoding: try Data(contentsOf: swiftFileURL), as: UTF8.self) + XCTAssertEqual(processor.output, expected) + } +}