Class: GoNative::Plugins::IOS::BuildFramework

Inherits:
Object
  • Object
show all
Extended by:
DSL::Serviceable
Defined in:
lib/gonative/plugins/ios/build_framework.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(archs, persist_build_dir, project_root = FileUtils.pwd) ⇒ BuildFramework

Returns a new instance of BuildFramework.



18
19
20
21
22
# File 'lib/gonative/plugins/ios/build_framework.rb', line 18

def initialize(archs, persist_build_dir, project_root = FileUtils.pwd)
  @archs = archs.gsub(',', ' ')
  @persist_build_dir = persist_build_dir
  @project_root = File.expand_path(project_root)
end

Instance Attribute Details

#archsObject (readonly)

Returns the value of attribute archs.



16
17
18
# File 'lib/gonative/plugins/ios/build_framework.rb', line 16

def archs
  @archs
end

#persist_build_dirObject (readonly)

Returns the value of attribute persist_build_dir.



16
17
18
# File 'lib/gonative/plugins/ios/build_framework.rb', line 16

def persist_build_dir
  @persist_build_dir
end

#project_rootObject (readonly)

Returns the value of attribute project_root.



16
17
18
# File 'lib/gonative/plugins/ios/build_framework.rb', line 16

def project_root
  @project_root
end

Instance Method Details

#archive_commandsObject



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/gonative/plugins/ios/build_framework.rb', line 130

