Class: Refinement::Analyzer

Inherits:
Object
  • Object
show all
Defined in:
lib/refinement/analyzer.rb

Overview

Analyzes changes in a repository and determines how those changes impact the targets in Xcode projects in the workspace.

Instance Method Summary collapse

Constructor Details

#initialize(changeset:, workspace_path:, projects: nil, augmenting_paths_yaml_files:, augmenting_paths_by_target: nil) ⇒ Analyzer

Initializes an analyzer with a changeset, projects, and augmenting paths.

Parameters:

  • changeset (Changeset)
  • workspace_path (Pathname)

    path to a root workspace or project, must be ‘nil` if `projects` are specified explicitly

  • projects (Array<Xcodeproj::Project>) (defaults to: nil)

    projects to find targets in, must not be specified if ‘workspace_path` is not `nil`

  • augmenting_paths_yaml_files (Array<Pathname>)

    paths to YAML files that provide augmenting paths by target, must be ‘nil` if `augmenting_paths_by_target` are specified explicitly

  • augmenting_paths_by_target (Hash<String, Array>) (defaults to: nil)

    arrays of hashes keyed by target name (or ‘*’ for all targets) describing paths or globs that each target should be considered to be using, must not be specified if ‘augmenting_paths_yaml_files` is not `nil`

Raises:

  • (ArgumentError)

    when conflicting arguments are given



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/refinement/analyzer.rb', line 25

def initialize(changeset:, workspace_path:, projects: nil,
               augmenting_paths_yaml_files:, augmenting_paths_by_target: nil)

  @changeset = changeset

  raise ArgumentError, 'Can only specify one of workspace_path and projects' if workspace_path && projects

  @workspace_path = workspace_path
  @projects = projects

  raise ArgumentError, 'Can only specify one of augmenting_paths_yaml_files and augmenting_paths_by_target' if augmenting_paths_yaml_files && augmenting_paths_by_target

  @augmenting_paths_yaml_files = augmenting_paths_yaml_files
  @augmenting_paths_by_target = augmenting_paths_by_target
end

Instance Method Details

#annotate_targets!Array<AnnotatedTarget>

Returns targets from the projects annotated with their changes, based upon the changeset.

Returns:

  • (Array<AnnotatedTarget>)

    targets from the projects annotated with their changes, based upon the changeset



43
44
45
# File 'lib/refinement/analyzer.rb', line 43

def annotate_targets!
  @annotate_targets ||= annotated_targets
end

#filtered_scheme(scheme_path:, change_level: :full_transitive, filter_when_scheme_has_changed: false, log_changes: false, filter_scheme_for_build_action:) ⇒ Xcodeproj::XCScheme

Returns a scheme whose unchanged targets have been removed.

Parameters:

  • scheme_path (Pathname)

    the absolute path to the scheme to be filtered

  • change_level (Symbol) (defaults to: :full_transitive)

    the change level at which a target must have changed in order to remain in the scheme. defaults to ‘:full_transitive`

  • filter_when_scheme_has_changed (Boolean) (defaults to: false)

    whether the scheme should be filtered even when the changeset includes the scheme’s path as changed. Defaults to ‘false`

  • log_changes (Boolean) (defaults to: false)

    whether modifications to the scheme are logged. Defaults to ‘false`

  • filter_scheme_for_build_action (:building, :testing)

    The xcodebuild action the scheme is being filtered for. The currently supported values are ‘:building` and `:testing`, with the only difference being `BuildActionEntry` are not filtered out when building for testing, since test action macro expansion could depend on a build entry being present.

Returns:

  • (Xcodeproj::XCScheme)

    a scheme whose unchanged targets have been removed



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/refinement/analyzer.rb', line 61

def filtered_scheme(scheme_path:, change_level: :full_transitive, filter_when_scheme_has_changed: false, log_changes: false,
                    filter_scheme_for_build_action:)
  scheme = Xcodeproj::XCScheme.new(scheme_path)

  sections_to_filter =
    case filter_scheme_for_build_action
    when :building
      %w[BuildActionEntry TestableReference]
    when :testing
      # don't want to filter out build action entries running
      # xcodebuild build-for-testing / test, since the test action could have a macro expansion
      # that depends upon one of the build targets.
      %w[TestableReference]
    else
      raise ArgumentError,
            'The supported values for the `filter_scheme_for_build_action` parameter are: [:building, :testing]. ' \
            "Given: #{filter_scheme_for_build_action.inspect}."
    end

  if filter_when_scheme_has_changed ||
     !UsedPath.new(path: Pathname(scheme_path), inclusion_reason: 'scheme').find_in_changeset(changeset)

    changes_by_suite_name = Hash[annotate_targets!
                            .map { |at| [at.xcode_target.name, at.change_reason(level: change_level)] }]

    doc = scheme.doc

    xpaths = sections_to_filter.map { |section| "//*/#{section}/BuildableReference" }
    xpaths.each do |xpath|
      doc.get_elements(xpath).to_a.each do |buildable_reference|
        suite_name = buildable_reference.attributes['BlueprintName']
        if (change_reason = changes_by_suite_name[suite_name])
          puts "#{suite_name} changed because #{change_reason}" if log_changes
          next
        end
        puts "#{suite_name} did not change, removing from scheme" if log_changes
        buildable_reference.parent.remove
      end
    end
  end

  scheme
end

#format_changes(include_unchanged_targets: false, change_level: :full_transitive) ⇒ String

Returns a string suitable for user display that explains target changes.

Parameters:

  • include_unchanged_targets (Boolean) (defaults to: false)

    whether targets that have not changed should also be displayed

  • change_level (Symbol) (defaults to: :full_transitive)

    the change level used for computing whether a target has changed

Returns:

  • (String)

    a string suitable for user display that explains target changes



108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/refinement/analyzer.rb', line 108

def format_changes(include_unchanged_targets: false, change_level: :full_transitive)
  annotate_targets!.group_by { |target| target.xcode_target.project.path.to_s }.sort_by(&:first)
                   .map do |project, annotated_targets|
    changes = annotated_targets.sort_by { |annotated_target| annotated_target.xcode_target.name }
                               .map do |annotated_target|
      change_reason = annotated_target.change_reason(level: change_level)
      next if !include_unchanged_targets && !change_reason

      change_reason ||= 'did not change'
      "\t#{annotated_target.xcode_target}: #{change_reason}"
    end.compact
    "#{project}:\n#{changes.join("\n")}" unless changes.empty?
  end.compact.join("\n")
end