Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Attributes
|--------------------------------|--------|-----------------------------------------|--------------------------|
| `['xcode']['url']` | String | URL to the Xcode DMG | `nil` |
| `['xcode']['checksum']` | String | Checksum of the Xcode DMG | (in the attributes file) |
| `['xcode']['package_type']` | String | Type of Package ('dmg' or 'zip') | (in the attributes file) |
| `['xcode']['cli']['url']` | String | URL to the Xcode Command-Line Tools DMG | `nil` |
| `['xcode']['cli']['checksum']` | String | Checksum of the Xcode CLI DMG | (in the attributes file) |

Expand Down
26 changes: 22 additions & 4 deletions attributes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
when /^10\.7/
default['xcode']['url'] = nil # should point to xcode4620419895a.dmg
default['xcode']['checksum'] = '3057224339823dae8a56943380a438065e92cff1ad4ab5a6a84f94f7a94dc035'
default["xcode"]["package_type"] = "dmg"
default['xcode']['last_gm_license'] = "EA0720"
default['xcode']['version'] = "4.6.2"

Expand All @@ -34,6 +35,7 @@
when /^10\.8/
default["xcode"]["url"] = nil # should point to xcode_5.1.1.dmg
default["xcode"]["checksum"] = "5bd3c1792b695dae3c96065a9cc02215ec2fab6aecbf708a66b7d19fa65ff967"
default["xcode"]["package_type"] = "dmg"
default["xcode"]["last_gm_license"] = "EA0720"
default["xcode"]["version"] = "5.1.1"

Expand All @@ -46,6 +48,7 @@
when /^10\.9/
default["xcode"]["url"] = nil # should point to Xcode_6.2.dmg
default["xcode"]["checksum"] = "00545c078470c14e6a53204324e2c10283c18c86d3a9f580bf90cbe97c6c28ec"
default["xcode"]["package_type"] = "dmg"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be parsed from the url / file name?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Brantone, great suggestion! I'll make that update.

default["xcode"]["last_gm_license"] = "EA1187"
default["xcode"]["version"] = "6.2"

Expand All @@ -58,6 +61,7 @@
when /^10\.10/
default["xcode"]["url"] = nil # should point to Xcode_7.2.dmg
default["xcode"]["checksum"] = "6f08ddabfb159143e9857de1668e5fdb04ba92b77297f7a1d50cf467be7222c8"
default["xcode"]["package_type"] = "dmg"
default["xcode"]["last_gm_license"] = "EA1327"
default["xcode"]["version"] = "7.2"

Expand All @@ -68,10 +72,24 @@
default["xcode"]["cli"]["package_id"] = "com.apple.pkg.CLTools_Executables"
default["xcode"]["cli"]["volumes_dir"] = "Command Line Developer Tools"
when /^10\.11/
default["xcode"]["url"] = nil # should point to Xcode_7.3.1.dmg
default["xcode"]["checksum"] = "bb0dedf613e86ecb46ced945913fa5069ab716a0ade1035e239d70dee0b2de1b"
default["xcode"]["last_gm_license"] = "EA1327"
default["xcode"]["version"] = "7.3.1"
default["xcode"]["url"] = nil # should point to Xcode_7.3.1.dmg or Xcode_8.1.0.xip
default["xcode"]["checksum"] = "30378e76f7d1adcf3573fc990bd7b46e0939b466b83338ba8f4290444462e5da"
default["xcode"]["package_type"] = "xip"
default["xcode"]["last_gm_license"] = "EA1421"
default["xcode"]["version"] = "8.1"