def archive_commands
  [
    xcodebuild_archive_command(
      destination: "generic/platform=iOS Simulator",
      archive_path: simulator_archive_path,
      sdk: "iphonesimulator",
      archs_override: true
    ),
    xcodebuild_archive_command(
      destination: "generic/platform=iOS",
      archive_path: device_archive_path,
      sdk: "iphoneos"
    ),
    create_xcframework_command,
    %(rm -rf #{Shellwords.escape(simulator_archive_path)} #{Shellwords.escape(device_archive_path)})
  ].join(' && ')
end

#archive_framework!Object

Raises:



121
122
123
124
125
126
127
128
# File 'lib/gonative/plugins/ios/build_framework.rb', line 121

def archive_framework!
  FileUtils.rm_rf(simulator_archive_path)
  FileUtils.rm_rf(device_archive_path)

  return if system(archive_commands)

  raise Error, 'Error building framework with Tuist. Please run the generated archive commands manually to inspect the failure'
end

#build_framework_with_tuist!Object



36
37
38
39
40
41
42
43
44
# File 'lib/gonative/plugins/ios/build_framework.rb', line 36

def build_framework_with_tuist!
  FileUtils.cd(project_root)
  FileUtils.mkdir_p(File.join('.build', 'Frameworks'))
  generate_module_artifacts!
  generate_tuist_project!
  patch_generated_project!
  archive_framework!
  move_xcframework_to_frameworks!
end

#callObject



24
25
26
27
28
29
30
31
32
33
34
# File 'lib/gonative/plugins/ios/build_framework.rb', line 24

def call
  original_dir = FileUtils.pwd

  raise Error, "No Project.swift found in #{project_root}" unless tuist_project?

  build_framework_with_tuist!
ensure
  FileUtils.cd(original_dir) if original_dir && File.directory?(original_dir)
  cleanup_build_dir unless persist_build_dir

end

#classes_root_pathObject



233
234
235
# File 'lib/gonative/plugins/ios/build_framework.rb', line 233

def classes_root_path
  File.join(project_root, 'Classes')
end

#cleanup_build_dirObject



209
210
211
212
213
214
215
# File 'lib/gonative/plugins/ios/build_framework.rb', line 209

def cleanup_build_dir
  FileUtils.rm_rf(File.join(project_root, '.build'))
  FileUtils.rm_rf(File.join(project_root, "#{plugin_name}.xcodeproj"))
  FileUtils.rm_rf(File.join(project_root, "#{plugin_name}.xcworkspace"))
  FileUtils.rm_f(File.join(project_root, "#{plugin_name}.modulemap"))
  FileUtils.rm_f(File.join(project_root, "#{plugin_name}-umbrella.h"))
end

#create_xcframework_commandObject



148
149
150
# File 'lib/gonative/plugins/ios/build_framework.rb', line 148

def create_xcframework_command
  %(xcodebuild -create-xcframework -framework #{Shellwords.escape(simulator_framework_path)} -debug-symbols #{Shellwords.escape(simulator_dsym_path)} -framework #{Shellwords.escape(device_framework_path)} -debug-symbols #{Shellwords.escape(device_dsym_path)} -output #{Shellwords.escape(xcframework_output_path)})
end

#device_archive_pathObject



185
186
187
# File 'lib/gonative/plugins/ios/build_framework.rb', line 185

def device_archive_path
  File.join('.build', 'Frameworks', 'iOS.xcarchive')
end

#device_dsym_pathObject



205
206
207
# File 'lib/gonative/plugins/ios/build_framework.rb', line 205

def device_dsym_path
  File.expand_path(File.join(device_archive_path, 'dSYMs', "#{plugin_name}.framework.dSYM"), project_root)
end

#device_framework_pathObject



197
198
199
# File 'lib/gonative/plugins/ios/build_framework.rb', line 197

def device_framework_path
  File.expand_path(File.join(device_archive_path, 'Products', 'Library', 'Frameworks', "#{plugin_name}.framework"), project_root)
end

#ensure_public_umbrella_header!(project, target) ⇒ Object



111
112
113
114
115
116
117
118
119
# File 'lib/gonative/plugins/ios/build_framework.rb', line 111

def ensure_public_umbrella_header!(project, target)
  relative_header_path = File.basename(umbrella_header_path)
  file_reference = project.files.find { |file| file.path == relative_header_path } ||
    project.main_group.new_file(relative_header_path)

  build_file = target.headers_build_phase.files_references.find { |file| file.path == relative_header_path }
  build_file ||= target.headers_build_phase.add_file_reference(file_reference, true)
  build_file.settings = { 'ATTRIBUTES' => ['Public'] }
end

#frameworks_dirObject



56
57
58
# File 'lib/gonative/plugins/ios/build_framework.rb', line 56

def frameworks_dir
  File.join(project_root, 'Frameworks')
end

#generate_module_artifacts!Object



74
75
76
77
78
# File 'lib/gonative/plugins/ios/build_framework.rb', line 74

def generate_module_artifacts!
  Utils::UI.info 'Generating umbrella header and module map'
  File.write(umbrella_header_path, umbrella_header_contents)
  File.write(modulemap_path, modulemap_contents)
end

#generate_tuist_project!Object

Raises:



80
81
82
83
84
85
# File 'lib/gonative/plugins/ios/build_framework.rb', line 80

def generate_tuist_project!
  Utils::UI.info 'Generating project with Tuist'
  return if system('tuist generate --no-open')

  raise Error, 'Error generating Tuist project'
end

#header_import_pathsObject



237
238
239
240
241
242
243
244
# File 'lib/gonative/plugins/ios/build_framework.rb', line 237

def header_import_paths
  Dir.glob(File.join(classes_root_path, '**', '*.h')).sort.filter_map do |header_path|
    basename = File.basename(header_path)
    next if basename == "#{plugin_name}-Bridging-Header.h"

    basename
  end
end

#modulemap_contentsObject



267
268
269
270
271
272
273
274
275
276
# File 'lib/gonative/plugins/ios/build_framework.rb', line 267

def modulemap_contents
  <<~MODULEMAP
    framework module #{plugin_name} {
      umbrella header "#{File.basename(umbrella_header_path)}"

      export *
      module * { export * }
    }
  MODULEMAP
end

#modulemap_pathObject



221
222
223
# File 'lib/gonative/plugins/ios/build_framework.rb', line 221

def modulemap_path
  File.join(project_root, "#{plugin_name}.modulemap")
end

#modulemap_project_setting_pathObject



229
230
231
# File 'lib/gonative/plugins/ios/build_framework.rb', line 229

def modulemap_project_setting_path
  "$(PROJECT_DIR)/#{File.basename(modulemap_path)}"
end

#move_xcframework_to_frameworks!Object



46
47
48
49
50
51
52
53
54
# File 'lib/gonative/plugins/ios/build_framework.rb', line 46

def move_xcframework_to_frameworks!
  Utils::UI.info 'Moving xcframework to Frameworks directory'
  source = File.join(project_root, '.build', 'Frameworks', "#{plugin_name}.xcframework")
  dest_dir = frameworks_dir
  FileUtils.mkdir_p(dest_dir)
  dest = File.join(dest_dir, "#{plugin_name}.xcframework")
  FileUtils.rm_rf(dest) if File.exist?(dest)
  FileUtils.cp_r(source, dest)
end

#patch_build_settings!(config) ⇒ Object



105
106
107
108
109
# File 'lib/gonative/plugins/ios/build_framework.rb', line 105

def patch_build_settings!(config)
  config.build_settings.delete('SWIFT_OBJC_BRIDGING_HEADER')
  config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
  config.build_settings['MODULEMAP_FILE'] = modulemap_project_setting_path
end

#patch_generated_project!Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/gonative/plugins/ios/build_framework.rb', line 87

def patch_generated_project!
  Utils::UI.info 'Patching generated Xcode project'
  project = Xcodeproj::Project.open(xcodeproj_path)

  project.build_configurations.each do |config|
    patch_build_settings!(config)
  end

  project.targets.select { |target| target.name == plugin_name }.each do |target|
    ensure_public_umbrella_header!(project, target)
    target.build_configurations.each do |config|
      patch_build_settings!(config)
    end
  end

  project.save
end

#plugin_nameObject



64
65
66
67
68
69
70
71
72
# File 'lib/gonative/plugins/ios/build_framework.rb', line 64

def plugin_name
  @plugin_name ||= begin
    project_definition = File.read(File.join(project_root, 'Project.swift'))
    match = project_definition.match(/name:\s*"([^"]+)"/)
    raise Error, "Could not determine project name from #{File.join(project_root, 'Project.swift')}" unless match

    match[1]
  end
