Login

Newsflash

command + option + shift will toggle the editor window in Xcode 

Automated Xcode Backups

Update!
  • Added timestamp in file name
  • Added backup every x builds instead of backing up every build
  • Added zip folder and upload to ftp
  • Added backup only files that have changes

Here is a script to backup your Xcode project during each build phase. This is pretty cool because it will save the backup in a versioned project folder inside a folder having the same name as the Xcode project.

This is not a replacement for a computer backup system, Subversion or GIT. This is an additional measure that ensures you will have an incremental backup of your project!

See this post for help setting up Subversion.

ChangeLog:
ChangeLog:
  Added:
  Monday January 19, 2009 
  Monday January 19, 2009 
    Added:
      - Zip files
      - Upload to ftp
      - Folder named with timestamp
      - Backup only files that have changed since last build
      - Now have the ability to only backup every x number of builds
          count starts with 1
          to backup every 5th time you would set this value to 6

	The build log displays info to let you know 
	when your next backup will occur
	----------------------------------------------
	BACKING UP PROJECT MESSAGE
	Next backup in 2 builds.
	----------------------------------------------

	And when a backup was performed.
	----------------------------------------------
	BACKING UP PROJECT MESSAGE
	Creating backup!
	----------------------------------------------

  Fixed:
  Monday January 19, 2009
    Folder name was inserting an unwanted new line character

TODO
  Backup entire Project Folder every x builds
Using the Script

The script uses a config.yaml file for settings. It will create this automatically.

Config.yaml Explained
---
types_to_backup:         [ '.m', '.c', '.h', '.applescript', '.scpt', '.xcodeproj', '.xib']
excluded_folders:        ['build']
backup_dir:              '#{ENV['HOME']}/Desktop/XcodeBackups'
backup_interval:         '11'
time_format:             '24'
skip_unchanged_files:    'YES'
ftp_upload:              'NO'
ftp_server:              'ftp.site_name.net'
ftp_user:                'user_name'
ftp_pass:                'password'
ftp_upload_dir:          'public_html/upload_directory_name/'
types_to_backup

Are only the files you WANT to backup. Only the specified types are backed up. Notice that each type includes the '.'

excluded_folders

Are exactly that. They will not be backed up.

backup_dir

This is where you want your backups to go. The default settings will create a folder on your desktop called 'XcodeBackups'. If you let the script create the config.yaml file the "ENV['HOME']" is automatically expanded. If not you need to hard code this path yourself.

backup_interval

This setting will determine how many builds are performed between each backup.
It is 1 based so add one to the number.
To backup every 10 builds this setting will be 11.
This information is stored in ~/Library/Preferences/com.allanCraig.BackupXcode.plist
A new entry is added for each application in which you use this script.

time_format

Set to '24' will yield 24 hour format.
Set to anything else will yield 12 hour format.

skip_unchanged_files

Setting this to 'YES' will only backup files with a modification date greater than the last backup date. This significantly reduces disk space usage.

ftp_upload

Leave this set to 'NO' unless you know what you are doing. :)

FTP Section

Post a comment if you are unclear how to use this feature.

Setup

Make the necessary changes to the DEFAULT_CONFIG settings in the script. You can always change these later by opening the config.yaml file located in '~/Library/Application Support/BackupXcode/config.yaml'

create a new 'Run Script' build phase in your project. Run Script Build Double-click to open Run Script

Set the shell to '/usr/bin/ruby'
Paste the script into the window.

Run Script Window

That should be it. If you have any problems just leave a comment.

Finished Result

Here is the result after building a few times on a project.
The date is "Year.Month.Day_Hours.Minutes.Seconds"

Folder Structure


An Easier Way to Add to Your Projects

Instead of adding this script to each of your projects, save it as a command line utility. I have a nifty little script for this. Then you only have to add one line of code to any project you wish to automagically backup!

One Line to add Code Last One, promise! Easy Install AppleScript

Instructions

  1. Download the Ruby code, unzip and place on your desktop
  2. Click here to open the script below in your default script editor!
  3. Open the Xcode project in which you wish to install this Run Script Phase.
  4. Click Run!
-- date:	Sunday January 25, 2009 
-- author: 	Craig Williams
-- desc: 	Creates a Run Script Build Phase in Xcode

(*
	Instructions:
		Script assumes you have made the Ruby file an executable.

		Change theScript path to match the location of the Ruby executable
		The path must be HFS not POSIX style.
*)

set theScript to read file ((path to desktop as Unicode text) & "BackupXcode")

