Top Level Namespace

Constant Summary collapse

BUILD_PHASE_PREFIX =
SPI

stands for Script Phase Injector

Returns:

  • (String)

    prefix used for all the build phase injected by this script

'[SPI] '.freeze
CONFIGURATION_FILE_PATH =
'Configuration/spinjector_configuration.yaml'.freeze

Instance Method Summary collapse

Instance Method Details

#app_target(project, target_name) ⇒ Xcodeproj::Project::Object::PBXNativeTarget

Returns the target named by target_name.

Parameters:

  • project (Xcodeproj::Project)
  • target_name (String)

Returns:

  • (Xcodeproj::Project::Object::PBXNativeTarget)

    the target named by target_name



67
68
69
70
71
# File 'lib/spinjector.rb', line 67

def app_target(project, target_name)
  target = project.targets.find { |t| t.name == target_name }
  raise "[Error] Invalid #{target_name} target." unless !target.nil?
  return target
end

#create_script_phases(script_phases, target) ⇒ Object

Parameters:

  • script_phases (Array<Hash>)

    the script phases defined in configuration files

  • target (Xcodeproj::Project::Object::PBXNativeTarget)

    to add the script phases



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/spinjector.rb', line 76

def create_script_phases(script_phases, target)
  script_phases.each do |script_phase|
    name_with_prefix = BUILD_PHASE_PREFIX + script_phase["name"]
    phase = target.new_shell_script_build_phase(name_with_prefix)
    script = File.read(script_phase["script_path"])
    phase.shell_script = script
    phase.shell_path = script_phase["shell_path"] || '/bin/sh'
    phase.input_paths = script_phase["input_paths"]
    phase.output_paths = script_phase["output_paths"]
    phase.input_file_list_paths = script_phase["input_file_list_paths"]
    phase.output_file_list_paths = script_phase["output_file_list_paths"]
    phase.dependency_file = script_phase["dependency_file"]
    # At least with Xcode 10 `showEnvVarsInLog` is *NOT* set to any value even if it's checked and it only
    # gets set to '0' if the user has explicitly disabled this.
    if (show_env_vars_in_log = script_phase.fetch("show_env_vars_in_log", '1')) == '0'
      phase.show_env_vars_in_log = show_env_vars_in_log
    end

    execution_position = script_phase["execution_position"] || :before_compile
    reorder_script_phase(target, phase, execution_position)
  end
end

#inject_script_phases(project, configuration_file_path) ⇒ Object

Parameters:

  • project (Xcodeproj::Project)


43
44
45
46
47
48
49
50
51
52
53
# File 'lib/spinjector.rb', line 43

def inject_script_phases(project, configuration_file_path)
  configuration_file = load_yml_content(configuration_file_path)
  configuration_file.each do |target_name, script_paths|
    script_phases = (script_paths || []).flat_map do |script_path|
      load_yml_content(script_path)
    end
    warn "[Warning] No script phases found for #{target_name} target. You can add them in your configuration file at #{configuration_file_path}" unless !script_phases.empty?
    target = app_target(project, target_name)
    create_script_phases(script_phases, target)
  end
end

#load_yml_content(configuration_path) ⇒ Hash

Returns the hash in the configuration file.

Parameters:

  • configuration_path (String)

Returns:

  • (Hash)

    the hash in the configuration file



58
59
60
61
# File 'lib/spinjector.rb', line 58

def load_yml_content(configuration_path)
  raise "[Error] YAML file #{configuration_path} not found." unless File.exist?(configuration_path)
  YAML.load(File.read(configuration_path)) || {}
end

#remove_all_spi_script_phases(project) ⇒ Object

Parameters:

  • project (Xcodeproj::Project)


29
30
31
32
33
34
35
36
37
38
39
# File 'lib/spinjector.rb', line 29

def remove_all_spi_script_phases(project)
  project.targets.each do |target|
    # Delete script phases no longer present in the target.
    native_target_script_phases = target.shell_script_build_phases.select do |bp|
      !bp.name.nil? && bp.name.start_with?(BUILD_PHASE_PREFIX)
    end
    native_target_script_phases.each do |script_phase|
      target.build_phases.delete(script_phase)
    end
  end
end

#reorder_script_phase(target, script_phase, execution_position) ⇒ Object

Parameters:

  • target (Xcodeproj::Project::Object::PBXNativeTarget)

    where build phases should be reordered

  • script_phase (Hash)

    to reorder

  • execution_position (Symbol)

    could be :before_compile, :after_compile, :before_headers, :after_headers



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/spinjector.rb', line 103

def reorder_script_phase(target, script_phase, execution_position)
  return if execution_position == :any || execution_position.to_s.empty?

  # Find the point P where to add the script phase
  target_phase_type = case execution_position
                      when :before_compile, :after_compile
                        Xcodeproj::Project::Object::PBXSourcesBuildPhase
                      when :before_headers, :after_headers
                        Xcodeproj::Project::Object::PBXHeadersBuildPhase
                      else
                        raise ArgumentError, "Unknown execution position `#{execution_position}`"
                      end

  # Decide whether to add script_phase before or after point P
  order_before = case execution_position
                 when :before_compile, :before_headers
                   true
                 when :after_compile, :after_headers
                   false
                 else
                   raise ArgumentError, "Unknown execution position `#{execution_position}`"
                 end

  # Get the first build phase index of P
  target_phase_index = target.build_phases.index do |bp|
    bp.is_a?(target_phase_type)
  end
  return if target_phase_index.nil?

  # Get the script phase we want to reorder index
  script_phase_index = target.build_phases.index do |bp|
    bp.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && !bp.name.nil? && bp.name == script_phase.name
  end

  # Move script phase to P if needed
  if (order_before && script_phase_index > target_phase_index) ||
    (!order_before && script_phase_index < target_phase_index)
    target.build_phases.move_from(script_phase_index, target_phase_index)
  end
end