Duplicate iOS application target, rename it, add new icon and build app from command line (without using Xcode GUI) - ios

I would like to automate the process of adding a new target to my iOS application and write a script which would speed up the whole process. Is there any way to duplicate Xcode iOS application target, then rename it, add new icon specific for this target, change bundle id, app name etc. and finally build the app? I've tried using AppleScript to achieve this, but with no luck.
The workaround I am using now is to create one additional target (using Xcode GUI) and customize it during the build time (every time I want to build the app). The biggest downside of this approach is that targets' configurations are not saved anywhere and the parameters of the target have to be specified for every build of the app. This can lead to unrepeatable builds and other undebuggable issues. That's why I am looking for a method or a tool which would allow me to duplicate and configure new target from command line script

You can use this ruby script to duplicate any target, but you need to install github.com/CocoaPods/Xcodeproj first :
#!/usr/bin/env ruby
require 'rubygems'
require 'xcodeproj'
puts "Project path:"
proj_path = gets
puts "New target name:"
name = gets
puts "New target bundle identifer"
bundleIdentifier = gets
puts "Witch target to clone?"
srcTargetName = gets
name = name.chomp
bundleIdentifier = bundleIdentifier.chomp
proj_path = proj_path.chomp
srcTargetName = srcTargetName.chomp
proj = Xcodeproj::Project.open(proj_path)
src_target = proj.targets.find { |item| item.to_s == srcTargetName }
#proj_path = "/testingTest.xcodeproj"
# create target
target = proj.new_target(src_target.symbol_type, name, src_target.platform_name, src_target.deployment_target)
target.product_name = name
# create scheme
scheme = Xcodeproj::XCScheme.new
scheme.add_build_target(target)
scheme.set_launch_target(target)
scheme.save_as(proj_path, name, shared = true)
# copy build_configurations
target.build_configurations.map do |item|
item.build_settings.update(src_target.build_settings(item.name))
end
target.build_configurations.each do |config|
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = bundleIdentifier
config.build_settings['PRODUCT_NAME'] = "$(TARGET_NAME)"
end
# copy build_phases
phases = src_target.build_phases.reject { |x| x.instance_of? Xcodeproj::Project::Object::PBXShellScriptBuildPhase }.collect(&:class)
phases.each do |klass|
src = src_target.build_phases.find { |x| x.instance_of? klass }
dst = target.build_phases.find { |x| x.instance_of? klass }
unless dst
dst ||= proj.new(klass)
target.build_phases << dst
end
dst.files.map { |x| x.remove_from_project }
src.files.each do |f|
file_ref = proj.new(Xcodeproj::Project::Object::PBXFileReference)
file_ref.name = f.file_ref.name
file_ref.path = f.file_ref.path
file_ref.source_tree = f.file_ref.source_tree
file_ref.last_known_file_type = f.file_ref.last_known_file_type
#file_ref.fileEncoding = f.file_ref.fileEncoding
begin
file_ref.move(f.file_ref.parent)
rescue
end
build_file = proj.new(Xcodeproj::Project::Object::PBXBuildFile)
build_file.file_ref = f.file_ref
dst.files << build_file
end
end
# add files
#classes = proj.main_group.groups.find { |x| x.to_s == 'Group' }.groups.find { |x| x.name == 'Classes' }
#sources = target.build_phases.find { |x| x.instance_of? Xcodeproj::Project::Object::PBXSourcesBuildPhase }
#file_ref = classes.new_file('test.m')
#build_file = proj.new(Xcodeproj::Project::Object::PBXBuildFile)
#build_file.file_ref = file_ref
#sources.files << build_file
proj.save

Related

I want to change the file name after downloading the zip file