tell application "Xcode"
		try
			set openProjects to every project
		on error
			display dialog "No Projects Open"
			return
		end try

		set projectList to {}
		if (count of openProjects) is greater than 1 then
			repeat with i from 1 to count of openProjects
				set thisProj to item i of openProjects
				set end of projectList to (name of thisProj)
			end repeat
			set chosenProj to choose from list projectList ¬
				with title ¬
				"Add Run Script Build Phase" with prompt "Choose Project"
			if chosenProj is false then return
			set theProject to item 1 of chosenProj
		else
			set theProject to name of item 1 of openProjects
		end if

		tell target 1 of project theProject
			set newRunScript to make new run script phase

			tell newRunScript
				set shell path to "/bin/sh"
				set shell script to theScript
				set name to "Automate Backup Build"
				set run only when installing to false
				set show environment variables to true
			end tell
		end tell
end tell
#!/usr/bin/env ruby -w

#################################################################################
#                                                                               #
#     BackupXcode.rb                                                            #
#                                                                               #
#     author:   Craig Williams                                                  #
#     created:  2009-01-17                                                      #
#                                                                               #
#################################################################################
#                                                                               #
#     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.                              #
#                                                                               #
#     You should have received a copy of the GNU General Public License         #
#     along with this program.  If not, see <http://www.gnu.org/licenses/>.     #
#                                                                               #
#################################################################################

=begin
  BackupXcodeProjectFiles is for backing up specific types of files
  like .m, .h, .applescript, .xcodeproj, .xib in a project.
  It is not designed to backup all the graphics and other misc
  resources that do not change on a normal basis.
=end

=begin
ChangeLog:
  Added:
  Monday January 19, 2009 
    Added:
      - Zip files
      - Upload to ftp
      - Folder named with timestamp
      - Backup only files that have changed since last build
      - Now have the ability to only backup every x number of builds
          count starts with 1
          to backup every 5th time you would set this value to 6

	The build log displays info to let you know 
	when your next backup will occur
	----------------------------------------------
	BACKING UP PROJECT MESSAGE
	Next backup in 2 builds.
	----------------------------------------------

	And when a backup was performed.
	----------------------------------------------
	BACKING UP PROJECT MESSAGE
	Creating backup!
	----------------------------------------------

  Fixed:
  Monday January 19, 2009
    Folder name was inserting an unwanted new line character

TODO
  Backup entire Project Folder every x builds
=end

# Code from O'Reilly's 'Ruby Cookbook' page 220
# Versions file or folder names
# Eg. MyProj, MyProj.001, MyProj.002
class File
  def File.versioned_name(base, first_suffix='.0001')
    suffix = nil
    folder_name = base
    while File.exists?(folder_name)
      suffix = (suffix ? suffix.succ : first_suffix)
      folder_name = base + suffix
    end
    return folder_name
  end
end


