Module: OS::Mac::Xcode Private

Defined in:
os/mac/xcode.rb

Overview

This module is part of a private API. This module may only be used in the Homebrew/brew repository. Third parties should avoid using this module if possible, as it may be removed or changed without warning.

Helper module for querying Xcode information.

Constant Summary collapse

DEFAULT_BUNDLE_PATH =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

Pathname("/Applications/Xcode.app").freeze
BUNDLE_ID =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

"com.apple.dt.Xcode"
OLD_BUNDLE_ID =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

"com.apple.Xcode"
APPLE_DEVELOPER_DOWNLOAD_URL =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

"https://developer.apple.com/download/all/"

Class Method Summary collapse

Class Method Details

.below_minimum_version?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


59
60
61
62
63
# File 'os/mac/xcode.rb', line 59

def self.below_minimum_version?
  return false unless installed?

  version < minimum_version
end

.bundle_pathPathname?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



117
118
119
120
121
122
123
124
125
# File 'os/mac/xcode.rb', line 117

def self.bundle_path
  # Use the default location if it exists.
  return DEFAULT_BUNDLE_PATH if DEFAULT_BUNDLE_PATH.exist?

  # Ask Spotlight where Xcode is. If the user didn't install the
  # helper tools and installed Xcode in a non-conventional place, this
  # is our only option. See: https://superuser.com/questions/390757
  MacOS.app_with_bundle_id(BUNDLE_ID, OLD_BUNDLE_ID)
end

.default_prefix?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


264
265
266
# File 'os/mac/xcode.rb', line 264

def self.default_prefix?
  prefix.to_s == "/Applications/Xcode.app/Contents/Developer"
end

.detect_versionString?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'os/mac/xcode.rb', line 191

def self.detect_version
  # This is a separate function as you can't cache the value out of a block
  # if return is used in the middle, which we do many times in here.
  return if !MacOS::Xcode.installed? && !MacOS::CLT.installed?

  if MacOS::Xcode.installed?
    # Fast path that will probably almost always work unless `xcode-select -p` is misconfigured
    version_plist = T.must(prefix).parent/"version.plist"
    if version_plist.file?
      data = Plist.parse_xml(version_plist, marshal: false)
      version = data["CFBundleShortVersionString"] if data
      return version if version
    end

    %W[
      #{prefix}/usr/bin/xcodebuild
      #{which("xcodebuild")}
    ].uniq.each do |xcodebuild_path|
      next unless File.executable? xcodebuild_path

      xcodebuild_output = Utils.popen_read(xcodebuild_path, "-version")
      next unless $CHILD_STATUS.success?

      xcode_version = xcodebuild_output[/Xcode (\d+(\.\d+)*)/, 1]
      return xcode_version if xcode_version

      # Xcode 2.x's xcodebuild has a different version string
      case xcodebuild_output[/DevToolsCore-(\d+\.\d)/, 1]
      when "798.0" then return "2.5"
      when "515.0" then return "2.0"
      end
    end
  end

  detect_version_from_clang_version
end

.detect_version_from_clang_versionString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'os/mac/xcode.rb', line 229

def self.detect_version_from_clang_version
  version = ::DevelopmentTools.clang_version

  return "dunno" if version.null?

  # This logic provides a fake Xcode version based on the
  # installed CLT version. This is useful as they are packaged
  # simultaneously so workarounds need to apply to both based on their
  # comparable version.
  case version
  when "6.0.0"  then "6.2"
  when "6.1.0"  then "6.4"
  when "7.0.0"  then "7.1"
  when "7.0.2"  then "7.2.1"
  when "7.3.0"  then "7.3.1"
  when "8.0.0"  then "8.2.1"
  when "8.1.0"  then "8.3.3"
  when "9.0.0"  then "9.2"
  when "9.1.0"  then "9.4.1"
  when "10.0.0" then "10.1"
  when "10.0.1" then "10.3"
  when "11.0.0" then "11.3.1"
  when "11.0.3" then "11.7"
  when "12.0.0" then "12.4"
  when "12.0.5" then "12.5.1"
  when "13.0.0" then "13.2.1"
  when "13.1.6" then "13.4.1"
  when "14.0.0" then "14.2"
  when "14.0.3" then "14.3.1"
  when "16.0.0" then "16.0"
  else               "15.4"
  end
end

.installation_instructionsString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



148
149
150
151
152
153
154
155
156
157
158
159
# File 'os/mac/xcode.rb', line 148

def self.installation_instructions
  if OS::Mac.version.prerelease?
    <<~EOS
      Xcode can be installed from:
        #{Formatter.url(APPLE_DEVELOPER_DOWNLOAD_URL)}
    EOS
  else
    <<~EOS
      Xcode can be installed from the App Store.
    EOS
  end
end

.installed?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


128
129
130
# File 'os/mac/xcode.rb', line 128

def self.installed?
  !prefix.nil?