What I want to solve
I want to change the name of the file after downloading the zip. I was trying to figure out how to remove the directory name in the middle of this.
In this case, the intermediate directory name is "output".
I would like to know if there is a better way.
2_J000001719_1202テスト/output/WNS_UP用データ.txt
2_J000001719_1202テスト/output/Images/1051687701.jpg
↓
2_J000001719_1202テスト/WNS_UP用データ.txt
2_J000001719_1202テスト/Images/1051687701.jpg
Original Source Code
add_dir_name = "#{order.priority}_#{order.orderno}_#{company_name[0, 15]}"
Zip::File.open_buffer(obj) do |zip|
zip.each do |entry|
ext = File.extname(entry.name)
file_name = File.basename(entry.name)
dir_name = File.dirname(entry.name)
next if ext.blank? || file_name.count(".") > 1
dir = File.join(add_dir_name, dir_name)
FileUtils.mkpath(dir.to_s)
zip.extract(entry, dir + ext) {true}
file_name.force_encoding("UTF-8")
new_file_name = "#{dir}/#{file_name}"
new_file_name.force_encoding("UTF-8")
File.rename(dir + ext, new_file_name)
#input_dir << new_file_name
end
end
What I tried
I added this method to the source code, but it did not work.
new_dir = Dir.glob(dir_name+"/*").last
demdem = new_dir.split('/').last
new_file_name = dir.sub("/" + demdem, "")
Here's a generic function to move all files (recursively) from one dir to another:
orig_dir = "2_J000001719_1202テスト/output"
new_dir = "2_J000001719_1202テスト"
def move_all_files(orig_dir, new_dir)
Dir.glob("#{orig_dir}/**/*").each do |path|
File.move(path, path.gsub(orig_dir, new_dir))
end
end

ZipRuby - Zipping files with duplicate names (clean code)

I have altered my code to prevent an error when zipping a number of files that have the same file name. I want to change it so that duplicate file names are incremented.
For example if you had three files called file.txt, then I want them to be file.txt, file_2.txt and file_3.txt
def zip(file_name)
files_added = []
ZipRuby::Archive.open(file_name, ZipRuby::CREATE) do |archive|
file_associations.sort_by { |fa| fa.created_at }.each do |fa|
attachment_file_name = fa.attachment_file_name
binding.pry
if files_added.include?(attachment_file_name)
n = 2
ext = File.extname(attachment_file_name)
base = File.basename(attachment_file_name, ext)
new_name = "#{base}_#{n}#{ext}"
while files_added.include? new_name
n += 1
new_name = "#{base}_#{n}#{ext}"
end
attachment_file_name = new_name
end
archive.add_buffer(attachment_file_name, fa.attachment.read)
files_added << attachment_file_name
end
end
end
The code above works but I am new to Ruby/Rails and it doesn't feel very Rubyist. Can anyone give any pointers for a refactor.

Rspec Ruby on Rails Test File System in model

