AppleScript syntax to automate Xcode 4.1 to Clean, Build then Run - xcode

I appreciate that there are already questions on this topic, but having read the ones I can find (particularly this one: Tell AppleScript To Build XCode Project), they all seem to be a couple of years old and the answers do not seem to apply to the current version of Xcode.
Similarly to the linked question, I am attempting to automate opening an Xcode project, building it and then running the app in the iPhone Simulator (v4.3). The project in question is the Selenium project's iPhoneDriver (see here for details: http://code.google.com/p/selenium/wiki/IPhoneDriver)
Based on the answer in the other question, I have written the following script:
tell application "Xcode"
open "/Users/<username>/Documents/Code/Selenium/iphone/iWebDriver.xcodeproj"
tell project "iWebDriver"
clean
build
try
debug
end try
end tell
end tell
Unfortunately, when I run this I get the following:
tell application "Xcode"
open "/Users/ben.adderson/Documents/Code/Selenium/iphone/iWebDriver.xcodeproj"
--> project document "iWebDriver.xcodeproj"
clean project "iWebDriver"
--> missing value
build project "iWebDriver"
--> missing value
debug project "iWebDriver"
--> error number -1708
end tell
If I run just the open command, Xcode opens the project without issue. But as soon as I include the rest of the script the Xcode icon in the dock bounces, but that's all I get, apart from the above from the AppleScript Editor.
Can anybody advise what I'm doing wrong? This is the first time I've used AppleScript or Xcode, so I'm struggling to diagnose the problem.
I've tried looking at the Xcode AppleScript Dictionary, but without worked examples I can't quite determine the syntax I need.
Thanks in advance for any help!

Using a mix of AppleScript, and command line tool (xcodebuild), I came up with this:
-- This script will clean, build then run the project at the path below.
-- You will need to slightly modify this script if you have more than one xcodeproject at this path
set pathOfXcodeProject to "/Users/<username>/Documents/Code/Selenium/iphone/iWebDriver.xcodeproj"
-- End of configuration
do shell script "cd " & pathOfXcodeProject & " && /usr/bin/xcodebuild clean build "
tell application "Xcode"
open pathOfXcodeProject
activate
end tell
tell application "System Events"
get system attribute "sysv"
if result is greater than or equal to 4144 then -- Mac OS X 10.3.0
if UI elements enabled then
tell application process "Xcode"
click menu item "Run" of menu 1 of menu bar item "Product" of menu bar 1
end tell
else
beep
display dialog "GUI Scripting is not enabled" & return & return & "Open System Preferences and check Enable Access for Assistive Devices in the Universal Access preference pane, then run this script again." with icon stop
if button returned of result is "OK" then
tell application "System Preferences"
activate
set current pane to pane "com.apple.preference.universalaccess"
end tell
end if
end if
else
beep
display dialog "This computer cannot run this script" & return & return & "The script uses GUI Scripting technology, which requires an upgrade to Mac OS X 10.3 Panther or newer." with icon caution buttons {"Quit"} default button "Quit"
end if
end tell
Let me know if this works for you.
I tested it against Xcode 4.2.1 on Lion, with an iPhone project.

If you're ok with Xcode coming to the foreground, I would use this instead:
tell application "Xcode"
activate
open "/Users/<username>/Documents/Code/Selenium/iphone/iWebDriver.xcodeproj"
end tell
tell application "System Events"
key code 15 using {command down}
end tell

Although I vote and cheer the above AppleScript, which is really good (and also works…) being all professional, taking care of old versions, and extreme cases etc. The script suggested by the question should have worked in the first place.
The reason it doesn't work is broken Xcode scriptability. Not only "clean" doesn't work. Most everything I tried to do with the internal object model of Xcode doesn't work.
AppleScript architecture (OSA) requires an application to expose a "human understandable" object graph that AppleScripts can reference. In Xcode it may look like:
tell target "my library" of project "my project" of workspace "my workspace" to clean.
or
tell application "Xcode" to close all projects whose name contains "Lib"
User-Interface scripting is the last resort of the scripter when application scriptability is poor. Because the UI hierarchy is standard in nature, and its scriptability is free from Cocoa, it most always work.