end

#simulator_archive_pathObject



181
182
183
# File 'lib/gonative/plugins/ios/build_framework.rb', line 181

def simulator_archive_path
  File.join('.build', 'Frameworks', 'simulator.xcarchive')
end

#simulator_dsym_pathObject



201
202
203
# File 'lib/gonative/plugins/ios/build_framework.rb', line 201

def simulator_dsym_path
  File.expand_path(File.join(simulator_archive_path, 'dSYMs', "#{plugin_name}.framework.dSYM"), project_root)
end

#simulator_framework_pathObject



193
194
195
# File 'lib/gonative/plugins/ios/build_framework.rb', line 193

def simulator_framework_path
  File.expand_path(File.join(simulator_archive_path, 'Products', 'Library', 'Frameworks', "#{plugin_name}.framework"), project_root)
end

#tuist_project?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/gonative/plugins/ios/build_framework.rb', line 60

def tuist_project?
  File.exist?(File.join(project_root, 'Project.swift'))
end

#tuist_workspace_pathObject



177
178
179
# File 'lib/gonative/plugins/ios/build_framework.rb', line 177

def tuist_workspace_path
  "#{plugin_name}.xcworkspace"
end

#umbrella_header_contentsObject



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/gonative/plugins/ios/build_framework.rb', line 246

def umbrella_header_contents
  <<~HEADER
    #ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #else
    #ifndef FOUNDATION_EXPORT
    #if defined(__cplusplus)
    #define FOUNDATION_EXPORT extern "C"
    #else
    #define FOUNDATION_EXPORT extern
    #endif
    #endif
    #endif

    #{header_import_paths.map { |path| %(#import "#{path}") }.join("\n")}

    FOUNDATION_EXPORT double #{plugin_name}VersionNumber;
    FOUNDATION_EXPORT const unsigned char #{plugin_name}VersionString[];
  HEADER
end

#umbrella_header_pathObject



225
226
227
# File 'lib/gonative/plugins/ios/build_framework.rb', line 225

def umbrella_header_path
  File.join(project_root, "#{plugin_name}-umbrella.h")
end

#xcframework_output_pathObject



189
190
191
# File 'lib/gonative/plugins/ios/build_framework.rb', line 189

def xcframework_output_path
  File.join('.build', 'Frameworks', "#{plugin_name}.xcframework")
end

#xcodebuild_archive_command(destination:, archive_path:, sdk:, archs_override: false) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/gonative/plugins/ios/build_framework.rb', line 152

def xcodebuild_archive_command(destination:, archive_path:, sdk:, archs_override: false)
  command = [
    "xcodebuild",
    "-workspace #{Shellwords.escape(tuist_workspace_path)}",
    "archive",
  ]

  if archs_override
    command << "ONLY_ACTIVE_ARCH=NO"
    command << "ARCHS=#{Shellwords.escape(archs)}"
  end

  command.concat(
    [
      "-scheme #{Shellwords.escape(plugin_name)}",
      %( -destination "#{destination}").strip,
      "-archivePath #{Shellwords.escape(archive_path)}",
      "-sdk #{Shellwords.escape(sdk)}",
      "SKIP_INSTALL=NO",
    ]
  )

  command.join(" ")
end

#xcodeproj_pathObject



217
218
219
# File 'lib/gonative/plugins/ios/build_framework.rb', line 217

def xcodeproj_path
  File.join(project_root, "#{plugin_name}.xcodeproj")
end