Skip to content

Commit

Permalink
feat!: improved xcodebuild performance by blocking Apple domain
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
wojciech-kulik committed Oct 20, 2024
1 parent 3b3cea5 commit 69145a0
Show file tree
Hide file tree
Showing 12 changed files with 361 additions and 76 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,7 @@ return {
Install external tools:

```shell
brew install xcode-build-server
brew install xcbeautify
brew install ruby
brew install pipx
brew install xcode-build-server xcbeautify ruby pipx
gem install xcodeproj
pipx install pymobiledevice3
```
Expand Down Expand Up @@ -408,6 +405,9 @@ vim.keymap.set("n", "<leader>xa", "<cmd>XcodebuildCodeActions<cr>", { desc = "Sh
open_expanded = false,
},
integrations = {
xcodebuild_offline = {
enabled = false, -- improves build time (requires configuration, see `:h xcodebuild.xcodebuild-offline`)
},
xcode_build_server = {
enabled = false, -- run "xcode-build-server config" when scheme changes
},
Expand Down
97 changes: 93 additions & 4 deletions doc/xcodebuild.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ neo-tree.nvim Integration ··················· |xcodebuild.int
oil.nvim Integration ························ |xcodebuild.integrations.oil-nvim|
Quick Test Framework Integration ··············· |xcodebuild.integrations.quick|
xcode-build-server Integration ···· |xcodebuild.integrations.xcode-build-server|
Xcodebuild Workaround ············· |xcodebuild.integrations.xcodebuild-offline|
Pickers ················································ |xcodebuild.ui.pickers|
Helpers ··················································· |xcodebuild.helpers|
Lua Utils ···················································· |xcodebuild.util|
Expand Down Expand Up @@ -165,6 +166,9 @@ M.setup({options}) *xcodebuild.setup*
open_expanded = false,
},
integrations = {
xcodebuild_offline = {
enabled = false, -- improves build time (requires configuration, see `:h xcodebuild.xcodebuild-offline`)
},
xcode_build_server = {
enabled = true, -- enable calling "xcode-build-server config" when project config changes
},
Expand Down Expand Up @@ -247,7 +251,7 @@ Availability of features


🔐 - requires passwordless `sudo` permission to `tools/remote_debugger`
script (see |xcodebuild.sudo|).
script (see |xcodebuild.ios17|).

🛠️ - available if pymobiledevice3 is installed.

Expand Down Expand Up @@ -290,7 +294,7 @@ or just:
<

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


==============================================================================
Expand Down Expand Up @@ -543,8 +547,7 @@ Sample `lualine` integration:
==============================================================================
Debugging On iOS 17+ Device *xcodebuild.integrations.ios17*

*xcodebuild.integrations.sudo*
*xcodebuild.sudo*
*xcodebuild.ios17*
Since iOS 17, a new secure connection between Mac and mobile devices is
required. Xcodebuild.nvim uses `pymobiledevice3` to establish a special
trusted tunnel that is later used for debugging. However, this operation
Expand Down Expand Up @@ -3699,6 +3702,92 @@ M.run_config({projectCommand}, {scheme})
(number) job id


==============================================================================
Xcodebuild Workaround *xcodebuild.integrations.xcodebuild-offline*

*xcodebuild.xcodebuild-offline*

This module provides a workaround for the issue with slow `xcodebuild` command.

The issue is caused by the fact that `xcodebuild` tries to connect to the Apple
servers before building the project, which can take 20 seconds or more.
Usually, those requests are not necessary, but they slow down each build.

This module provides a workaround by mapping Apple servers to localhost in the
`/etc/hosts` file during the build. It is a temporary solution and should be
used with caution.

Keep in mind that disabling access to `developerservices2.apple.com` for
`xcodebuild` may cause some issues with the build process. It will disable
things like registering devices, capabilities, and other network-related
features. Therefore, it's best to use it when you are working just on the
code and don't need updating project settings.

You can apply this workaround in two ways:
1. Manual - by either editing manually `/etc/hosts` and adding
`127.0.0.1 developerservices2.apple.com` or by blocking the
`developerservices2.apple.com` domain in any network sniffer like
Proxyman or Charles Proxy.
2. Automatic - more advanced integration that is supported by the plugin.
The advantage of this approach is that the Apple server will be blocked
only when the `xcodebuild` command (triggered from Neovim) is running.
However, it requires a passwordless `sudo` permission for the script.

⚠️ CAUTION
Giving passwordless `sudo` access to that file, potentially opens a gate for
malicious software that could modify the file and run some evil code using
`root` account. The best way to protect that file is to create a local copy,
change the owner to `root`, and give write permission only to `root`.
The script below does that automatically.

👉 Enable integration that automatically blocks Apple servers

Update your config with:
>lua
integrations = {
xcodebuild_offline = {
enabled = true,
},
}
<

👉 Run the following command to install & protect the script

>bash
DIR="$HOME/Library/xcodebuild.nvim" && \
FILE="$DIR/xcodebuild_offline" && \
SOURCE="$HOME/.local/share/nvim/lazy/xcodebuild.nvim/tools/xcodebuild_offline" && \
ME="$(whoami)" && \
mkdir -p "$DIR" && \
cp "$SOURCE" "$FILE" && \
chmod 755 "$FILE" && \
sudo chown root "$FILE" && \
sudo bash -c "echo \"$ME ALL = (ALL) NOPASSWD: $FILE\" >> /etc/sudoers"
<

More details about this issue can be found here:
https://github.com/wojciech-kulik/xcodebuild.nvim/issues/201#issuecomment-2423828065


*xcodebuild.integrations.xcodebuild-offline.is_enabled*
M.is_enabled()
Returns whether the `xcodebuild` command should be run in offline mode.

Returns: ~
(boolean)


*xcodebuild.integrations.xcodebuild-offline.wrap_command_if_needed*
M.wrap_command_if_needed({command})
Wraps the `xcodebuild` command with the workaround script if needed.

Parameters: ~
{command} (string)

Returns: ~
(string)


==============================================================================
Pickers *xcodebuild.ui.pickers*

Expand Down
3 changes: 3 additions & 0 deletions lua/xcodebuild/core/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ local defaults = {
open_expanded = false,
},
integrations = {
xcodebuild_offline = {
enabled = false, -- improves build time (requires configuration, see `:h xcodebuild.xcodebuild-offline`)
},
xcode_build_server = {
enabled = true, -- enable calling "xcode-build-server config" when project config changes
},
Expand Down
9 changes: 9 additions & 0 deletions lua/xcodebuild/core/xcode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
local util = require("xcodebuild.util")
local notifications = require("xcodebuild.broadcasting.notifications")
local constants = require("xcodebuild.core.constants")
local xcodebuildOffline = require("xcodebuild.integrations.xcodebuild-offline")

local M = {}
local CANCELLED_CODE = 143
Expand Down Expand Up @@ -144,6 +145,7 @@ end
---@return number # job id
function M.get_destinations(projectCommand, scheme, callback)
local command = "xcodebuild -showdestinations " .. projectCommand .. " -scheme '" .. scheme .. "'"
command = xcodebuildOffline.wrap_command_if_needed(command)

return vim.fn.jobstart(command, {
stdout_buffered = true,
Expand Down Expand Up @@ -185,6 +187,7 @@ end
---@return number # job id
function M.get_schemes(projectCommand, callback)
local command = "xcodebuild " .. projectCommand .. " -list"
command = xcodebuildOffline.wrap_command_if_needed(command)

return vim.fn.jobstart(command, {
stdout_buffered = true,
Expand Down Expand Up @@ -254,6 +257,7 @@ end
---@return number # job id
function M.get_project_information(xcodeproj, callback)
local command = "xcodebuild -project '" .. xcodeproj .. "' -list"
command = xcodebuildOffline.wrap_command_if_needed(command)

return vim.fn.jobstart(command, {
stdout_buffered = true,
Expand Down Expand Up @@ -304,6 +308,7 @@ end
---@return number # job id
function M.get_testplans(projectCommand, scheme, callback)
local command = "xcodebuild test " .. projectCommand .. " -scheme '" .. scheme .. "' -showTestPlans"
command = xcodebuildOffline.wrap_command_if_needed(command)

return vim.fn.jobstart(command, {
stdout_buffered = true,
Expand Down Expand Up @@ -346,6 +351,7 @@ function M.build_project(opts)
.. opts.destination
.. "'"
.. (string.len(opts.extraBuildArgs) > 0 and " " .. opts.extraBuildArgs or "")
command = xcodebuildOffline.wrap_command_if_needed(command)

return vim.fn.jobstart(command, {
stdout_buffered = false,
Expand Down Expand Up @@ -393,6 +399,7 @@ function M.get_build_settings(platform, projectCommand, scheme, xcodeprojPath, c
.. "' -showBuildSettings"
.. " -sdk "
.. sdk
command = xcodebuildOffline.wrap_command_if_needed(command)

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

return vim.fn.jobstart(command, {
on_exit = function(_, code, _)
Expand Down Expand Up @@ -807,6 +815,7 @@ function M.run_tests(opts)
.. opts.testPlan
.. "'"
.. (string.len(opts.extraTestArgs) > 0 and " " .. opts.extraTestArgs or "")
command = xcodebuildOffline.wrap_command_if_needed(command)

if opts.testsToRun then
for _, test in ipairs(opts.testsToRun) do
Expand Down
2 changes: 1 addition & 1 deletion lua/xcodebuild/docs/features.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
---
---
--- 🔐 - requires passwordless `sudo` permission to `tools/remote_debugger`
--- script (see |xcodebuild.sudo|).
--- script (see |xcodebuild.ios17|).
---
--- 🛠️ - available if pymobiledevice3 is installed.
---
Expand Down
3 changes: 1 addition & 2 deletions lua/xcodebuild/docs/ios17.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---@mod xcodebuild.integrations.ios17 Debugging On iOS 17+ Device
---@tag xcodebuild.integrations.sudo
---@tag xcodebuild.sudo
---@tag xcodebuild.ios17
---@brief [[
---Since iOS 17, a new secure connection between Mac and mobile devices is
---required. Xcodebuild.nvim uses `pymobiledevice3` to establish a special
Expand Down
2 changes: 1 addition & 1 deletion lua/xcodebuild/docs/requirements.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
---<
---
---To debug on physical devices with iOS 17+ you will need to set up `sudo`,
---see |xcodebuild.sudo| to learn more.
---see |xcodebuild.ios17| to learn more.
---
---@brief ]]

Expand Down
59 changes: 45 additions & 14 deletions lua/xcodebuild/health.lua
Original file line number Diff line number Diff line change
Expand Up @@ -277,31 +277,61 @@ local function check_os()
end
end

local function check_sudo()
local function has_sudo_access(path)
local util = require("xcodebuild.util")
local permissions = util.shell("sudo -l 2>/dev/null")

for _, line in ipairs(permissions) do
if line:match("NOPASSWD.*" .. path) then
return true
end
end

return false
end

local function check_remote_debugger_sudo()
local deviceProxy = require("xcodebuild.platform.device_proxy")
if not deviceProxy.is_installed() then
return
end

start("Checking passwordless sudo")
start("Checking passwordless sudo for remote_debugger")

local config = require("xcodebuild.core.config")
local appdata = require("xcodebuild.project.appdata")
local util = require("xcodebuild.util")

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

for _, line in ipairs(permissions) do
if line:match("NOPASSWD.*" .. path) then
ok("sudo: configured")
return
end
if has_sudo_access(path) then
ok("sudo: configured")
else
warn("passwordless sudo permission for `remote_debugger` is not configured.")
warn("debugging on physical devices with iOS 17+ will not work.")
warn("see `:h xcodebuild.ios17` for more information.")
end
end

warn("sudo: passwordless permission for `remote_debugger` is not configured.")
warn("debugging on physical devices with iOS 17+ will not work.")
warn("see `:h xcodebuild.sudo` for more information.")
local function check_xcodebuild_offline_sudo()
local xcodebuildOffline = require("xcodebuild.integrations.xcodebuild-offline")
local isEnabled = xcodebuildOffline.is_enabled()
if not isEnabled then
start("Checking xcodebuild_offline tool")
warn("tool not enabled - builds might be slower.")
warn("see `:h xcodebuild.xcodebuild-offline` for more information.")
return
end

start("Checking passwordless sudo for xcodebuild_offline")

if has_sudo_access(xcodebuildOffline.scriptPath) then
ok("sudo: configured")
else
error("passwordless sudo permission for `xcodebuild_offline` is not configured.")
error("see `:h xcodebuild.xcodebuild-offline` for more information.")
end
end

local function check_plugin_commit()
Expand Down Expand Up @@ -367,7 +397,8 @@ M.check = function()
start("Checking .nvim/xcodebuild/settings.json")
check_xcodebuild_settings()

check_sudo()
check_xcodebuild_offline_sudo()
check_remote_debugger_sudo()
end

return M
3 changes: 3 additions & 0 deletions lua/xcodebuild/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ end
--- open_expanded = false,
--- },
--- integrations = {
--- xcodebuild_offline = {
--- enabled = false, -- improves build time (requires configuration, see `:h xcodebuild.xcodebuild-offline`)
--- },
--- xcode_build_server = {
--- enabled = true, -- enable calling "xcode-build-server config" when project config changes
--- },
Expand Down
Loading

0 comments on commit 69145a0

Please sign in to comment.