I have a model that has a method that looks through the filesystem starting at a particular location for files that match a particular regex. This is executed in an after_save callback. I'm not sure how to test this using Rspec and FactoryGirl. I'm not sure how to use something like FakeFS with this because the method is in the model, not the test or the controller. I specify the location to start in my FactoryGirl factory, so I could change that to a fake directory created by the test in a set up clause? I could mock the directory? I think there are probably several different ways I could do this, but which makes the most sense?
Thanks!
def ensure_files_up_to_date
files = find_assembly_files
add_files = check_add_assembly_files(files)
errors = add_assembly_files(add_files)
if errors.size > 0 then
return errors
end
update_files = check_update_assembly_files(files)
errors = update_assembly_files(update_files)
if errors.size > 0 then
return errors
else
return []
end
end
def find_assembly_files
start_dir = self.location
files = Hash.new
if ! File.directory? start_dir then
errors.add(:location, "Directory #{start_dir} does not exist on the system.")
abort("Directory #{start_dir} does not exist on the system for #{self.inspect}")
end
Find.find(start_dir) do |path|
filename = File.basename(path).split("/").last
FILE_TYPES.each { |filepart, filehash|
type = filehash["type"]
vendor = filehash["vendor"]
if filename.match(filepart) then
files[type] = Hash.new
files[type]["path"] = path
files[type]["vendor"] = vendor
end
}
end
return files
end
def check_add_assembly_files(files=self.find_assembly_files)
add = Hash.new
files.each do |file_type, file_hash|
# returns an array
file_path = file_hash["path"]
file_vendor = file_hash["vendor"]
filename = File.basename(file_path)
af = AssemblyFile.where(:name => filename)
if af.size == 0 then
add[file_path] = Hash.new
add[file_path]["type"] = file_type
add[file_path]["vendor"] = file_vendor
end
end
if add.size == 0 then
logger.error("check_add_assembly_files did not find any files to add")
return []
end
return add
end
def check_update_assembly_files(files=self.find_assembly_files)
update = Hash.new
files.each do |file_type, file_hash|
file_path = file_hash["path"]
file_vendor = file_hash["vendor"]
# returns an array
filename = File.basename(file_path)
af = AssemblyFile.find_by_name(filename)
if !af.nil? then
if af.location != file_path or af.file_type != file_type then
update[af.id] = Hash.new
update[af.id]['path'] = file_path
update[af.id]['type'] = file_type
update[af.id]['vendor'] = file_vendor
end
end
end
return update
end
def add_assembly_files(files=self.check_add_assembly_files)
if files.size == 0 then
logger.error("add_assembly_files didn't get any results from check_add_assembly_files")
return []
end
asm_file_errors = Array.new
files.each do |file_path, file_hash|
file_type = file_hash["type"]
file_vendor = file_hash["vendor"]
logger.debug "file type is #{file_type} and path is #{file_path}"
logger.debug FileType.find_by_type_name(file_type)
file_type_id = FileType.find_by_type_name(file_type).id
header = file_header(file_path, file_vendor)
if file_vendor == "TBA" then
check = check_tba_header(header, file_type, file_path)
software = header[TBA_SOFTWARE_PROGRAM]
software_version = header[TBA_SOFTWARE_VERSION]
elsif file_vendor == "TBB" then
check = check_tbb_header(header, file_type, file_path)
if file_type == "TBB-ANNOTATION" then
software = header[TBB_SOURCE]
else
software = "Unified"
end
software_version = "UNKNOWN"
end
if check == 0 then
logger.error("skipping file #{file_path} because it contains incorrect values for this filetype")
asm_file_errors.push("#{file_path} cannot be added to assembly because it contains incorrect values for this filetype")
next
end
if file_vendor == "TBA" then
xml = header.to_xml(:root => "assembly-file")
elsif file_vendor == "TBB" then
xml = header.to_xml
else
xml = ''
end
filename = File.basename(file_path)
if filename.match(/~$/) then
logger.error("Skipping a file with a tilda when adding assembly files. filename #{filename}")
next
end
assembly_file = AssemblyFile.new(
:assembly_id => self.id,
:file_type_id => file_type_id,
:name => filename,
:location => file_path,
:file_date => creation_time(file_path),
:software => software,
:software_version => software_version,
:current => 1,
:metadata => xml
)
assembly_file.save! # exclamation point forces it to raise an error if the save fails
end # end files.each
return asm_file_errors
end
Quick answer: you can stub out model methods like any others. Either stub a specific instance of a model, and then stub find or whatever to return that, or stub out any_instance to if you don't want to worry about which model is involved. Something like:
it "does something" do
foo = Foo.create! some_attributes
foo.should_receive(:some_method).and_return(whatever)
Foo.stub(:find).and_return(foo)
end
The real answer is that your code is too complicated to test effectively. Your models should not even know that a filesystem exists. That behavior should be encapsulated in other classes, which you can test independently. Your model's after_save can then just call a single method on that class, and testing whether or not that single method gets called will be a lot easier.
Your methods are also very difficult to test, because they are trying to do too much. All that conditional logic and external dependencies means you'll have to do a whole lot of mocking to get to the various bits you might want to test.
This is a big topic and a good answer is well beyond the scope of this answer. Start with the Wikipedia article on SOLID and read from there for some of the reasoning behind separating concerns into individual classes and using tiny, composed methods. To give you a ballpark idea, a method with more than one branch or more than 10 lines of code is too big; a class that is more than about 100 lines of code is too big.

How to use a static library created by a custom task?