class BackupXcodeProjectFiles
  require 'yaml'
  require 'fileutils'
  require 'find'
  require 'net/ftp'
  require 'time'

  CONFIG_YAML_PATH  = "#{ENV['HOME']}/Library/Application Support/BackupXcode"
  MESSAGE_HEADER    = 'BACKING UP PROJECT MESSAGE'
  DEFAULT_CONFIG    = "---
  types_to_backup:        [ '.m', '.c', '.h', '.applescript', '.scpt', '.xcodeproj', '.xib']
  excluded_folders:       ['build']
  backup_dir:             '#{ENV['HOME']}/Desktop/XcodeBackups'
  backup_interval:          3
  time_format:             '24'
  skip_unchanged_files:    'YES'
  ftp_upload:              'NO'
  ftp_server:              'ftp.web_site.net'
  ftp_user:                 'user_name'
  ftp_pass:                 'user_password'
  ftp_upload_dir:           'path/to/upload/'"

  def initialize
    load_config_file
    instantiate_variables
  end

  def main_worker_bee
    defaults_read

    if backup_on_x_number_of_builds
      log_message('Creating backup!')
      create_parent_folder
      create_archive_folder
      create_versioned_folder
      copy_files
      zipped_file = create_zip_remove_folder
      upload_to_ftp(zipped_file) if @ftp_upload == 'YES'

      #reset default Count
      defaults_write(1)
    end
  end

  def instantiate_variables
    @proj_dir               = File.dirname(File.dirname(ENV['BUILD_DIR']))
    @proj_name              = ENV['PROJECT_NAME']
    @backup_dir             = @config['backup_dir']
    @types_to_backup        = @config['types_to_backup']
    @excluded_folders       = @config['excluded_folders']
    @backup_interval        = @config['backup_interval']
    @time_format            = @config['time_format']
    @skip_unchanged_files   = @config['skip_unchanged_files']

    # ftp
    @ftp_server             = @config['ftp_server']
    @ftp_user               = @config['ftp_user']
    @ftp_pass               = @config['ftp_pass']
    @ftp_upload_dir         = @config['ftp_upload_dir']
    @ftp_upload             = @config['ftp_upload']
  end

  def backup_on_x_number_of_builds
    # To backup during every build set backup_interval: in config.yaml to 'ALL'
    return true if @backup_interval == 'ALL'

    current_backup_count = defaults_read_count
    if current_backup_count.to_i >= @backup_interval.to_i
      return true
    else
      new_build_count = (current_backup_count.to_i + 1)
      defaults_write(new_build_count)
      build_log_statement(new_build_count)
      return false
    end
  end

  def build_log_statement(new_build_count)
    next_backup_count = (@backup_interval.to_i - new_build_count)
    msg = next_backup_count == 0 ? "Backup on next build!." : "Next backup in #{next_backup_count + 1} builds."
    log_message(msg)
  end

  def create_parent_folder
    begin
      FileUtils.mkdir(@backup_dir) if !File.exists?(@backup_dir)
    rescue
      log_message("ERROR in create_parent_folder\nerror => #{$!}")
    end
  end

  def create_archive_folder
    begin
      @archive_folder_name = "#{@backup_dir}/#{@proj_name}"
      FileUtils.mkdir(@archive_folder_name) if !File.exists?(@archive_folder_name)
    rescue
      log_message("ERROR in create_archive_folder\nerror => #{$!}")
    end
  end

  def create_versioned_folder
    begin
      time_stamp = (@time_format == '24' ? `date +'%Y.%m.%d_%H.%M.%S'` : `date +'%Y.%m.%d_%I.%M.%S'`)
      folder_name = "#{time_stamp.chomp}_#{@proj_name}"
      @archive_versioned_name = File.versioned_name("#{@archive_folder_name}/#{folder_name}")
      FileUtils.mkdir(@archive_versioned_name)
    rescue
      log_message("ERROR in create_versioned_folder\nerror => #{$!}")
    end
  end

  def copy_files
    defaults_time = defaults_read_time

    Find.find(@proj_dir) do |path|
      Find.prune if @excluded_folders.include?(File.basename(path))
      next if !@types_to_backup.include?(File.extname(path))

      # We are skipping files that have not changed since last backup
      if @skip_unchanged_files == 'YES'
        file_time = File.mtime(path).to_i
        next if file_time < defaults_time
      end

      begin
        FileUtils.cp_r(path, @archive_versioned_name, :remove_destination => true)
      rescue
        # System will announce if there is an error backing up a file
        `/usr/bin/osascript -e 'say "There was an error backing up files"'`
        log_message("ERROR in copy_files\nerror => #{$!}")
      end
    end
  end

  # If no config.yaml file found create one
  def load_config_file
    @config_path = "#{CONFIG_YAML_PATH}/config.yaml"
    if !File.exists?(@config_path)
      create_config_file
      log_message("No 'config.yaml' file found. \nCreating one in location ~/Library/Application Support/BackupXcode/config.yaml\nWith default settings.")
    end
    @config = YAML.load_file(@config_path)
  end

  def log_message(msg)
    puts "\n\n----------------------------------------------"
    puts MESSAGE_HEADER
    puts msg
    puts "----------------------------------------------\n\n"
  end

  def create_config_file
    begin
      FileUtils.mkdir(CONFIG_YAML_PATH) if !File.exists?(CONFIG_YAML_PATH)
      File.open(@config_path, 'w') { |f| f.puts DEFAULT_CONFIG }
    rescue
      log_message("ERROR in create_config_file\nerror => #{$!}")
    end
  end

  def defaults_write(new_build_count)
    `defaults write com.allanCraig.BackupXcode '#{@proj_name}' -dict Time #{Time.now.to_i} Count '#{new_build_count}'`
  end

  def defaults_read
    @defaults = `defaults read com.allanCraig.BackupXcode '#{@proj_name}'`
    if @defaults == ''
      log_message("There was an error reading defaults - maybe the file does not exist\nCreate one and return 1")
      defaults_write(1)
      log_message("Writing defaults because defaults were nil")
      @defaults = `defaults read com.allanCraig.BackupXcode '#{@proj_name}'`
      return 1
    end
  end

  def defaults_read_count
    defaults = @defaults.scan(/.+Count\s=\s(\d+).+/)
    return defaults[0][0].to_i
  end

  def defaults_read_time
    defaults = @defaults.scan(/.+Time\s=\s(\d+).+/)
    return defaults[0][0].to_i
  end

  def create_zip_remove_folder
    zipped_file = "#{@archive_versioned_name}.zip"
    `zip -r '#{zipped_file}' '#{@archive_versioned_name}'`
    FileUtils.rm_r(@archive_versioned_name)
    return zipped_file
  end

  def upload_to_ftp(zipped_file)
    ftp = Net::FTP.new(@ftp_server, @ftp_user, @ftp_pass)
    begin
      ftp.chdir(@ftp_upload_dir)
      ftp.put(zipped_file)
      ftp.close
      log_message("Just finished uploading to ftp!")
    rescue
      log_message("There was an error uploading file\nError: #{$!}")
      ftp.close
    end
  end

end


if __FILE__ == $0
  backup_files = BackupXcodeProjectFiles.new
  backup_files.main_worker_bee
  backup_files.log_message('Looks like all went well!')
end

Add comment


Security code
Refresh

Product Categories