Skip to content

Commit e7f88bf

Browse files
feat!: improved xcodebuild performance by blocking Apple domain
BREAKING CHANGE: (optional) please see `:h xcodebuild.xcodebuild-offline` to enable tool that improves build speed. More details about this issue here (#201): #201 (comment)
1 parent 3b3cea5 commit e7f88bf

12 files changed

+349
-76
lines changed

README.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,7 @@ return {
119119
Install external tools:
120120

121121
```shell
122-
brew install xcode-build-server
123-
brew install xcbeautify
124-
brew install ruby
125-
brew install pipx
122+
brew install xcode-build-server xcbeautify ruby pipx
126123
gem install xcodeproj
127124
pipx install pymobiledevice3
128125
```
@@ -408,6 +405,7 @@ vim.keymap.set("n", "<leader>xa", "<cmd>XcodebuildCodeActions<cr>", { desc = "Sh
408405
open_expanded = false,
409406
},
410407
integrations = {
408+
xcodebuild_offline = false, -- improves build time (requires configuration, see `:h xcodebuild.xcodebuild-offline`)
411409
xcode_build_server = {
412410
enabled = false, -- run "xcode-build-server config" when scheme changes
413411
},

doc/xcodebuild.txt

+89-4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ neo-tree.nvim Integration ··················· |xcodebuild.int
4444
oil.nvim Integration ························ |xcodebuild.integrations.oil-nvim|
4545
Quick Test Framework Integration ··············· |xcodebuild.integrations.quick|
4646
xcode-build-server Integration ···· |xcodebuild.integrations.xcode-build-server|
47+
Xcodebuild Workaround ············· |xcodebuild.integrations.xcodebuild-offline|
4748
Pickers ················································ |xcodebuild.ui.pickers|
4849
Helpers ··················································· |xcodebuild.helpers|
4950
Lua Utils ···················································· |xcodebuild.util|
@@ -165,6 +166,7 @@ M.setup({options}) *xcodebuild.setup*
165166
open_expanded = false,
166167
},
167168
integrations = {
169+
xcodebuild_offline = false, -- improves build time (requires configuration, see `:h xcodebuild.xcodebuild-offline`)
168170
xcode_build_server = {
169171
enabled = true, -- enable calling "xcode-build-server config" when project config changes
170172
},
@@ -247,7 +249,7 @@ Availability of features
247249

248250

249251
🔐 - requires passwordless `sudo` permission to `tools/remote_debugger`
250-
script (see |xcodebuild.sudo|).
252+
script (see |xcodebuild.ios17|).
251253

252254
🛠️ - available if pymobiledevice3 is installed.
253255

@@ -290,7 +292,7 @@ or just:
290292
<
291293

292294
To debug on physical devices with iOS 17+ you will need to set up `sudo`,
293-
see |xcodebuild.sudo| to learn more.
295+
see |xcodebuild.ios17| to learn more.
294296

295297

296298
==============================================================================
@@ -543,8 +545,7 @@ Sample `lualine` integration:
543545
==============================================================================
544546
Debugging On iOS 17+ Device *xcodebuild.integrations.ios17*
545547

546-
*xcodebuild.integrations.sudo*
547-
*xcodebuild.sudo*
548+
*xcodebuild.ios17*
548549
Since iOS 17, a new secure connection between Mac and mobile devices is
549550
required. Xcodebuild.nvim uses `pymobiledevice3` to establish a special
550551
trusted tunnel that is later used for debugging. However, this operation
@@ -3699,6 +3700,90 @@ M.run_config({projectCommand}, {scheme})
36993700
(number) job id
37003701

37013702

3703+
==============================================================================
3704+
Xcodebuild Workaround *xcodebuild.integrations.xcodebuild-offline*
3705+
3706+
*xcodebuild.xcodebuild-offline*
3707+
3708+
This module provides a workaround for the issue with slow `xcodebuild` command.
3709+
3710+
The issue is caused by the fact that `xcodebuild` tries to connect to the Apple
3711+
servers before building the project, which can take 20 seconds or more.
3712+
Usually, those requests are not necessary, but they slow down each build.
3713+
3714+
This module provides a workaround by mapping Apple servers to localhost in the
3715+
`/etc/hosts` file during the build. It is a temporary solution and should be
3716+
used with caution.
3717+
3718+
Keep in mind that disabling access to `developerservices2.apple.com` for
3719+
`xcodebuild` may cause some issues with the build process. It will disable
3720+
things like registering devices, capabilities, and other network-related
3721+
features. Therefore, it's best to use it when you are working just on the
3722+
code and don't need updating project settings.
3723+
3724+
You can apply this workaround in two ways:
3725+
1. Manual - by either editing manually `/etc/hosts` and adding
3726+
`127.0.0.1 developerservices2.apple.com` or by blocking the
3727+
`developerservices2.apple.com` domain in any network sniffer like
3728+
Proxyman or Charles Proxy.
3729+
2. Automatic - more advanced integration that is supported by the plugin.
3730+
The advantage of this approach is that the Apple server will be blocked
3731+
only when the `xcodebuild` command (triggered from Neovim) is running.
3732+
However, it requires a passwordless `sudo` permission for the script.
3733+
3734+
⚠️ CAUTION
3735+
Giving passwordless `sudo` access to that file, potentially opens a gate for
3736+
malicious software that could modify the file and run some evil code using
3737+
`root` account. The best way to protect that file is to create a local copy,
3738+
change the owner to `root`, and give write permission only to `root`.
3739+
The script below does that automatically.
3740+
3741+
👉 Enable integration that automatically blocks Apple servers
3742+
3743+
Update your config with:
3744+
>lua
3745+
integrations = {
3746+
xcodebuild_offline = true,
3747+
}
3748+
<
3749+
3750+
👉 Run the following command to install & protect the script
3751+
3752+
>bash
3753+
DIR="$HOME/Library/xcodebuild.nvim" && \
3754+
FILE="$DIR/xcodebuild_offline" && \
3755+
SOURCE="$HOME/.local/share/nvim/lazy/xcodebuild.nvim/tools/xcodebuild_offline" && \
3756+
ME="$(whoami)" && \
3757+
mkdir -p "$DIR" && \
3758+
cp "$SOURCE" "$FILE" && \
3759+
chmod 755 "$FILE" && \
3760+
sudo chown root "$FILE" && \
3761+
sudo bash -c "echo \"$ME ALL = (ALL) NOPASSWD: $FILE\" >> /etc/sudoers"
3762+
<
3763+
3764+
More details about this issue can be found here:
3765+
https://github.com/wojciech-kulik/xcodebuild.nvim/issues/201#issuecomment-2423828065
3766+
3767+
3768+
*xcodebuild.integrations.xcodebuild-offline.is_enabled*
3769+
M.is_enabled()
3770+
Returns whether the `xcodebuild` command should be run in offline mode.
3771+
3772+
Returns: ~
3773+
(boolean)
3774+
3775+
3776+
*xcodebuild.integrations.xcodebuild-offline.wrap_command_if_needed*
3777+
M.wrap_command_if_needed({command})
3778+
Wraps the `xcodebuild` command with the workaround script if needed.
3779+
3780+
Parameters: ~
3781+
{command} (string)
3782+
3783+
Returns: ~
3784+
(string)
3785+
3786+
37023787
==============================================================================
37033788
Pickers *xcodebuild.ui.pickers*
37043789

lua/xcodebuild/core/config.lua

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ local defaults = {
102102
open_expanded = false,
103103
},
104104
integrations = {
105+
xcodebuild_offline = false, -- improves build time (requires configuration, see `:h xcodebuild.xcodebuild-offline`)
105106
xcode_build_server = {
106107
enabled = true, -- enable calling "xcode-build-server config" when project config changes
107108
},

lua/xcodebuild/core/xcode.lua

+9
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
local util = require("xcodebuild.util")
6565
local notifications = require("xcodebuild.broadcasting.notifications")
6666
local constants = require("xcodebuild.core.constants")
67+
local xcodebuildOffline = require("xcodebuild.integrations.xcodebuild-offline")
6768

6869
local M = {}
6970
local CANCELLED_CODE = 143
@@ -144,6 +145,7 @@ end
144145
---@return number # job id
145146
function M.get_destinations(projectCommand, scheme, callback)
146147
local command = "xcodebuild -showdestinations " .. projectCommand .. " -scheme '" .. scheme .. "'"
148+
command = xcodebuildOffline.wrap_command_if_needed(command)
147149

148150
return vim.fn.jobstart(command, {
149151
stdout_buffered = true,
@@ -185,6 +187,7 @@ end
185187
---@return number # job id
186188
function M.get_schemes(projectCommand, callback)
187189
local command = "xcodebuild " .. projectCommand .. " -list"
190+
command = xcodebuildOffline.wrap_command_if_needed(command)
188191

189192
return vim.fn.jobstart(command, {
190193
stdout_buffered = true,
@@ -254,6 +257,7 @@ end
254257
---@return number # job id
255258
function M.get_project_information(xcodeproj, callback)
256259
local command = "xcodebuild -project '" .. xcodeproj .. "' -list"
260+
command = xcodebuildOffline.wrap_command_if_needed(command)
257261

258262
return vim.fn.jobstart(command, {
259263
stdout_buffered = true,
@@ -304,6 +308,7 @@ end
304308
---@return number # job id
305309
function M.get_testplans(projectCommand, scheme, callback)
306310
local command = "xcodebuild test " .. projectCommand .. " -scheme '" .. scheme .. "' -showTestPlans"
311+
command = xcodebuildOffline.wrap_command_if_needed(command)
307312

308313
return vim.fn.jobstart(command, {
309314
stdout_buffered = true,
@@ -346,6 +351,7 @@ function M.build_project(opts)
346351
.. opts.destination
347352
.. "'"
348353
.. (string.len(opts.extraBuildArgs) > 0 and " " .. opts.extraBuildArgs or "")
354+
command = xcodebuildOffline.wrap_command_if_needed(command)
349355

350356
return vim.fn.jobstart(command, {
351357
stdout_buffered = false,
@@ -393,6 +399,7 @@ function M.get_build_settings(platform, projectCommand, scheme, xcodeprojPath, c
393399
.. "' -showBuildSettings"
394400
.. " -sdk "
395401
.. sdk
402+
command = xcodebuildOffline.wrap_command_if_needed(command)
396403

397404
if config then
398405
command = command .. " -configuration '" .. config .. "'"
@@ -747,6 +754,7 @@ function M.enumerate_tests(opts, callback)
747754
.. "' -disableAutomaticPackageResolution -skipPackageUpdates -parallelizeTargets"
748755
.. " -test-enumeration-style flat"
749756
.. (string.len(opts.extraTestArgs) > 0 and " " .. opts.extraTestArgs or "")
757+
command = xcodebuildOffline.wrap_command_if_needed(command)
750758

751759
return vim.fn.jobstart(command, {
752760
on_exit = function(_, code, _)
@@ -807,6 +815,7 @@ function M.run_tests(opts)
807815
.. opts.testPlan
808816
.. "'"
809817
.. (string.len(opts.extraTestArgs) > 0 and " " .. opts.extraTestArgs or "")
818+
command = xcodebuildOffline.wrap_command_if_needed(command)
810819

811820
if opts.testsToRun then
812821
for _, test in ipairs(opts.testsToRun) do

lua/xcodebuild/docs/features.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
---
3939
---
4040
--- 🔐 - requires passwordless `sudo` permission to `tools/remote_debugger`
41-
--- script (see |xcodebuild.sudo|).
41+
--- script (see |xcodebuild.ios17|).
4242
---
4343
--- 🛠️ - available if pymobiledevice3 is installed.
4444
---

lua/xcodebuild/docs/ios17.lua

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---@mod xcodebuild.integrations.ios17 Debugging On iOS 17+ Device
2-
---@tag xcodebuild.integrations.sudo
3-
---@tag xcodebuild.sudo
2+
---@tag xcodebuild.ios17
43
---@brief [[
54
---Since iOS 17, a new secure connection between Mac and mobile devices is
65
---required. Xcodebuild.nvim uses `pymobiledevice3` to establish a special

lua/xcodebuild/docs/requirements.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
---<
3434
---
3535
---To debug on physical devices with iOS 17+ you will need to set up `sudo`,
36-
---see |xcodebuild.sudo| to learn more.
36+
---see |xcodebuild.ios17| to learn more.
3737
---
3838
---@brief ]]
3939

lua/xcodebuild/health.lua

+45-14
Original file line numberDiff line numberDiff line change
@@ -277,31 +277,61 @@ local function check_os()
277277
end
278278
end
279279

280-
local function check_sudo()
280+
local function has_sudo_access(path)
281+
local util = require("xcodebuild.util")
282+
local permissions = util.shell("sudo -l 2>/dev/null")
283+
284+
for _, line in ipairs(permissions) do
285+
if line:match("NOPASSWD.*" .. path) then
286+
return true
287+
end
288+
end
289+
290+
return false
291+
end
292+
293+
local function check_remote_debugger_sudo()
281294
local deviceProxy = require("xcodebuild.platform.device_proxy")
282295
if not deviceProxy.is_installed() then
283296
return
284297
end
285298

286-
start("Checking passwordless sudo")
299+
start("Checking passwordless sudo for remote_debugger")
287300

288301
local config = require("xcodebuild.core.config")
289302
local appdata = require("xcodebuild.project.appdata")
290-
local util = require("xcodebuild.util")
291303

292-
local path = config.options.commands.remote_debugger or appdata.tool_path(appdata.REMOTE_DEBUGGER_TOOL)
293-
local permissions = util.shell("sudo -l 2>/dev/null")
304+
local path = config.options.commands.remote_debugger
305+
and vim.fn.expand(config.options.commands.remote_debugger)
306+
or appdata.tool_path(appdata.REMOTE_DEBUGGER_TOOL)
294307

295-
for _, line in ipairs(permissions) do
296-
if line:match("NOPASSWD.*" .. path) then
297-
ok("sudo: configured")
298-
return
299-
end
308+
if has_sudo_access(path) then
309+
ok("sudo: configured")
310+
else
311+
warn("passwordless sudo permission for `remote_debugger` is not configured.")
312+
warn("debugging on physical devices with iOS 17+ will not work.")
313+
warn("see `:h xcodebuild.ios17` for more information.")
300314
end
315+
end
301316

302-
warn("sudo: passwordless permission for `remote_debugger` is not configured.")
303-
warn("debugging on physical devices with iOS 17+ will not work.")
304-
warn("see `:h xcodebuild.sudo` for more information.")
317+
local function check_xcodebuild_offline_sudo()
318+
local xcodebuildOffline = require("xcodebuild.integrations.xcodebuild-offline")
319+
local isEnabled = xcodebuildOffline.is_enabled()
320+
if not isEnabled then
321+
start("Checking xcodebuild_offline tool")
322+
warn("tool not enabled - builds might be slower.")
323+
warn("see `:h xcodebuild.xcodebuild-offline` for more information.")
324+
return
325+
end
326+
327+
start("Checking passwordless sudo for xcodebuild_offline")
328+
329+
if has_sudo_access(xcodebuildOffline.scriptPath) then
330+
ok("sudo: configured")
331+
else
332+
error("passwordless sudo permission for `xcodebuild_offline` is not configured.")
333+
error("see `:h xcodebuild.xcodebuild-offline` for more information.")
334+
end
305335
end
306336

307337
local function check_plugin_commit()
@@ -367,7 +397,8 @@ M.check = function()
367397
start("Checking .nvim/xcodebuild/settings.json")
368398
check_xcodebuild_settings()
369399

370-
check_sudo()
400+
check_xcodebuild_offline_sudo()
401+
check_remote_debugger_sudo()
371402
end
372403

373404
return M

lua/xcodebuild/init.lua

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ end
182182
--- open_expanded = false,
183183
--- },
184184
--- integrations = {
185+
--- xcodebuild_offline = false, -- improves build time (requires configuration, see `:h xcodebuild.xcodebuild-offline`)
185186
--- xcode_build_server = {
186187
--- enabled = true, -- enable calling "xcode-build-server config" when project config changes
187188
--- },

0 commit comments

Comments
 (0)