I want use waf to trigger a makefile to build an other library. For this I created the following task:
def build(bld):
def run(self):
bld_dir = self.generator.bld.path.get_bld()
src_dir = self.inputs[0].parent
tgt = self.outputs[0]
tgt_dir = bld_dir.make_node(os.path.splitext(tgt.name)[0])
cmd = 'BUILDDIR="{tgt_dir}" make config gdb=1 debug=1 cc={cc} && BUILDDIR=" {tgt_dir}" make'.format(
tgt_dir = tgt_dir.abspath(),
cc = self.env.get_flat("CC"))
self.exec_command(cmd, cwd=src_dir.abspath())
return self.exec_command(['cp', lib.abspath(), tgt.abspath()],
cwd=tgt_dir.abspath())
bld(
rule = run,
source = "Makefile",
target = 'metis',
)
How can I tell waf that the task created a static library, so that I can use "metis" in a use keyword:
bld(
features = "cxx cxxprogramm"
source = "main.cpp",
target = 'main',
use = 'metis'
)
To finally solve the problem I created a on link_task, that basicly does notthing (similar to the fake_lib in ccroot.py):
from waflib.TaskGen import feature, after_method
from waflib.Tools.ccroot import stlink_task
class custom_stlib(stlink_task):
""" Dummy link task """
pass
#feature("custom_stlib")
def custom_lib(self):
self.env['custom_stlib_PATTERN'] = 'lib%s.a'
self.link_task = self.create_task('custom_stlib', [])
self.link_task.add_target(self.target)
def build(bld):
# ...
bld(
features = "cxx custom_stlib",
target = 'metis',
after = "metis_bld",
)

How can I turn the output of my iOS UIAutomation tests into JUnit style output for Jenkins?

