Class: ReactNativeUtil::Project

Inherits:
Xcodeproj::Project show all
Includes:
Util
Defined in:
lib/react_native_util/project.rb

Constant Summary collapse

DEFAULT_DEPENDENCIES =
Array<String>

Xcode projects from react-native that may be in the Libraries group

%w[
  ART
  React
  RCTActionSheet
  RCTAnimation
  RCTBlob
  RCTCameraRoll
  RCTGeolocation
  RCTImage
  RCTLinking
  RCTNetwork
  RCTPushNotification
  RCTSettings
  RCTTest
  RCTText
  RCTVibration
  RCTWebSocket
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Util

#boolean_env_var?, #elapsed_from, #execute, #float_env_var, #have_command?, #log, #mac?, #platform, #run_command_with_spinner!, #validate_commands!

Instance Attribute Details

#app_nameObject

Returns the value of attribute app_name.



30
31
32
# File 'lib/react_native_util/project.rb', line 30

def app_name
  @app_name
end

Instance Method Details

#add_packager_script_from(react_project) ⇒ Object

Adds the Start Packager script from the React.xcodeproj under node_modules to the main application target before deleting React.xcodeproj from the Libraries group. Adjusts paths in the script to account for the different project location. If the relevant build phase is not found, a warning is logged, and this step is skipped.



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/react_native_util/project.rb', line 172

def add_packager_script_from(react_project)
  old_packager_phase = react_project.packager_phase
  unless old_packager_phase
    log 'Could not find packager build phase in React.xcodeproj. Skipping.'.yellow
    return
  end

  # location of project is different relative to packager script
  script = old_packager_phase.shell_script.gsub(%r{../scripts}, '../node_modules/react-native/scripts')

  phase = app_target.new_shell_script_build_phase old_packager_phase.name
  phase.shell_script = script

  # Move packager script to first position. This is independent of the
  # entire Xcode build process. As an optimization, the packager can
  # load its dependencies in parallel. This is the way it is on the
  # original React.xcodeproj.
  app_target.build_phases.delete phase
  app_target.build_phases.insert 0, phase
end

#app_target(platform = :ios) ⇒ Object



32
33
34
# File 'lib/react_native_util/project.rb', line 32

def app_target(platform = :ios)
  targets.find { |t| t.platform_name == platform && t.product_type == 'com.apple.product-type.application' }
end

#dependenciesArray<String>

A list of external dependencies from NPM requiring react-native link.

Returns:

  • (Array<String>)

    a list of NPM package names



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/react_native_util/project.rb', line 125

def dependencies
  return [] if libraries_group.nil?

  dependency_paths.map do |path|
    # Map each path to a Pathname, expanding $(SRCROOT) or ${SRCROOT}
    # SRCROOT = location of the app project: ./ios
    Pathname.new path.gsub(/\$(\(SRCROOT\)|{SRCROOT})/, 'ios')
  end.select do |pathname|
    # Valid if any path component named node_modules
    pathname.each_filename do |path_component|
      break true if path_component == 'node_modules'

      false
    end
  end.map do |pathname|
    # Map each selected Pathname to the root immediately under node_modules
    node_modules_found = false
    pathname.each_filename do |path_component|
      break path_component if node_modules_found

      node_modules_found = path_component == 'node_modules'
      '' # In case node_modules is the last component
      # TODO: Then what? Shouldn't happen, of course....
    end
  end
end

#dependency_pathsArray<String>

Paths to Xcode projects in the Libraries group from external deps.

Returns:

  • (Array<String>)

    a list of absolute paths to Xcode projects



154
155
156
157
158
159
# File 'lib/react_native_util/project.rb', line 154

def dependency_paths
  return [] if libraries_group.nil?

  paths = libraries_group.children.reject { |c| DEFAULT_DEPENDENCIES.include?(c.name.sub(/\.xcodeproj$/, '')) }.map(&:path)
  paths.map { |p| File.expand_path p, File.join(Dir.pwd, 'ios') }
end

#libraries_groupObject

A representation of the Libraries group (if any) from the Xcode project.

Returns:

  • the Libraries group



48
49
50
# File 'lib/react_native_util/project.rb', line 48

def libraries_group
  self['Libraries']
end

#library_rootsObject



161
162
163
164
165
# File 'lib/react_native_util/project.rb', line 161

def library_roots
  libraries_group.children.map do |library|
    File.basename(library.path).sub(/\.xcodeproj$/, '')
  end
end

#packager_phaseObject

Returns the original Start Packager build phase (from the React.xcodeproj under node_modules). This contains the original script.

Returns:

  • the packager build phase if found

  • nil if not found



210
211
212
# File 'lib/react_native_util/project.rb', line 210

def packager_phase
  targets.first.build_phases.find { |p| p.name =~ /packager/i }
end

#remove_libraries_from_target(target) ⇒ Object



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

def remove_libraries_from_target(target)
  to_remove = target.frameworks_build_phase.files.select do |file|
    path = file.file_ref.pretty_print
    next false unless /^lib(.+)\.a$/.match?(path)

    path = path.sub(/-tvOS\.a$/, '.a')
    static_libs.include?(path)
  end

  log "Removing Libraries from #{target.name}" unless to_remove.empty?
  to_remove.each do |f|
    log " Removing #{f.file_ref.pretty_print}"
    target.frameworks_build_phase.remove_build_file f
  end
end

#remove_libraries_groupObject

Remove the Libraries group from the xcodeproj in memory.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/react_native_util/project.rb', line 80

def remove_libraries_group
  # Remove links against these static libraries from all targets
  targets.each do |t|
    remove_libraries_from_target t
  end

  # Remove any unused projects from the Libraries group
  library_roots.each do |root|
    next unless targets_linking_with(root).empty?

    # Remove this lib from the Libraries group. Nothing is linking against it
    # anymore.
    child = libraries_group.children.find { |c| c.path =~ /#{root}\.xcodeproj/ }
    log "Removing #{root}.xcodeproj from Libraries group."
    child.remove_from_project
  end

  # Remove the Libraries group if it's empty. Or report that it's not.
  unless library_roots.empty?
    log 'Libraries group not empty. Not removing.'
    return
  end

  log 'Removing Libraries group.'
  libraries_group.remove_from_project
end

#static_libs(platform = :ios) ⇒ Array<String>

All static libraries from the Libraries group

Returns:

  • (Array<String>)

    an array of filenames



195
196
197
198
199
200
201
202
203
204
# File 'lib/react_native_util/project.rb', line 195

def static_libs(platform = :ios)
  library_roots.map do |root|
    case platform
    when :tvos
      "lib#{root}-tvOS.a"
    else
      "lib#{root}.a"
    end
  end
end

#targets_linking_with(root) ⇒ Object



52
53
54
55
56
57
58
59
60
61
# File 'lib/react_native_util/project.rb', line 52

def targets_linking_with(root)
  targets_matching do |r|
    if root.respond_to?(:include?)
      root.include? r
    else
      log "r = #{r}, root = #{root}"
      r == root
    end
  end
end

#targets_matching(&block) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/react_native_util/project.rb', line 63

def targets_matching(&block)
  targets.reject do |target|
    file_refs = target.frameworks_build_phase.files.map(&:file_ref).reject(&:nil?).map(&:pretty_print)

    libs_from_libraries_group = file_refs.select do |lib|
      lib = lib.sub(/-tvOS/, '')
      matches = /^lib(.+)\.a$/.match lib
      next false unless matches

      yield matches[1]
    end

    libs_from_libraries_group.empty?
  end
end

#test_target(platform = :ios) ⇒ Object



36
37
38
# File 'lib/react_native_util/project.rb', line 36

def test_target(platform = :ios)
  targets.select(&:test_target_type?).select { |t| t.platform_name == platform }.first
end

#validate_app_target!Object

Validate an assumption about the project. TODO: Provide override option.

Raises:

  • ConversionError if an application target is not found with the same name as the project.



42
43
44
# File 'lib/react_native_util/project.rb', line 42

def validate_app_target!
  raise ConversionError, "Unable to find application target in #{path}." if app_target.nil?
end