Skip to content

Instantly share code, notes, and snippets.

@AlanQuatermain
Created March 27, 2011 21:36
Show Gist options
  • Save AlanQuatermain/889654 to your computer and use it in GitHub Desktop.
Save AlanQuatermain/889654 to your computer and use it in GitHub Desktop.
Updates root locations of .xcarchive bundles for given sub-packages or components within a PackageMaker document. Doesn't modify permissions, content inclusion, etc. Just the root content path, everywhere it lies.
#!/usr/bin/ruby
# == Usage
#
# Modifies an existing (and fully-set-up) PackageMaker document to point a package component
# at a new Xcode Archive root directory.
#
# This script is designed to work only with components based on Xcode 4 archives (folders ending
# in .xcarchive) containing a 'Products' subfolder which is used as the component's root folder.
#
# Each time a new build is made, a new .xcarchive folder is created. It is a fairly involved
# process to edit an existing PackageMaker document to point its root at the new archive and to
# set recommended permissions on everything within that folder. Internally however the document
# contains only relative path information along with (in a few different places) the path to the
# component root. This script will find the references to the package component designated by the
# --component-id parameter and replace the portion of that path ending in a .xcarchive item with a
# new .xcarchive path specified using the --component-root parameter. It can operate on multiple
# sub-packages/components at a time, meaning that you can supply multiple --component-id and
# --component-root parameters, however they *must* be paired, with each id preceding a root.
#
# Usage:
#
# update_package_archive [OPTIONS]
#
# -h, --help::
# Show this message.
# -c, --component-id::
# The identifier (i.e. com.mycompany.mycomponent) specifying the component to act upon.
# -r, --component-root::
# The new .xcarchive folder to use as the component's root. Actually uses the 'Products' folder
# within the .xcarchive bundle.
# -d, --doc::
# The PackageMaker document upon which to act.
# -v, --verbose::
# Print out scanning/replacement information while running.
# -t, --trial-run::
# Don't actually update any files. Implies -v.
#
# Example
#
# update_package_archive --doc /path/to/my.pmdoc --component-id com.me.opt1 --component-root /path/to/opt1.xcarchive --component-id com.me.opt2 --component-root /path/to/opt2.xcarchive
require 'getoptlong'
require 'rexml/document'
require 'rexml/formatters/transitive'
require 'rdoc/usage'
opts = GetoptLong.new(
['--help', '-h', GetoptLong::NO_ARGUMENT],
['--component-id', '-i', GetoptLong::REQUIRED_ARGUMENT],
['--component-root', '-r', GetoptLong::REQUIRED_ARGUMENT],
['--doc', '-d', GetoptLong::REQUIRED_ARGUMENT],
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
['--trial-run', '-t', GetoptLong::NO_ARGUMENT]
)
@@Doc = nil
@@ComponentRefs = Hash.new
@@CurrentID = nil
@@ComponentPaths = Hash.new
@@Verbose = false
@@TrialRun = false
opts.each do |opt, arg|
case opt
when '--help'
RDoc::usage
return
when '--verbose'
@@Verbose = true
when '--trial-run'
@@TrialRun = true
@@Verbose = true
when '--doc'
@@Doc = arg
when '--component-id'
@@CurrentID = arg
when '--component-root'
if @@CurrentID.nil?
fail "Must specify a package ID before each package root."
return
end
path = arg.chomp('/')
fail "Root path '#{path}' does not refer to a .xcarchive file." unless File.basename(path) =~ /\.xcarchive$/
@@ComponentRefs[@@CurrentID] = path
@@CurrentID = nil
else
fail "Unrecognized option '#{opt}'."
end
end
if @@ComponentRefs.empty? or @@Doc.nil?
RDoc::usage
return
end
# Look for the document & scan its contents
Dir.glob("#{@@Doc}/*.xml") do |filename|
# skip index.xml, it's not interesting to us
next if File.basename(filename) == 'index.xml'
# skip the contents files at this point
next if File.basename(filename) =~ /contents.xml$/
# we now have a package info file, read it
File.open(filename) do |file|
xmldoc = REXML::Document.new(file)
# store component refs
xmldoc.elements.each("pkgref/contents/component") do |element|
unless element.attributes['path'] =~ /\.xcarchive\/Products/
puts "Skipping non-xcarchive component '#{element.attributes['id']}'" if @@Verbose
next
end
puts "Found xcarchive component '#{element.attributes['id']}'" if @@Verbose
@@ComponentPaths[element.attributes['id']] = filename
end
# store package refs (not all packages install something PackageMaker considers a 'component')
xmldoc.elements.each("pkgref/config") do |config|
identifier = config.get_elements('identifier').first.text
puts "#{identifier}"
unless config.get_elements('installFrom').first.text =~ /\.xcarchive\/Products/
puts "Skipping non-xcarchive package ref '#{identifier}" if @@Verbose
next
end
puts "Found xcarchive package '#{identifier}" if @@Verbose
@@ComponentPaths[identifier] = filename
end
end
end
# Now go through each requested package ID and update its root
@@ComponentRefs.each do |id, root|
filename = @@ComponentPaths[id]
fail "Package ID '#{id}' not found." if filename.nil?
xmldoc = nil
File.open(filename) do |file|
xmldoc = REXML::Document.new(file)
xmldoc.inspect
# might be a component that needs tweaking
xmldoc.elements.each("pkgref/contents/component[@id='#{id}']") do |element|
path = element.attributes['path']
puts "Old path = #{path}" if @@Verbose
# Replace everything up to '.xcarchive' with the new root...
path.gsub!(/^.*\.xcarchive/, root)
puts "New path for component '#{id}' is '#{path}'" if @@Verbose
# ...and put it back into the element
element.add_attribute('path', path) unless @@TrialRun
puts "#{element.inspect}" if @@Verbose
end
# might be a package-- check if that's the case
next unless xmldoc.get_elements("/pkgref/config/identifier").first.text == id rescue next
# it's a package id-- almost certain to have an 'installFrom'
puts "#{xmldoc.get_elements('pkgref/config/installFrom')}"
xmldoc.elements.each('pkgref/config/installFrom') do |element|
path = element.text
puts "Old path = #{path}" if @@Verbose
# Replace everything up to '.xcarchive' with the new root...
path.gsub!(/^.*\.xcarchive/, root)
puts "New path for package '#{id}' is '#{path}'" if @@Verbose
# ...and put it back into the element
unless @@TrialRun
element.text = path
end
end
# close the file
file.close
end
# open it truncated for writing
unless @@TrialRun
File.open(filename, "w+") do |outfile|
# Write out the modified document
formatter = REXML::Formatters::Transitive.new(2)
formatter.write(xmldoc, outfile)
outfile.close
end
end
# now look at the xxx-contents.xml file -- only one change in here
contents = File.join(File.dirname(filename), File.basename(filename, '.xml') + '-contents.xml')
File.open(contents) do |file|
xmldoc = REXML::Document.new(file)
# first 'f' element below root (pkg-contents) is the one with a path (pt) attribute
element = xmldoc.get_elements("/pkg-contents/f").first
next if element.nil?
path = element.attributes['pt']
puts "Old path = #{path}" if @@Verbose
if path =~ /\.xcarchive\/Products/
# Replace everything up to '.xcarchive' with the new root
path.gsub!(/^.*\.xcarchive/, root)
puts "New path in '#{contents}' is '#{path}'" if @@Verbose
# ...and put it back into the element
element.add_attribute('pt', path) unless @@TrialRun
end
# close the file
file.close
end
# open it trancated for writing
unless @@TrialRun
File.open(contents, "w+") do |outfile|
# Write out the modified document
formatter = REXML::Formatters::Transitive.new(2)
formatter.write(xmldoc, outfile)
outfile.close
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment