pindo 5.1.1 → 5.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/pindo/command/android/autobuild.rb +13 -0
- data/lib/pindo/command/unity/autobuild.rb +200 -0
- data/lib/pindo/command/unity/web.rb +152 -0
- data/lib/pindo/command/unity.rb +1 -1
- data/lib/pindo/command/web/run.rb +18 -178
- data/lib/pindo/module/android/build_helper.rb +325 -0
- data/lib/pindo/module/webserver/brotli_file_handler.rb +57 -0
- data/lib/pindo/module/webserver/preview/preview.css +433 -0
- data/lib/pindo/module/webserver/preview/preview.html +73 -0
- data/lib/pindo/module/webserver/preview/preview.js +595 -0
- data/lib/pindo/module/webserver/responsive_preview_handler.rb +104 -0
- data/lib/pindo/module/webserver/webgl_page_handler.rb +45 -0
- data/lib/pindo/module/webserver/webgl_server_helper.rb +190 -0
- data/lib/pindo/version.rb +1 -1
- metadata +11 -2
@@ -3,6 +3,7 @@ require_relative 'base_helper'
|
|
3
3
|
require_relative 'gradle_helper'
|
4
4
|
require_relative 'so_helper'
|
5
5
|
require_relative 'apk_helper'
|
6
|
+
require 'fileutils'
|
6
7
|
|
7
8
|
module Pindo
|
8
9
|
class AndroidBuildHelper
|
@@ -12,12 +13,81 @@ module Pindo
|
|
12
13
|
include ApkHelper
|
13
14
|
include Singleton
|
14
15
|
|
16
|
+
|
17
|
+
|
15
18
|
class << self
|
16
19
|
def share_instance
|
17
20
|
instance
|
18
21
|
end
|
19
22
|
end
|
20
23
|
|
24
|
+
def add_test_scheme(project_dir:nil, scheme_name:nil)
|
25
|
+
puts "正在为Android项目添加自定义scheme: #{scheme_name}"
|
26
|
+
puts "项目路径: #{project_dir}, 当前工作目录: #{Dir.pwd}"
|
27
|
+
|
28
|
+
# 参数验证
|
29
|
+
return puts "错误: 需要提供scheme名称" if scheme_name.nil?
|
30
|
+
return puts "错误: 需要提供项目路径" if project_dir.nil?
|
31
|
+
return puts "错误: 项目路径不存在: #{project_dir}" unless File.directory?(project_dir)
|
32
|
+
|
33
|
+
scheme_name = scheme_name.to_s.downcase.strip.gsub(/[\s\-_]/, '')
|
34
|
+
# 查找所有可能的模块
|
35
|
+
main_module = get_main_module(project_dir)
|
36
|
+
search_modules = ["app", "unityLibrary"]
|
37
|
+
search_modules << main_module if main_module
|
38
|
+
search_modules = search_modules.compact.uniq
|
39
|
+
|
40
|
+
puts "搜索的模块: #{search_modules.join(', ')}"
|
41
|
+
|
42
|
+
# 查找所有可能的AndroidManifest.xml文件
|
43
|
+
manifest_candidates = find_manifests(project_dir, search_modules)
|
44
|
+
|
45
|
+
if manifest_candidates.empty?
|
46
|
+
return puts "错误: 未找到任何AndroidManifest.xml文件"
|
47
|
+
end
|
48
|
+
|
49
|
+
puts "找到#{manifest_candidates.size}个AndroidManifest.xml文件"
|
50
|
+
|
51
|
+
# 寻找最佳的AndroidManifest.xml和Activity
|
52
|
+
manifest_path, main_activity, android_prefix = find_best_activity(manifest_candidates)
|
53
|
+
|
54
|
+
if manifest_path.nil? || main_activity.nil?
|
55
|
+
return puts "错误: 无法找到合适的Activity"
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "已选择 #{manifest_path} 文件中的 #{main_activity['android:name'] || '主Activity'}"
|
59
|
+
|
60
|
+
# 检查scheme是否已存在
|
61
|
+
scheme_exists, existing_scheme = check_scheme_exists(main_activity, android_prefix, scheme_name)
|
62
|
+
|
63
|
+
# 如果scheme已存在,检查是否需要更新格式
|
64
|
+
if scheme_exists
|
65
|
+
activity_name = main_activity["#{android_prefix}:name"] || "主Activity"
|
66
|
+
if existing_scheme != scheme_name
|
67
|
+
# 格式不同,需要更新
|
68
|
+
update_scheme = update_existing_scheme(manifest_path, main_activity, android_prefix, existing_scheme, scheme_name)
|
69
|
+
if update_scheme
|
70
|
+
puts "已将scheme从'#{existing_scheme}'更新为'#{scheme_name}'"
|
71
|
+
else
|
72
|
+
puts "尝试更新scheme格式失败,但scheme已存在: #{existing_scheme}"
|
73
|
+
end
|
74
|
+
else
|
75
|
+
puts "scheme: #{scheme_name}已存在于#{activity_name}"
|
76
|
+
end
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
80
|
+
# 尝试添加scheme
|
81
|
+
success = add_scheme_with_dom(manifest_path, main_activity, android_prefix, scheme_name)
|
82
|
+
|
83
|
+
# 如果DOM操作失败,使用文本替换
|
84
|
+
unless success
|
85
|
+
add_scheme_with_text_replace(manifest_path, scheme_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
puts "添加scheme操作完成,项目路径: #{project_dir}"
|
89
|
+
end
|
90
|
+
|
21
91
|
def auto_build_apk(project_dir, debug = false, ignore_sub = false)
|
22
92
|
# 检查 gradle.properties 中是否设置了 Java Home
|
23
93
|
gradle_properties_path = File.join(project_dir, "gradle.properties")
|
@@ -108,5 +178,260 @@ module Pindo
|
|
108
178
|
rescue StandardError => e
|
109
179
|
raise Informative, "准备项目失败: #{e.message}"
|
110
180
|
end
|
181
|
+
|
182
|
+
# 查找所有可能的AndroidManifest.xml文件
|
183
|
+
def find_manifests(project_dir, modules)
|
184
|
+
manifest_candidates = []
|
185
|
+
|
186
|
+
# 搜索每个模块中的AndroidManifest.xml
|
187
|
+
modules.each do |mod|
|
188
|
+
mod_path = mod.is_a?(String) && !mod.start_with?(project_dir) ? File.join(project_dir, mod) : mod
|
189
|
+
|
190
|
+
# 标准位置
|
191
|
+
standard_manifest = File.join(mod_path, "src", "main", "AndroidManifest.xml")
|
192
|
+
manifest_candidates << standard_manifest if File.exist?(standard_manifest)
|
193
|
+
|
194
|
+
# 根目录
|
195
|
+
root_manifest = File.join(mod_path, "AndroidManifest.xml")
|
196
|
+
manifest_candidates << root_manifest if File.exist?(root_manifest)
|
197
|
+
end
|
198
|
+
|
199
|
+
# 项目根目录
|
200
|
+
manifest_candidates << File.join(project_dir, "AndroidManifest.xml") if File.exist?(File.join(project_dir, "AndroidManifest.xml"))
|
201
|
+
manifest_candidates << File.join(project_dir, "src/main/AndroidManifest.xml") if File.exist?(File.join(project_dir, "src/main/AndroidManifest.xml"))
|
202
|
+
|
203
|
+
manifest_candidates.uniq
|
204
|
+
end
|
205
|
+
|
206
|
+
# 查找最佳的Activity
|
207
|
+
def find_best_activity(manifest_candidates)
|
208
|
+
require 'nokogiri'
|
209
|
+
|
210
|
+
best_manifest = nil
|
211
|
+
best_activity = nil
|
212
|
+
android_prefix = nil
|
213
|
+
|
214
|
+
manifest_candidates.each do |manifest_path|
|
215
|
+
begin
|
216
|
+
puts "检查: #{manifest_path}"
|
217
|
+
doc = Nokogiri::XML(File.read(manifest_path))
|
218
|
+
|
219
|
+
# 处理命名空间
|
220
|
+
android_ns = 'http://schemas.android.com/apk/res/android'
|
221
|
+
prefix = nil
|
222
|
+
|
223
|
+
doc.root.namespace_definitions.each do |ns|
|
224
|
+
if ns.href == android_ns
|
225
|
+
prefix = ns.prefix
|
226
|
+
break
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
prefix ||= 'android'
|
231
|
+
|
232
|
+
# 构建XPath查询
|
233
|
+
ns_xpath = lambda do |xpath|
|
234
|
+
xpath.gsub(/@android:/, "@#{prefix}:")
|
235
|
+
.gsub(/\[@android:/, "[@#{prefix}:")
|
236
|
+
end
|
237
|
+
|
238
|
+
# 查找LAUNCHER Activity
|
239
|
+
launcher_activities = doc.xpath(ns_xpath.call('//activity[intent-filter/category[@android:name="android.intent.category.LAUNCHER"]]'))
|
240
|
+
|
241
|
+
if !launcher_activities.empty?
|
242
|
+
best_manifest = manifest_path
|
243
|
+
best_activity = launcher_activities.first
|
244
|
+
android_prefix = prefix
|
245
|
+
puts "找到带有LAUNCHER的Activity: #{best_activity['android:name']}"
|
246
|
+
break
|
247
|
+
elsif best_activity.nil?
|
248
|
+
activities = doc.xpath('//activity')
|
249
|
+
if !activities.empty?
|
250
|
+
best_manifest = manifest_path
|
251
|
+
best_activity = activities.first
|
252
|
+
android_prefix = prefix
|
253
|
+
puts "找到Activity(无LAUNCHER): #{best_activity['android:name']}"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
rescue => e
|
257
|
+
puts "解析失败: #{e.message}"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
[best_manifest, best_activity, android_prefix]
|
262
|
+
end
|
263
|
+
|
264
|
+
# 检查scheme是否已存在
|
265
|
+
def check_scheme_exists(activity, android_prefix, scheme_name)
|
266
|
+
scheme_exists = false
|
267
|
+
existing_scheme = nil
|
268
|
+
|
269
|
+
# 首先检查完全一致的scheme
|
270
|
+
activity.xpath("intent-filter/data[@#{android_prefix}:scheme='#{scheme_name}']").each do |node|
|
271
|
+
scheme_exists = true
|
272
|
+
existing_scheme = scheme_name
|
273
|
+
break
|
274
|
+
end
|
275
|
+
|
276
|
+
# 如果没有找到完全一致的,检查可能格式不同的scheme
|
277
|
+
if !scheme_exists
|
278
|
+
# 查找所有scheme属性
|
279
|
+
activity.xpath("intent-filter/data[@#{android_prefix}:scheme]").each do |node|
|
280
|
+
current_scheme = node["#{android_prefix}:scheme"]
|
281
|
+
normalized_current = current_scheme.to_s.downcase.strip.gsub(/[\s\-_]/, '')
|
282
|
+
|
283
|
+
if normalized_current == scheme_name
|
284
|
+
scheme_exists = true
|
285
|
+
existing_scheme = current_scheme
|
286
|
+
puts "发现格式不同但实质相同的scheme: '#{current_scheme}'"
|
287
|
+
break
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
[scheme_exists, existing_scheme]
|
293
|
+
end
|
294
|
+
|
295
|
+
# 使用DOM操作添加scheme
|
296
|
+
def add_scheme_with_dom(manifest_path, activity, android_prefix, scheme_name)
|
297
|
+
begin
|
298
|
+
doc = Nokogiri::XML(File.read(manifest_path))
|
299
|
+
|
300
|
+
# 创建intent-filter
|
301
|
+
intent_filter = doc.create_element('intent-filter')
|
302
|
+
|
303
|
+
# 添加子元素
|
304
|
+
intent_filter.add_child(create_element(doc, 'action', "#{android_prefix}:name", 'android.intent.action.VIEW'))
|
305
|
+
intent_filter.add_child(create_element(doc, 'category', "#{android_prefix}:name", 'android.intent.category.DEFAULT'))
|
306
|
+
intent_filter.add_child(create_element(doc, 'category', "#{android_prefix}:name", 'android.intent.category.BROWSABLE'))
|
307
|
+
intent_filter.add_child(create_element(doc, 'data', "#{android_prefix}:scheme", scheme_name))
|
308
|
+
|
309
|
+
# 添加空白和缩进
|
310
|
+
activity.add_child(doc.create_text_node("\n "))
|
311
|
+
activity.add_child(intent_filter)
|
312
|
+
activity.add_child(doc.create_text_node("\n "))
|
313
|
+
|
314
|
+
# 保存修改
|
315
|
+
xml_content = doc.to_xml(indent: 2, encoding: 'UTF-8')
|
316
|
+
|
317
|
+
# 验证修改是否成功
|
318
|
+
if xml_content.include?("android:scheme=\"#{scheme_name}\"")
|
319
|
+
File.write(manifest_path, xml_content)
|
320
|
+
puts "DOM操作成功添加了scheme: #{scheme_name}"
|
321
|
+
return true
|
322
|
+
end
|
323
|
+
|
324
|
+
puts "DOM操作未能添加scheme"
|
325
|
+
return false
|
326
|
+
rescue => e
|
327
|
+
puts "DOM操作失败: #{e.message}"
|
328
|
+
return false
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# 创建XML元素并设置属性
|
333
|
+
def create_element(doc, name, attr_name, attr_value)
|
334
|
+
element = doc.create_element(name)
|
335
|
+
element[attr_name] = attr_value
|
336
|
+
element
|
337
|
+
end
|
338
|
+
|
339
|
+
# 使用文本替换添加scheme
|
340
|
+
def add_scheme_with_text_replace(manifest_path, scheme_name)
|
341
|
+
begin
|
342
|
+
# 读取原始内容
|
343
|
+
xml_content = File.read(manifest_path)
|
344
|
+
|
345
|
+
# 定义要添加的intent-filter
|
346
|
+
scheme_intent_filter = %Q{
|
347
|
+
<intent-filter>
|
348
|
+
<action android:name="android.intent.action.VIEW"/>
|
349
|
+
<category android:name="android.intent.category.DEFAULT"/>
|
350
|
+
<category android:name="android.intent.category.BROWSABLE"/>
|
351
|
+
<data android:scheme="#{scheme_name}"/>
|
352
|
+
</intent-filter>}
|
353
|
+
|
354
|
+
# 在</activity>前添加intent-filter
|
355
|
+
if xml_content.match(/<\/activity>/)
|
356
|
+
modified_xml = xml_content.gsub(/<\/activity>/) do |match|
|
357
|
+
"#{scheme_intent_filter}\n #{match}"
|
358
|
+
end
|
359
|
+
|
360
|
+
# 保存修改
|
361
|
+
File.write(manifest_path, modified_xml)
|
362
|
+
puts "文本替换成功添加了scheme: #{scheme_name}"
|
363
|
+
return true
|
364
|
+
else
|
365
|
+
puts "在XML中找不到</activity>标签"
|
366
|
+
return false
|
367
|
+
end
|
368
|
+
rescue => e
|
369
|
+
puts "文本替换失败: #{e.message}"
|
370
|
+
return false
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def update_existing_scheme(manifest_path, activity, android_prefix, existing_scheme, scheme_name)
|
375
|
+
begin
|
376
|
+
doc = Nokogiri::XML(File.read(manifest_path))
|
377
|
+
|
378
|
+
# 查找所有intent-filter
|
379
|
+
intent_filters = doc.xpath("//intent-filter")
|
380
|
+
|
381
|
+
intent_filters.each do |intent_filter|
|
382
|
+
# 检查intent-filter中的scheme
|
383
|
+
intent_filter.xpath("data[@#{android_prefix}:scheme]").each do |data|
|
384
|
+
current_scheme = data["#{android_prefix}:scheme"]
|
385
|
+
if current_scheme == existing_scheme
|
386
|
+
# 找到intent-filter,更新scheme
|
387
|
+
data["#{android_prefix}:scheme"] = scheme_name
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# 保存修改
|
393
|
+
xml_content = doc.to_xml(indent: 2, encoding: 'UTF-8')
|
394
|
+
|
395
|
+
# 验证修改是否成功
|
396
|
+
if xml_content.include?("android:scheme=\"#{scheme_name}\"")
|
397
|
+
File.write(manifest_path, xml_content)
|
398
|
+
puts "DOM操作成功更新了scheme: #{scheme_name}"
|
399
|
+
return true
|
400
|
+
end
|
401
|
+
|
402
|
+
puts "DOM操作未能更新scheme,尝试文本替换方法"
|
403
|
+
return update_scheme_with_text_replace(manifest_path, existing_scheme, scheme_name)
|
404
|
+
rescue => e
|
405
|
+
puts "DOM操作失败: #{e.message},尝试文本替换方法"
|
406
|
+
return update_scheme_with_text_replace(manifest_path, existing_scheme, scheme_name)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
# 使用文本替换方法更新scheme
|
411
|
+
def update_scheme_with_text_replace(manifest_path, existing_scheme, scheme_name)
|
412
|
+
begin
|
413
|
+
# 读取原始内容
|
414
|
+
xml_content = File.read(manifest_path)
|
415
|
+
|
416
|
+
# 使用正则表达式替换scheme值
|
417
|
+
attribute_pattern = /android:scheme=["']#{Regexp.escape(existing_scheme)}["']/
|
418
|
+
|
419
|
+
if xml_content.match(attribute_pattern)
|
420
|
+
modified_xml = xml_content.gsub(attribute_pattern, "android:scheme=\"#{scheme_name}\"")
|
421
|
+
|
422
|
+
# 保存修改
|
423
|
+
File.write(manifest_path, modified_xml)
|
424
|
+
puts "文本替换成功更新scheme: #{scheme_name}"
|
425
|
+
return true
|
426
|
+
else
|
427
|
+
puts "在XML中找不到匹配的scheme属性"
|
428
|
+
return false
|
429
|
+
end
|
430
|
+
rescue => e
|
431
|
+
puts "文本替换更新失败: #{e.message}"
|
432
|
+
return false
|
433
|
+
end
|
434
|
+
end
|
111
435
|
end
|
112
436
|
end
|
437
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'highline/import'
|
2
|
+
require 'xcodeproj'
|
3
|
+
require 'find'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'pindo/base/executable'
|
6
|
+
require 'webrick' # 添加WebRick HTTP服务器
|
7
|
+
require 'socket'
|
8
|
+
|
9
|
+
module Pindo
|
10
|
+
module WebServer
|
11
|
+
|
12
|
+
# 处理.br扩展名请求的servlet
|
13
|
+
class BrotliFileHandler < WEBrick::HTTPServlet::AbstractServlet
|
14
|
+
def initialize(server, root_dir, debug=false)
|
15
|
+
super(server)
|
16
|
+
@root_dir = root_dir
|
17
|
+
@debug = debug
|
18
|
+
end
|
19
|
+
|
20
|
+
def do_GET(req, res)
|
21
|
+
# 只处理.br结尾的文件
|
22
|
+
unless req.path.end_with?('.br')
|
23
|
+
res.status = 404
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
file_path = File.join(@root_dir, req.path[1..-1])
|
28
|
+
|
29
|
+
if File.exist?(file_path)
|
30
|
+
# 确定原始文件类型
|
31
|
+
base_name = File.basename(req.path, ".br")
|
32
|
+
ext = File.extname(base_name)
|
33
|
+
|
34
|
+
content_type = case ext
|
35
|
+
when ".js" then "application/javascript"
|
36
|
+
when ".wasm" then "application/wasm"
|
37
|
+
when ".data" then "application/octet-stream"
|
38
|
+
when ".json" then "application/json"
|
39
|
+
else "application/octet-stream"
|
40
|
+
end
|
41
|
+
|
42
|
+
# 读取文件
|
43
|
+
file_content = File.binread(file_path)
|
44
|
+
|
45
|
+
# 设置响应
|
46
|
+
res.status = 200
|
47
|
+
res.header["Content-Encoding"] = "br"
|
48
|
+
res.content_type = content_type
|
49
|
+
res.body = file_content
|
50
|
+
else
|
51
|
+
res.status = 404
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|