Created
January 6, 2013 16:52
-
-
Save AlanQuatermain/4468499 to your computer and use it in GitHub Desktop.
A simple script which will change the root-paths of components in a PackageMaker document at the base of a new Xcode Archive, all without opening PackageMaker itself. Very useful for projects where you have a lot of separate components (and thus separate .xcarchive folders), which can take a while to update from the PackageMaker application afte…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# A copy of the GNU General Public License can be found at | |
# <http://www.gnu.org/licenses/gpl-3.0.html>. | |
require 'optparse' | |
require 'rexml/document' | |
require 'rexml/formatters/transitive' | |
@@Doc = nil | |
@@ComponentRefs = Hash.new | |
@@CurrentID = nil | |
@@ComponentPaths = Hash.new | |
@@Verbose = false | |
@@TrialRun = false | |
OptionParser.new do |opts| | |
opts.banner = "Usage: update_package_archive (-c <component ID> -r <.../app.xcarchive>)... -d <x.pmdoc>" | |
opts.separator "" | |
opts.separator "Modifies an existing (and fully-set-up) PackageMaker document to point a package component" | |
opts.separator "at a new Xcode Archive root directory." | |
opts.separator "" | |
opts.separator "This script is designed to work only with components based on Xcode 4 archives (folders ending" | |
opts.separator "in .xcarchive) containing a 'Products' subfolder which is used as the component's root folder." | |
opts.separator "" | |
opts.separator "Each time a new build is made, a new .xcarchive folder is created. It is a fairly involved" | |
opts.separator "process to edit an existing PackageMaker document to point its root at the new archive and to" | |
opts.separator "set recommended permissions on everything within that folder. Internally however the document" | |
opts.separator "contains only relative path information along with (in a few different places) the path to the" | |
opts.separator "component root. This script will find the references to the package component designated by the" | |
opts.separator "--component-id parameter and replace the portion of that path ending in a .xcarchive item with a" | |
opts.separator "new .xcarchive path specified using the --component-root parameter. It can operate on multiple" | |
opts.separator "sub-packages/components at a time, meaning that you can supply multiple --component-id and" | |
opts.separator "--component-root parameters, however they *must* be paired, with each id preceding a root." | |
opts.separator "" | |
opts.separator "Options:" | |
opts.on_tail( "-h", "--help", "Show this message.", :NONE) do |arg| | |
puts opts | |
exit | |
end | |
opts.on_tail("-v", "--verbose", Boolean, "Print out scanning/replacement information while running.", :NONE) do |arg| | |
@@Verbose = arg | |
end | |
opts.on_tail("-t", "--trial-run", Boolean, "Don't update any files. Implies -v.", :NONE) do |arg| | |
@@DryRun = arg | |
@@Verbose = arg | |
end | |
opts.on("-c", "--component-id", | |
"The identifier (i.e. com.mycompany.mycomponent) specifying the component to act upon.", :REQUIRED) do |arg| | |
@@CurrentID = arg | |
end | |
opts.on("-r", "--component-root", | |
"The new .xcarchive folder to use as the component's root. Actually uses the 'Products' folder", | |
"within the .xcarchive bundle.", :REQUIRED) do |arg| | |
fail "Must specify a package ID before each package root." if @@CurrentID.nil? | |
path = arg.chomp('/') | |
fail "Root path '#{path}' does not refer to a .xcarchive file." unless File.basename(path) =~ /\.xcarchive$/ | |
@@ComponentRefs[@@CurrentID] = path | |
@@CurrentID = nil | |
end | |
opts.on("-d", "--doc", "The PackageMaker document upon which to act.", :REQUIRED) do |arg| | |
@@Doc = arg | |
end | |
end.parse! | |
if @@ComponentRefs.empty? or @@Doc.nil? | |
fail "#{opts}" | |
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