end

.latest_sdk_version?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


66
67
68
# File 'os/mac/xcode.rb', line 66

def self.latest_sdk_version?
  OS::Mac.full_version >= OS::Mac.latest_sdk_version
end

.latest_version(macos: MacOS.version) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Bump these when a new version is available from the App Store and our CI systems have been updated. This may be a beta version for a beta macOS.

Parameters:

Returns:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'os/mac/xcode.rb', line 17

def self.latest_version(macos: MacOS.version)
  latest_stable = "15.4"
  case macos
  when "15" then "16.0"
  when "14" then latest_stable
  when "13" then "15.2"
  when "12" then "14.2"
  when "11" then "13.2.1"
  when "10.15" then "12.4"
  when "10.14" then "11.3.1"
  when "10.13" then "10.1"
  when "10.12" then "9.2"
  when "10.11" then "8.2.1"
  else
    raise "macOS '#{MacOS.version}' is invalid" unless OS::Mac.version.prerelease?

    # Default to newest known version of Xcode for unreleased macOS versions.
    latest_stable
  end
end

.minimum_versionString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Bump these if things are badly broken (e.g. no SDK for this macOS) without this. Generally this will be the first Xcode release on that macOS version (which may initially be a beta if that version of macOS is also in beta).

Returns:



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'os/mac/xcode.rb', line 43

def self.minimum_version
  case MacOS.version
  when "15" then "16.0"
  when "14" then "15.0"
  when "13" then "14.1"
  when "12" then "13.1"
  when "11" then "12.2"
  when "10.15" then "11.0"
  when "10.14" then "10.2"
  when "10.13" then "9.0"
  when "10.12" then "8.0"
  else "7.3"
  end
end

.needs_clt_installed?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


71
72
73
74
75
76
77
78
79
80
# File 'os/mac/xcode.rb', line 71

def self.needs_clt_installed?
  return false if latest_sdk_version?

  # With fake El Capitan for Portable Ruby, we want the full 10.11 SDK so that we can link
  # against the correct set of libraries in the SDK sysroot rather than the system's copies.
  # We therefore do not use the CLT under this setup, which installs to /usr/include.
  return false if ENV["HOMEBREW_FAKE_MACOS"]

  without_clt?
end

.outdated?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


83
84
85
86
87
# File 'os/mac/xcode.rb', line 83

def self.outdated?
  return false unless installed?

  version < latest_version
end

.prefixPathname?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns a Pathname object corresponding to Xcode.app's Developer directory or nil if Xcode.app is not installed.

Returns:



97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'os/mac/xcode.rb', line 97

def self.prefix
  @prefix ||= begin
    dir = MacOS.active_developer_dir

    if dir.empty? || dir == CLT::PKG_PATH || !File.directory?(dir)
      path = bundle_path
      path/"Contents/Developer" if path
    else
      # Use cleanpath to avoid pathological trailing slash
      Pathname.new(dir).cleanpath
    end
  end
end

.sdk(version = nil) ⇒ SDK?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



138
139
140
# File 'os/mac/xcode.rb', line 138

def self.sdk(version = nil)
  sdk_locator.sdk_if_applicable(version)
end

.sdk_locatorXcodeSDKLocator

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



133
134
135
# File 'os/mac/xcode.rb', line 133

def self.sdk_locator
  @sdk_locator ||= XcodeSDKLocator.new
end

.sdk_path(version = nil) ⇒ Pathname?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



143
144
145
# File 'os/mac/xcode.rb', line 143

def self.sdk_path(version = nil)
  sdk(version)&.path
end

.toolchain_pathPathname

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



112
113
114
# File 'os/mac/xcode.rb', line 112

def self.toolchain_path
  Pathname("#{prefix}/Toolchains/XcodeDefault.xctoolchain")
end

.update_instructionsString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



162
163
164
165
166
167
168
169
170
171
172
173
# File 'os/mac/xcode.rb', line 162

def self.update_instructions
  if OS::Mac.version.prerelease?
    <<~EOS
      Xcode can be updated from:
        #{Formatter.url(APPLE_DEVELOPER_DOWNLOAD_URL)}
    EOS
  else
    <<~EOS
      Xcode can be updated from the App Store.
    EOS
  end
end

.version::Version

This method is part of an internal API. This method may only be used internally in repositories owned by Homebrew, except in casks or formulae. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Get the Xcode version.

Returns:



179
180
181
182
183
184
185
186
187
188
# File 'os/mac/xcode.rb', line 179

def self.version
  # may return a version string
  # that is guessed based on the compiler, so do not
  # use it in order to check if Xcode is installed.
  if @version ||= detect_version
    ::Version.new @version
  else
    ::Version::NULL
  end
end

.without_clt?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


90
91
92
# File 'os/mac/xcode.rb', line 90

def self.without_clt?
  !MacOS::CLT.installed?
end