I am using UIAutomation scripts to test my iOS application. I've managed to get the scripts running from the command line but now I need to convert the output (pass/fails) in a format that Jenkins can understand, ideally JUnit style.
Has anyone written any scripts to do this before I try & write one?
Many thanks
Maybe you can have a look at : https://github.com/shaune/jasmine-ios-acceptance-tests
Edit :
I've also avoided to use jasmine. To 'listen' start, pass and fail test, I've simply replaced UIALogger.logStart, UIALogger.logFail and UIALogger.logPass :
(function () {
// An anonymous function wrapper helps you keep oldSomeFunction private
var oldSomeFunction = UIALogger.logStart;
UIALogger.logStart = function () {
//UIALogger.logDebug("intercepted a logStart : " + arguments);
OKJunitLogger.reportTestSuiteStarting(arguments[0]);
oldSomeFunction.apply(this, arguments);
}
})();
I bealive you will find what you need here:
https://github.com/shinetech/jenkins-ios-example
What you're interest in is the script call "ocunit2junit.rb"
I used it for a project, it work very well.
#!/usr/bin/ruby
#
# ocunit2junit.rb was written by Christian Hedin <christian.hedin#jayway.com>
# Version: 0.1 - 30/01 2010
# Usage:
# xcodebuild -yoursettings | ocunit2junit.rb
# All output is just passed through to stdout so you don't miss a thing!
# JUnit style XML-report are put in the folder specified below.
#
# Known problems:
# * "Errors" are not cought, only "warnings".
# * It's not possible to click links to failed test in Hudson
# * It's not possible to browse the source code in Hudson
#
# Acknowledgement:
# Big thanks to Steen Lehmann for prettifying this script.
################################################################
# Edit these variables to match your system
#
#
# Where to put the XML-files from your unit tests
TEST_REPORTS_FOLDER = "test-reports"
#
#
# Don't edit below this line
################################################################
require 'time'
require 'FileUtils'
require 'socket'
class ReportParser
attr_reader :exit_code
def initialize(piped_input)
#piped_input = piped_input
#exit_code = 0
FileUtils.rm_rf(TEST_REPORTS_FOLDER)
FileUtils.mkdir(TEST_REPORTS_FOLDER)
parse_input
end
private
def parse_input
#piped_input.each do |piped_row|
puts piped_row
case piped_row
when /Test Suite '(\S+)'.*started at\s+(.*)/
t = Time.parse($2.to_s)
handle_start_test_suite(t)
when /Test Suite '(\S+)'.*finished at\s+(.*)./
t = Time.parse($2.to_s)
handle_end_test_suite($1,t)
when /Test Case '-\[\S+\s+(\S+)\]' started./
test_case = $1
when /Test Case '-\[\S+\s+(\S+)\]' passed \((.*) seconds\)/
test_case = $1
test_case_duration = $2.to_f
handle_test_passed(test_case,test_case_duration)
when /(.*): error: -\[(\S+) (\S+)\] : (.*)/
error_location = $1
test_suite = $2
test_case = $3
error_message = $4
handle_test_error(test_suite,test_case,error_message,error_location)
when /Test Case '-\[\S+ (\S+)\]' failed \((\S+) seconds\)/
test_case = $1
test_case_duration = $2.to_f
handle_test_failed(test_case,test_case_duration)
when /failed with exit code (\d+)/
#exit_code = $1.to_i
when
/BUILD FAILED/
#exit_code = -1;
end
end
end
def handle_start_test_suite(start_time)
#total_failed_test_cases = 0
#total_passed_test_cases = 0
#tests_results = Hash.new # test_case -> duration
#errors = Hash.new # test_case -> error_msg
#ended_current_test_suite = false
#cur_start_time = start_time
end
def handle_end_test_suite(test_name,end_time)
unless #ended_current_test_suite
current_file = File.open("#{TEST_REPORTS_FOLDER}/TEST-#{test_name}.xml", 'w')
host_name = string_to_xml Socket.gethostname
test_name = string_to_xml test_name
test_duration = (end_time - #cur_start_time).to_s
total_tests = #total_failed_test_cases + #total_passed_test_cases
suite_info = '<testsuite errors="0" failures="'+#total_failed_test_cases.to_s+'" hostname="'+host_name+'" name="'+test_name+'" tests="'+total_tests.to_s+'" time="'+test_duration.to_s+'" timestamp="'+end_time.to_s+'">'
current_file << "<?xml version='1.0' encoding='UTF-8' ?>\n"
current_file << suite_info
#tests_results.each do |t|
test_case = string_to_xml t[0]
duration = #tests_results[test_case]
current_file << "<testcase classname='#{test_name}' name='#{test_case}' time='#{duration.to_s}'"
unless #errors[test_case].nil?
# uh oh we got a failure
puts "tests_errors[0]"
puts #errors[test_case][0]
puts "tests_errors[1]"
puts #errors[test_case][1]
message = string_to_xml #errors[test_case][0].to_s
location = string_to_xml #errors[test_case][1].to_s
current_file << ">\n"
current_file << "<failure message='#{message}' type='Failure'>#{location}</failure>\n"
current_file << "</testcase>\n"
else
current_file << " />\n"
end
end
current_file << "</testsuite>\n"
current_file.close
#ended_current_test_suite = true
end
end
def string_to_xml(s)
s.gsub(/&/, '&').gsub(/'/, '"').gsub(/</, '<')
end
def handle_test_passed(test_case,test_case_duration)
#total_passed_test_cases += 1
#tests_results[test_case] = test_case_duration
end
def handle_test_error(test_suite,test_case,error_message,error_location)
# error_message.tr!('<','').tr!('>','')
#errors[test_case] = [ error_message, error_location ]
end
def handle_test_failed(test_case,test_case_duration)
#total_failed_test_cases +=1
#tests_results[test_case] = test_case_duration
end
end
#Main
#piped_input = File.open("tests_fail.txt") # for debugging this script
piped_input = ARGF.read
report = ReportParser.new(piped_input)
exit report.exit_code
Maybe you can use this.
And in Jenkins execute shell:
sh setup.sh sh runTests.sh ./sample/alltests.js "/Users/komejun/Library/Application Support/iPhone Simulator/5.0/Applications/1622F505-8C07-47E0-B0F0-3A125A88B329/Recipes.app/"
And the report will be auto-created in ./ynmsk-report/test.xml
You can use the tuneupjs library. The library provides test runner that generates a xml report (jUnit style). It works with Xcode 6. For the xml report just provide -x param. Check it here: http://www.tuneupjs.org/running.html

Resources