default["xcode"]["cli"]["url"] = nil # should point to Command_Line_Tools_OS_X_10.11_for_Xcode_7.3.1.dmg
default["xcode"]["cli"]["checksum"] = "0c80753d207fa2254bcc1c880d4d8907071241f3f2e092c7caa87e340245835a"
default["xcode"]["cli"]["package_name"] = "Command Line Tools (OS X 10.11)"
default["xcode"]["cli"]["package_type"] = "pkg"
default["xcode"]["cli"]["package_id"] = "com.apple.pkg.CLTools_Executables"
default["xcode"]["cli"]["volumes_dir"] = "Command Line Developer Tools"
when /^10\.12/
default["xcode"]["url"] = nil # should point to Xcode_7.3.1.dmg or Xcode_8.1.0.xip
default["xcode"]["checksum"] = "30378e76f7d1adcf3573fc990bd7b46e0939b466b83338ba8f4290444462e5da"
default["xcode"]["package_type"] = "xip"
default["xcode"]["last_gm_license"] = "EA1421"
default["xcode"]["version"] = "8.1"

default["xcode"]["cli"]["url"] = nil # should point to Command_Line_Tools_OS_X_10.11_for_Xcode_7.3.1.dmg
default["xcode"]["cli"]["checksum"] = "0c80753d207fa2254bcc1c880d4d8907071241f3f2e092c7caa87e340245835a"
Expand Down
80 changes: 80 additions & 0 deletions files/parse_pbzx2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Source: https://gist.github.com/pudquick/ff412bcb29c9c1fa4b8d#file-parse_pbzx2-py
#
# v2 pbzx stream handler
# My personal writeup on the differences here: https://gist.github.com/pudquick/29fcfe09c326a9b96cf5
#
# Pure python reimplementation of .cpio.xz content extraction from pbzx file payload originally here:
# http://www.tonymacx86.com/general-help/135458-pbzx-stream-parser.html
#
# Cleaned up C version (as the basis for my code) here, thanks to Pepijn Bruienne / @bruienne
# https://gist.github.com/bruienne/029494bbcfb358098b41

import struct, sys

def seekread(f, offset=None, length=0, relative=True):
if (offset != None):
# offset provided, let's seek
f.seek(offset, [0,1,2][relative])
if (length != 0):
return f.read(length)

def parse_pbzx(pbzx_path):
section = 0
xar_out_path = '%s.part%02d.cpio.xz' % (pbzx_path, section)
f = open(pbzx_path, 'rb')
# pbzx = f.read()
# f.close()
magic = seekread(f,length=4)
if magic != 'pbzx':
raise "Error: Not a pbzx file"
# Read 8 bytes for initial flags
flags = seekread(f,length=8)
# Interpret the flags as a 64-bit big-endian unsigned int
flags = struct.unpack('>Q', flags)[0]
xar_f = open(xar_out_path, 'wb')
while (flags & (1 << 24)):
# Read in more flags
flags = seekread(f,length=8)
flags = struct.unpack('>Q', flags)[0]
# Read in length
f_length = seekread(f,length=8)
f_length = struct.unpack('>Q', f_length)[0]
xzmagic = seekread(f,length=6)
if xzmagic != '\xfd7zXZ\x00':
# This isn't xz content, this is actually _raw decompressed cpio_ chunk of 16MB in size...
# Let's back up ...
seekread(f,offset=-6,length=0)
# ... and split it out ...
f_content = seekread(f,length=f_length)
section += 1
decomp_out = '%s.part%02d.cpio' % (pbzx_path, section)
g = open(decomp_out, 'wb')
g.write(f_content)
g.close()
# Now to start the next section, which should hopefully be .xz (we'll just assume it is ...)
xar_f.close()
section += 1
new_out = '%s.part%02d.cpio.xz' % (pbzx_path, section)
xar_f = open(new_out, 'wb')
else:
f_length -= 6
# This part needs buffering
f_content = seekread(f,length=f_length)
tail = seekread(f,offset=-2,length=2)
xar_f.write(xzmagic)
xar_f.write(f_content)
if tail != 'YZ':
xar_f.close()
raise "Error: Footer is not xar file footer"
try:
f.close()
xar_f.close()
except:
pass

def main():
result = parse_pbzx(sys.argv[1])
print "Now xz decompress the .xz chunks, then 'cat' them all together in order into a single new.cpio file"