Related

AppleScript: loop over application windows; bring each to front & trigger action in other app?

Short Version
I want to write some AppleScript which iterates over an application's (non-minimised) windows and for each of them, brings it to the front and triggers something in another app, BTT (which will move the window). The aim is to move all windows of the app to the same place. The moving part is handled (by BTT), I just need help with making this happen to all the app's windows.
Long Version
(This question involves BetterTouchTool but it's not really about that - I think - and I suspect you don't need to know anything about BTT to answer...)
I'm trying to write an AppleScript script which (when I boil it down to the simplest version possible) activates some app, and loops over all its windows, triggering a BetterTouchTool "named trigger" for each window. The job of the named trigger is to move the current window to some location, but the details of that don't matter.
This feels to me like it should be straightforward, but I don't seem to be able to find the right approach. I've tried cargo-culting it but I'm not getting anywhere (other than a suspicion I'll need to involve "System Events")... I'm an experienced programmer but I've never gone deep into AppleScript and find its way of doing things rather weird/confusing.
What I've got working so far is a version which doesn't loop over the windows, so it just hits the "first" one (in some sense of "first", which seems to be "most recently active for this app").
For example, the following works. (I've hard coded it here to VSCode, though in the full thing I'm trying to write, it iterates over a list of app names and the triggers to run for them.)
on run
if application "Visual Studio Code" is running then
tell application "Visual Studio Code" to activate
tell application "BetterTouchTool"
trigger_named "Move window: monitor 4k: core"
end tell
end if
end run
So that activates VSCode, then tells BTT to run a named trigger to move the current window to some position on the screen. The effect is that the most recently used VSCode window gets moved.
OK, not a bad start, but I want to do this for all VSCode windows (or ideally, all non-minimised ones on currently visible desktops).
BTT's AppleScript interface doesn't seem to support targetting particular windows - it just works on whatever app/window is active, so I need to somehow activate/bring to front (?) the window then call the BTT trigger.
I guess I need something like this (ignoring, for now, the problem of omitting minimised windows):
on run
if application "Visual Studio Code" is running then
tell application "Visual Studio Code" to activate
repeat with app_window in windows
bring_window_to_front()
tell application "BetterTouchTool"
trigger_named "Move window: monitor 4k: core"
end tell
end repeat
end if
end run
... where I don't know how to do the bring_window_to_front() part!
(Also not considered here: other desktops; but I can live without that I think.)
Could someone point me in the right direction? All help appreciated!
Summary
I want to:
Given an application identified by a given name...
get all of its windows and loop over them, and for each:
activate/bring that window to the front (provided it's not minimised) and...
tell BetterTouchTool to run the trigger with the given name.
Context
MacOS Mojave 10.14.5
BetterTouchTouch 3.072 (not that I think this makes any difference)
Any scriptable app — I'm assuming "Visual Studio Code" is scriptable (has a scripting dictionary) from what you wrote — should respond to this code:
on run
if application "Visual Studio Code" is running then
tell application "Visual Studio Code" to activate
set wList to every window whose visible is true
repeat with app_window in wList
set index of app_window to 1
delay .5
tell application "BetterTouchTool"
trigger_named "Move window: monitor 4k: core"
end tell
end repeat
end if
end run
The ... whose visible is true part of line 4 excludes hidden or minimized windows.
The set index of ... command in line 6 cycles each window to the front.
index and visible are part of the standard Window properties that all AppleScript developers are supposed to implement, so unless the app designers are complete amateurs they should 'just work.' Look at the scripting dictionary in Script Editor to be sure. Sometimes AppleScript designers add special commands that are useful, or change the names of standard properties (possibly just to be annoying).
I'm not sure if the delay .5 in line 7 is needed. I threw it in to be overly-sure the window was frontmost before the BTT call.
Don't use GUI-scripting or System Events unless the app is non-scriptable. GUI-scripting is extremely useful when it is needed, but if the app has a scripting dictionary using that will be far more efficient and reliable. GUI-scripting is clunky, slow, and subject to unpredictable disruptions. But if you do need to use it, the code is similar.
tell application "System Events"
tell process "Visual Studio Code"
set frontmost to true
set wlist to every window
repeat with aWindow in wlist
tell aWindow
perform action "AXRaise"
delay .5
tell application "BetterTouchTool"
trigger_named "Move window: monitor 4k: core"
end tell
end tell
end repeat
end tell
end tell
Yes, you need System Events. It can give you all running and visible processes (i.e. = applications) and for each, list of windows.
Even more, you can then directly update position of each window. Here is sample script which gets all open windows of all applications and move the position of the "Library" Window of "Script Editor" (I open it for my tests):
tell application "System Events"
set theProcesses to application processes whose visible is true
repeat with aProcess in theProcesses -- loop thru all visible processes
set allWindows to every window of aProcess
repeat with myWindow in allWindows -- for each process, look for all windows
set Pname to name of aProcess
set Wname to name of myWindow
if Pname = "Script Editor" and Wname = "Library" then
set position of myWindow to {400, 900}
end if
end repeat -- window loop
end repeat -- process loop
end tell

`AXFocusedUIElement` <- does not get focused element (folder, file), it points to `Finder` menu

trying to reproduce right-click context menu on my Mac.
I found such an article:
https://beebom.com/how-right-click-using-keyboard-mac/
I did accordingly but when I click my keyboard shortcut I get Finder menu not a currently selected file/folder menu.
This is an apple script used,
on run {input, parameters}
tell application "System Events" to set frontApp to name of first process whose frontmost is true
tell application "System Events"
tell application process frontApp
set _selection to value of attribute "AXFocusedUIElement"
tell _selection to perform action "AXShowMenu"
end tell
end tell
return input
end run
Spent hours trying to get this basic and obvious to every Windows user functionality to work, lost of time and very frustrating!
I think code is correct, maybe there is something specific on my computer that stops it from working as expected?
Please help :-)
Not sure i understood what is actually not working for you, but i had problem to reproduce the same script, once i wrote it in Automator.app i tried to press "play" button to see if the script was working and it was telling me something like "syntax error, can't get attribute AXFocusedUIElement of application process Automator" (from memory, not sure it's exactly what was written)
and struggled for a while as well untill i realised there was a pop window that i didn't see, saying me that "automator wants permission to control this computer using accessibility features (this thing in the system preference, security and privacy, privacy, accessibility), so i opened, there was a list of apps allowed to control my computer, i ticked Automator.app and after that it worked
Then every app that i was trying to do a right click was showing me the same pop up and i had to do the same for each one (safari, finder etc...)
And then it worked
Hope might help you!
Encountered same issue.
Allow 'Finder' to control computer at System Preferences>Security & Privacy>Privacy>Accessibility.
Worked for me

xcode: application runs properly from Debug folder, but not if copied to any other folder

I have a weirdness with my applescript application created in xcode.
When building for run, it runs properly. When copying the app from the Debug folder to another place, it does not run properly.
The application starts Photoshop, and then uses System Events UI scripting to open the Open File dialog. When not running from the Debug folder, it gets to start Photoshop, also activates System events, it seems to find the Photoshop process, but does not start with the UI scripting.
Being a noob, it baffles me, and I am stuck.
If further information is needed, please bear with me, and let me know what is needed.
Additional information: Another very similar application, which in particular uses exactly the same code, does work without issues.
Also, as asked in a comment, the application is given control to the machine in the Accessibility settings.
Edit: code as requested in comment
set myps to "com.adobe.Photoshop"
set applName to (get name of application id myps)
delay 1
tell application id myps
-- activate
using terms from application "Adobe Photoshop CC 2018"
activate
tell application "System Events"
set ProcessList to name of every process
if (applName is in ProcessList) then
tell process applName
delay 0.8
display alert "here I am"
click menu item 2 of menu 1 of menu bar item 3 of menu bar 1
delay 0.8
tell window 1
-- and so on
"click menu item 2 of menu 1 of menu bar item 3 of menu bar 1" opens the Open File dialog.
When running automatically after building or form the Debug (or Build) folder, that dialog opens; when running from elsewhere, the last thing I get is the "I am here" alert.
Also note that there is another block of code omitted, which uses a plist file to pass some parameters at runtime, but these parameters are needed only later in the script.
Thanks in advance
This is most likely because you need to build for release instead of debug. To do this go to Product -> Scheme -> Edit Scheme. Then click the Run tab on the side and change the Build Configuration to Release.
This will build an executable that should be completely self contained.

Automator not working inside xcode

I am running an automator with an applescript, and while it works inside automator, it fails when run from xcode.
Here's my code:
tell application "Xcode"
set targetProjectPath to path of active workspace document
set targetProjectPath to POSIX file (targetProjectPath & "/..") as string
set targetProjectPath to POSIX path of targetProjectPath
tell application "System Events"
tell process "Xcode"
click menu item "Save" of menu 0 of menu bar item "File" of menu bar 0
end tell
end tell
return {targetProjectPath}
end tell
I am trying to trigger save and return me the path of the active workspace to do some work later.
I just started on it few days back, so pardon me if it's something really stupid.
It was really simple. All I needed was to give xcode access to be able to modify accessibility settings. The code is working perfectly fine.

Simple GUI scripting question

I am trying this simple GUI script to open a new window of Safari:
tell application "Safari"
activate
end tell
tell application "System Events"
tell process "Safari"
try
tell menu bar 1
tell menu bar item 3
click menu item 1
end tell
end tell
on error theError
display dialog ("An error occurred while performing requested action" & theError) buttons "OK" default button "OK"
end try
end tell
end tell
but it is giving this error message:
Expected end of line but found """
Can anyone suggest me where I may be wrong?
Thanks,
Miraaj
Wow, that was weird. Your script broke AppleScript Editor. After running your script and it not working... I tried to recompile the script and then the error you posted starting showing up. So somehow your code caused AppleScript editor to break and thus the error. I had to quit and relaunch AppleScript Editor to get it working again.
I used the application UI Browser and found the problem. Your reference to the menu item was wrong. There's an extra menu in there that we can't see... and you didn't reference that extra menu. This is the problem with gui scripting. And even if a gui script works it may break at some future date as an application is updated. As such avoid gui scripting if at all possible.
Anyway, here's what your code should look like...
tell application "Safari"
activate
end tell
tell application "System Events"
tell process "Safari"
try
tell menu bar 1
tell menu bar item 3
click menu item 1 of menu 1
end tell
end tell
on error theError
display dialog ("An error occurred while performing requested action " & theError) buttons "OK" default button "OK"
end try
end tell
end tell
EDIT:
As I mentioned in my comment below, if you can't find a native command from an application's dictionary, the next most reliable method is using keyboard shortcuts. Most menu items have them. For example, if I wanted to open a new tab in a window that menu item has the keyboard shortcut command-t. So we can use that like this. Note there is a native command to open a new tab without using keystrokes, I'm just showing this as an example.
tell application "Safari" to activate
tell application "System Events"
keystroke "t" using command down
end tell
end
Keyboard commands don't usually change between application updates whereas gui commands often do because programmers redesign their interface in updates... and when that happens gui scripting goes haywire. One of the gotcha's with both gui scripting and keystrokes is that sometimes the script goes too fast and these techniques can't keep up with the speed of the program, so they often error. When this happens you need to slow down the script using small delays to allow the interface to keep up with the script.

Resources