Skip to content

Dry run #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ DerivedData

# Spec
spec/test_dummy/
spec/test_dry_run_dummy/
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Synx supports the following options:
--no-sort-by-name disable sorting groups by name
--quiet, -q silence all output
--exclusion, -e EXCLUSION ignore an Xcode group while syncing
--warn-type, -w warning|error show warnings or errors only without modifying the project structure
```

For example, OCMock could have been organized using this command:
Expand All @@ -53,6 +54,16 @@ For example, OCMock could have been organized using this command:

if they had wanted not to sync the `/OCMock/Core Mocks` and `/OCMockTests` groups, and also remove (`-p`) any image/source files found by synx that weren't referenced by any groups in Xcode.

### Integration with Xcode

Synx can be integrated with Xcode to check for synchronization issues after every build. In order to do that open your project settings, go to **Build Phases** tab, click **+** sign and select **New Run Script Phase**. Paste the following content into Run script window:

synx -w warning ${PROJECT_FILE_PATH}

You can also make Synx fail the build in case it finds any issues with a following script:

synx -w error ${PROJECT_FILE_PATH}

## Contributing

We'd love to see your ideas for improving this library! The best way to contribute is by submitting a pull request. We'll do our best to respond to your patch as soon as possible. You can also submit a [new Github issue](https://github.com/venmo/synx/issues/new) if you find bugs or have questions. :octocat:
Expand Down
4 changes: 3 additions & 1 deletion bin/synx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Clamp do
option "--no-sort-by-name", :flag, "disable sorting groups by name"
option ["--quiet", "-q"], :flag, "silence all output"
option ["--exclusion", "-e"], "EXCLUSION", "ignore an Xcode group while syncing", :multivalued => true
option ["--warn-type", "-w"], "warning|error", "show warnings or errors only without modifying the project structure", :attribute_name => :warn_type
option ["--version", "-v"], :flag, "shows synx version" do
puts "Synx #{Synx::VERSION}"
exit(0)
Expand All @@ -22,7 +23,8 @@ Clamp do
puts "You cannot run Synx as root.".red
else
project = Synx::Project.open(xcodeproj_path)
project.sync(:prune => prune?, :quiet => quiet?, :no_color => no_color?, :no_default_exclusions => no_default_exclusions?, :no_sort_by_name => no_sort_by_name?, :group_exclusions => exclusion_list)
project.sync(:prune => prune?, :quiet => quiet?, :no_color => no_color?, :no_default_exclusions => no_default_exclusions?, :no_sort_by_name => no_sort_by_name?, :group_exclusions => exclusion_list, :warn => warn_type)
exit(project.exit_code)
end
end

Expand Down
2 changes: 2 additions & 0 deletions lib/synx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
require "synx/version"
require "synx/project"
require "synx/tabber"
require "synx/file_utils"
require "synx/issue_registry"

require "colorize"
13 changes: 13 additions & 0 deletions lib/synx/file_utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Synx
class BlankFileUtils

class << self
def rm_rf(list)
end

def mv(src, dest)
end
end

end
end
38 changes: 38 additions & 0 deletions lib/synx/issue_registry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module Synx
class IssueRegistry

attr_accessor :output

def initialize
@issues = {}
@output = $stderr
end

def add_issue(reason, basename, type = nil)
existing_issue = @issues.each_pair.select { |(name, (reason, type))| name == basename.to_s and type == :not_synchronized }

if existing_issue.empty?
@issues[basename.to_s] = [reason, type]
end
end

def issues
@issues.values.map(&:first).sort
end

def issues_for_basename(partial_basename)
@issues.each_pair.select { |(basename, _)| basename.include? partial_basename.to_s }.map { |_, issue| issue.first }.sort
end

def issues_count
@issues.size
end

def print(type)
issues.each do |issue|
output.puts [type, issue].join(': ')
end
end

end
end
48 changes: 41 additions & 7 deletions lib/synx/project.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
require 'fileutils'
require 'xcodeproj'

module Synx
Expand All @@ -10,7 +9,7 @@ class Project < Xcodeproj::Project
DEFAULT_EXCLUSIONS = %W(/Libraries /Frameworks /Products /Pods)
private_constant :DEFAULT_EXCLUSIONS

attr_accessor :delayed_groups_set_path, :group_exclusions, :prune, :sort_by_name
attr_accessor :delayed_groups_set_path, :group_exclusions, :prune, :sort_by_name, :warn_type, :file_utils

def sync(options={})
set_options(options)
Expand All @@ -24,7 +23,8 @@ def sync(options={})
main_group.sort_by_name if self.sort_by_name
transplant_work_project
Synx::Tabber.decrease
save
print_dry_run_issues
save unless warn_type
end

def presync_check
Expand All @@ -50,16 +50,27 @@ def set_options(options)

self.group_exclusions |= options[:group_exclusions] if options[:group_exclusions]
self.sort_by_name = !options[:no_sort_by_name]
self.warn_type = validated_warn_type(options)

Synx::Tabber.options = options
sync_issues_repository.output = options[:output] unless options[:output].nil?
end
private :set_options

def validated_warn_type(options)
if options[:warn].to_s == 'warning' or options[:warn].to_s == 'error'
options[:warn]
elsif options[:warn]
Synx::Tabber.puts "Unknown warn-type: #{options[:warn]}".red
abort
end
end

def transplant_work_project
# Move the synced entries over
Dir.glob(work_root_pathname + "*").each do |path|
FileUtils.rm_rf(work_pathname_to_pathname(Pathname(path)))
FileUtils.mv(path, root_pathname.to_s)
file_utils.rm_rf(work_pathname_to_pathname(Pathname(path)))
file_utils.mv(path, root_pathname.to_s)
end
end
private :transplant_work_project
Expand All @@ -74,7 +85,7 @@ def work_root_pathname
else
@work_root_pathname = Pathname(File.join(SYNXRONIZE_DIR, root_pathname.basename.to_s))
# Clean up any previous synx and start fresh
FileUtils.rm_rf(@work_root_pathname.to_s) if @work_root_pathname.exist?
file_utils.rm_rf(@work_root_pathname.to_s) if @work_root_pathname.exist?
@work_root_pathname.mkpath
@work_root_pathname
end
Expand Down Expand Up @@ -121,6 +132,29 @@ def has_object_for_pathname?(pathname)
end
end
end

def file_utils
@file_utils ||= (warn_type.nil? ? FileUtils : Synx::BlankFileUtils)
end

def sync_issues_repository
@sync_issues ||= Synx::IssueRegistry.new
end

def print_dry_run_issues
sync_issues_repository.print(warn_type.to_s) if warn_type
end

def scanned_files
@scanned_files ||= []
end

def exit_code
if warn_type.to_s == 'error' and sync_issues_repository.issues_count > 0
-1
else
0
end
end
end
end

6 changes: 5 additions & 1 deletion lib/synx/tabber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ def options

def puts(str="")
str = str.uncolorize if options[:no_color]
output.puts (a_single_tab * @tabbing) + str.to_s unless options[:quiet]
output.puts (a_single_tab * @tabbing) + str.to_s unless quiet
end

def quiet
options[:quiet] or options[:warn]
end

def a_single_tab
Expand Down
18 changes: 18 additions & 0 deletions lib/synx/xcodeproj_ext/project/object/abstract_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,28 @@ def ensure_internal_consistency(group)
end
end

def track_sync_issues
current_relative_path = real_path.relative_path_from(project.root_pathname).to_s
synced_relative_path = work_pathname.relative_path_from(project.work_root_pathname).to_s

if current_relative_path != synced_relative_path
issue = "#{readable_type} #{basename} is not synchronized with file system (current path: #{current_relative_path}, desired path: #{synced_relative_path})."
project.sync_issues_repository.add_issue(issue, basename, :not_synchronized)
end
end

def readable_type
isa.sub('PBX', '').split(/(?=[A-Z])/).join(' ').capitalize
end

def sync(group)
raise NotImplementedError
end

def file_utils
project.file_utils
end

end
end
end
Expand Down
4 changes: 3 additions & 1 deletion lib/synx/xcodeproj_ext/project/object/pbx_file_reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ class PBXFileReference
def sync(group)
if should_sync?
if should_move?
FileUtils.mv(real_path.to_s, work_pathname.to_s)
track_sync_issues
project.scanned_files << real_path.to_s
file_utils.mv(real_path.to_s, work_pathname.to_s)
# TODO: move out to abstract_object
self.source_tree = "<group>"
self.path = work_pathname.relative_path_from(parent.work_pathname).to_s
Expand Down
22 changes: 20 additions & 2 deletions lib/synx/xcodeproj_ext/project/object/pbx_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def sync(group)
if excluded_from_sync?
Synx::Tabber.puts "#{basename}/ (excluded)".yellow
else
track_sync_issues
Synx::Tabber.puts "#{basename}/".green
Synx::Tabber.increase

Expand Down Expand Up @@ -35,6 +36,8 @@ def excluded_from_sync?
end

def sort_by_name
before_sorting = children.to_a

children.sort! do |x, y|
if x.isa == 'PBXGroup' && !(y.isa == 'PBXGroup')
-1
Expand All @@ -46,6 +49,19 @@ def sort_by_name
0
end
end

if before_sorting != children.to_a
issue = "Group #{pretty_hierarchy_path} is not sorted alphabetically."
project.sync_issues_repository.add_issue(issue, real_path, :not_sorted)
end
end

def pretty_hierarchy_path
if hierarchy_path.to_s.empty?
'/'
else
hierarchy_path.to_s
end
end

def move_entries_not_in_xcodeproj
Expand All @@ -55,7 +71,7 @@ def move_entries_not_in_xcodeproj
Synx::Tabber.puts "#{basename}/".green
Synx::Tabber.increase
real_path.children.each do |entry_pathname|
unless project.has_object_for_pathname?(entry_pathname)
unless project.scanned_files.include?(entry_pathname.to_s) or project.has_object_for_pathname?(entry_pathname)
handle_unused_entry(entry_pathname)
end
end
Expand Down Expand Up @@ -97,12 +113,14 @@ def handle_unused_file(file_pathname)
is_file_to_prune = prune_file_extensions.include?(file_pathname.extname.downcase)

if is_file_to_prune && project.prune
issue = "Unused file not referenced by Xcode project: #{file_pathname.basename}."
project.sync_issues_repository.add_issue(issue, file_pathname.basename, :unused)
Synx::Tabber.puts "#{file_pathname.basename} (removed source/image file that is not referenced by the Xcode project)".red
return
elsif !project.prune || !is_file_to_prune
destination = project.pathname_to_work_pathname(file_pathname.parent.realpath)
destination.mkpath
FileUtils.mv(file_pathname.realpath, destination)
file_utils.mv(file_pathname.realpath, destination)
if is_file_to_prune
Synx::Tabber.puts "#{file_pathname.basename} (source/image file that is not referenced by the Xcode project)".yellow
else
Expand Down
8 changes: 8 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
require 'pry'

DUMMY_SYNX_PATH = File.expand_path('../dummy', __FILE__)
DUMMY_SYNX_PBXPROJ_PATH = File.join(DUMMY_SYNX_PATH, 'dummy.xcodeproj/project.pbxproj')
DUMMY_SYNX_TEST_PATH = File.expand_path('../test_dummy', __FILE__)
DUMMY_SYNX_TEST_PROJECT_PATH = File.join(DUMMY_SYNX_TEST_PATH, 'dummy.xcodeproj')
FileUtils.rm_rf(DUMMY_SYNX_TEST_PATH)
FileUtils.cp_r(DUMMY_SYNX_PATH, DUMMY_SYNX_TEST_PATH)
DUMMY_SYNX_TEST_PROJECT = Synx::Project.open(DUMMY_SYNX_TEST_PROJECT_PATH)

DUMMY_SYNX_DRY_RUN_TEST_PATH = File.expand_path('../test_dry_run_dummy', __FILE__)
DUMMY_SYNX_DRY_RUN_TEST_PROJECT_PATH = File.join(DUMMY_SYNX_DRY_RUN_TEST_PATH, 'dummy.xcodeproj')
DUMMY_SYNX_DRY_RUN_TEST_PBXPROJ_PATH = File.join(DUMMY_SYNX_DRY_RUN_TEST_PROJECT_PATH, 'project.pbxproj')
FileUtils.rm_rf(DUMMY_SYNX_DRY_RUN_TEST_PATH)
FileUtils.cp_r(DUMMY_SYNX_PATH, DUMMY_SYNX_DRY_RUN_TEST_PATH)
DUMMY_SYNX_DRY_RUN_TEST_PROJECT = Synx::Project.open(DUMMY_SYNX_DRY_RUN_TEST_PROJECT_PATH)

RSpec.configure do |config|
end
Loading