if __name__ == '__main__':
main()
107 changes: 103 additions & 4 deletions recipes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,109 @@
# limitations under the License.
#

dmg_package "Xcode" do
source node['xcode']['url']
checksum node['xcode']['checksum']
action :install
Chef::Log.info("xcode package type: '#{node['xcode']['package_type']}'.")

case node['xcode']['package_type']
when 'dmg'
dmg_package "Xcode" do
source node['xcode']['url']
checksum node['xcode']['checksum']
action :install
end
when 'xip'

# TODO: add a version check. (i.e. "xcodebuild -version | grep #{node['xcode']['version']}")
if ::Dir.exist? "/Applications/Xcode.app"
Chef::Log.info("xcode version #{node['xcode']['version']} is alread installed. Nothing to do.")
else
# Instructions for how to install XCode via the command line were taken from:
# http://stackoverflow.com/a/39489446
Chef::Log.info("xcode is NOT installed. Installing...")

# extract the name of the archive from the download URL
file_name = File.basename(node['xcode']['url'])

# download the remote xip archive
remote_file "#{Chef::Config[:file_cache_path]}/#{file_name}" do
source node['xcode']['url']
checksum node['xcode']['checksum']
backup false
mode 0644
owner "root"
group "wheel"
action :create
not_if { ::File.exist? "#{Chef::Config[:file_cache_path]}/#{file_name}" }
end

execute 'Verify the signature and certificate chain that signed the archive' do
command "pkgutil --check-signature #{Chef::Config[:file_cache_path]}/#{file_name}"
cwd Chef::Config[:file_cache_path]
end

execute 'Extract the xcode xip archive' do
command "xar -xf #{Chef::Config[:file_cache_path]}/#{file_name}"
cwd Chef::Config[:file_cache_path]
end

# Obtain a PBZX v2 unpacker and... unpack the packed stuff.
# Install PBZX v2 unpacker
cookbook_file "#{Chef::Config[:file_cache_path]}/parse_pbzx2.py" do
source 'parse_pbzx2.py'
owner 'root'
group 'wheel'
mode '0755'
action :create
end

# Unpack the PBZX stream
execute 'Unpack the PBZX stream' do
command 'python parse_pbzx2.py Content'
cwd Chef::Config[:file_cache_path]
end

# Decompress the archive (there should only be one chunk, "part00").
execute 'Unpack the CPIO archive as a privileged user' do
command 'cpio -izmdu 0<Content.part00.cpio.xz'
cwd Chef::Config[:file_cache_path]
end

# Move the resulting Xcode app bundle into /Applications
execute 'Move the resulting Xcode app bundle into /Applications' do
command 'mv -f Xcode.app /Applications/Xcode.app'
cwd Chef::Config[:file_cache_path]
end

# Cleanup
# remove the PBZX v2 unpacker
file "#{Chef::Config[:file_cache_path]}/parse_pbzx2.py" do
backup false
action :delete
only_if { ::File.exist? "#{Chef::Config[:file_cache_path]}/parse_pbzx2.py" }
end

# remove the Content directory
file "#{Chef::Config[:file_cache_path]}/Content" do
backup false
action :delete
only_if { ::File.exist? "#{Chef::Config[:file_cache_path]}/Content" }
end

# Remove the compressed archive
file "#{Chef::Config[:file_cache_path]}/Content.part00.cpio.xz" do
backup false
action :delete
only_if { ::File.exist? "#{Chef::Config[:file_cache_path]}/Content.part00.cpio.xz" }
end

# Remove the downloaded xip file
file "#{Chef::Config[:file_cache_path]}/#{file_name}" do
backup false
action :delete
only_if { ::File.exist? "#{Chef::Config[:file_cache_path]}/#{file_name}" }
end
end
else
raise "Unsupported xcode package type #{node['xcode']['package_type']}! Allowed types are ['dmg', 'xip']."
end

dmg_package node['xcode']['cli']['package_name'] do
Expand Down