cocoapods-jsource 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1f41012354f907c69ab430275b225d557aae7816be39059850418b839aafda6
4
- data.tar.gz: 8f1e481b717aae28942f3e153da93e147499524c883fc6c3fd11f664044d491f
3
+ metadata.gz: 67322efccb2959369205ff4e95f974670f0f0dd9fc3a1b9f60bb94584958a42b
4
+ data.tar.gz: e51f5e1d5bb70389a5d7ba501c7e5f32a4b3c468bb02727fdfa64c67874a5819
5
5
  SHA512:
6
- metadata.gz: 6113c095233da159a99fad3311a1c999e377e64b4eaf4835ae3a5d7cedf687b0d8b0294c8c8e5cc8e66176b11d6b43d41f68bff094a90ec2cd029f9cad9d9733
7
- data.tar.gz: dcf0a3f39a72152b9b985cb83340f85a487e0f4c1a6aaf27783eef3f95622514c1ec97525b8802921086ac076480fe0dadc8f5e3255f64c07be0ce6686a60643
6
+ metadata.gz: 958bfe53fad947326aa12c53f313ff2f4cbbb0f0ad2ce6ff013c63a18226299237af9a989a6ebf419c8ee0542277cf1ae0d001ce204d0767e39735ee78da62fc
7
+ data.tar.gz: 29158cd9629837b9189e8ea7dfc8ff5a0b71b763eeafee391f41fe7da8d2ac6780fae8fa5871f04495153b00f24dc64ea4d4318bb7dad10b33169d5207aa3e05
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://gems.ruby-china.com/"
2
2
 
3
3
  # Specify your gem's dependencies in cocoapods-jsource.gemspec
4
4
  gemspec
@@ -1,31 +1,34 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cocoapods-jsource (0.0.2)
4
+ cocoapods-jsource (0.0.3)
5
5
  cocoapods
6
6
 
7
7
  GEM
8
- remote: https://rubygems.org/
8
+ remote: https://gems.ruby-china.com/
9
9
  specs:
10
- CFPropertyList (3.0.0)
10
+ CFPropertyList (3.0.1)
11
11
  activesupport (4.2.11.1)
12
12
  i18n (~> 0.7)
13
13
  minitest (~> 5.1)
14
14
  thread_safe (~> 0.3, >= 0.3.4)
15
15
  tzinfo (~> 1.1)
16
+ algoliasearch (1.27.1)
17
+ httpclient (~> 2.8, >= 2.8.3)
18
+ json (>= 1.5.1)
16
19
  atomos (0.1.3)
17
20
  bacon (1.2.0)
18
- claide (1.0.2)
19
- cocoapods (1.7.5)
21
+ claide (1.0.3)
22
+ cocoapods (1.8.4)
20
23
  activesupport (>= 4.0.2, < 5)
21
24
  claide (>= 1.0.2, < 2.0)
22
- cocoapods-core (= 1.7.5)
25
+ cocoapods-core (= 1.8.4)
23
26
  cocoapods-deintegrate (>= 1.0.3, < 2.0)
24
27
  cocoapods-downloader (>= 1.2.2, < 2.0)
25
28
  cocoapods-plugins (>= 1.0.0, < 2.0)
26
29
  cocoapods-search (>= 1.0.0, < 2.0)
27
30
  cocoapods-stats (>= 1.0.0, < 2.0)
28
- cocoapods-trunk (>= 1.3.1, < 2.0)
31
+ cocoapods-trunk (>= 1.4.0, < 2.0)
29
32
  cocoapods-try (>= 1.1.0, < 2.0)
30
33
  colored2 (~> 3.1)
31
34
  escape (~> 0.0.4)
@@ -34,18 +37,20 @@ GEM
34
37
  molinillo (~> 0.6.6)
35
38
  nap (~> 1.0)
36
39
  ruby-macho (~> 1.4)
37
- xcodeproj (>= 1.10.0, < 2.0)
38
- cocoapods-core (1.7.5)
40
+ xcodeproj (>= 1.11.1, < 2.0)
41
+ cocoapods-core (1.8.4)
39
42
  activesupport (>= 4.0.2, < 6)
43
+ algoliasearch (~> 1.0)
44
+ concurrent-ruby (~> 1.1)
40
45
  fuzzy_match (~> 2.0.4)
41
46
  nap (~> 1.0)
42
47
  cocoapods-deintegrate (1.0.4)
43
- cocoapods-downloader (1.2.2)
48
+ cocoapods-downloader (1.3.0)
44
49
  cocoapods-plugins (1.0.0)
45
50
  nap
46
51
  cocoapods-search (1.0.0)
47
- cocoapods-stats (1.0.0)
48
- cocoapods-trunk (1.3.1)
52
+ cocoapods-stats (1.1.0)
53
+ cocoapods-trunk (1.4.1)
49
54
  nap (>= 0.8, < 2.0)
50
55
  netrc (~> 0.11)
51
56
  cocoapods-try (1.1.0)
@@ -55,12 +60,12 @@ GEM
55
60
  fourflusher (2.3.1)
56
61
  fuzzy_match (2.0.4)
57
62
  gh_inspector (1.1.3)
63
+ httpclient (2.8.3)
58
64
  i18n (0.9.5)
59
65
  concurrent-ruby (~> 1.0)
60
- metaclass (0.0.4)
61
- minitest (5.11.3)
62
- mocha (1.9.0)
63
- metaclass (~> 0.0.1)
66
+ json (2.2.0)
67
+ minitest (5.13.0)
68
+ mocha (1.10.1)
64
69
  mocha-on-bacon (0.2.3)
65
70
  mocha (>= 0.13.0)
66
71
  molinillo (0.6.6)
@@ -69,12 +74,12 @@ GEM
69
74
  netrc (0.11.0)
70
75
  prettybacon (0.0.2)
71
76
  bacon (~> 1.2)
72
- rake (12.3.2)
77
+ rake (13.0.1)
73
78
  ruby-macho (1.4.0)
74
79
  thread_safe (0.3.6)
75
80
  tzinfo (1.2.5)
76
81
  thread_safe (~> 0.1)
77
- xcodeproj (1.11.0)
82
+ xcodeproj (1.13.0)
78
83
  CFPropertyList (>= 2.3.3, < 4.0)
79
84
  atomos (~> 0.1.3)
80
85
  claide (>= 1.0.2, < 2.0)
@@ -39,7 +39,6 @@ module Pod
39
39
  path
40
40
  end
41
41
 
42
-
43
42
  def cache_file
44
43
  cache_path = cache_dir
45
44
  return cache_path + "jsource.yaml"
@@ -55,6 +54,21 @@ module Pod
55
54
  cache_dict
56
55
  end
57
56
 
57
+ def version_from_path(component_name, path)
58
+ cache_dict = cache_object
59
+ version = nil
60
+ cache_dict.each do |name, verision_hash|
61
+ verision_hash.each do |version_string, detail_hash|
62
+ return version unless detail_hash.include? :source_paths
63
+ detail_hash[:source_paths].each do |binary_name, source_path|
64
+ return version_string if source_path == path and binary_name == component_name
65
+ end
66
+ end
67
+ end
68
+ return version
69
+ end
70
+
71
+
58
72
  def dump_to_yaml(hash)
59
73
  File.open(cache_file, "wb+") {|f| YAML.dump(hash, f) }
60
74
  end
@@ -1,6 +1,7 @@
1
-
2
1
  require "cocoapods-core/lockfile"
3
2
  require 'fileutils'
3
+ require 'cocoapods-jsource/command/xcode_manager'
4
+ require 'cocoapods'
4
5
 
5
6
  module Pod
6
7
  class Command
@@ -12,85 +13,286 @@ module Pod
12
13
  Add source code debugging capabilities to binary.
13
14
  DESC
14
15
 
15
- def self.options
16
- [
17
- ['--names', 'component names to be added'],
18
- ['--gits', 'component git url to be added'],
19
- ].concat(super)
20
- end
16
+ self.arguments = [
17
+ CLAide::Argument.new('NAMES', true),
18
+ ]
21
19
 
22
20
  def initialize(argv)
23
- @namesString = argv.option('names')
21
+ @namesString = argv.shift_argument
24
22
  @names = @namesString.split(',') unless @namesString.nil?
25
- @gitsString = argv.option('gits')
26
- @gits = @gitsString.split(',') unless @gitsString.nil?
23
+ @cache_dict = cache_object
24
+ @manager = XcodeManager.new(argv)
25
+ @remote = argv.flag?('remote')
26
+ @index = -1
27
27
  super
28
28
  end
29
29
 
30
+ def self.options
31
+ [
32
+ ['--remote', 'add components from internet'],
33
+ ].concat(super)
34
+ end
35
+
30
36
  def validate!
31
37
  super
32
- help! 'component name is required.' unless @names.length > 0
33
- help! 'component git url is required.' unless @gits.length > 0
34
- help! 'names number must be equal to gits number' unless @names.length == @gits.length
38
+ help! 'component name is required.' if @namesString.nil?
39
+ #help! 'component git url is required.' unless @gits.length > 0
40
+ #help! 'names number must be equal to gits number' unless @names.length == @gits.length
35
41
  help! 'podfile.lock file is required. you need pod install/update' unless File.exist? config.lockfile_path
36
42
  end
37
43
 
44
+ def have_cached(component_name, version, subspecs)
45
+ return false unless @cache_dict.has_key? component_name
46
+ pod_cache_dict = @cache_dict[component_name]
47
+ return false unless pod_cache_dict.has_key? version
48
+ return false unless pod_cache_dict[version].has_key? :source_paths
49
+ source_path_hash = pod_cache_dict[version][:source_paths]
50
+ return false unless source_path_hash.length > 0
51
+ if subspecs.length > 0
52
+ tem_list = subspecs & (source_path_hash.keys - [component_name])
53
+ return false unless tem_list == subspecs
54
+ subspecs.each do |binary_name|
55
+ return false unless source_path_hash.keys.include? binary_name
56
+ dir_path = source_path_hash[binary_name]
57
+ return false unless File.exist? dir_path
58
+ end
59
+ else
60
+ source_path_hash.each do |binary_name, dir_path|
61
+ return false unless File.exist? dir_path
62
+ end
63
+ end
64
+ return true
65
+ end
66
+
67
+ def spec_with_name(name, version)
68
+ set = config.sources_manager.search(Dependency.new(name, version))
69
+ if set
70
+ path = set.specification_paths_for_version(Version.new(version)).first
71
+ spec = Specification.from_file(path)
72
+ spec.root
73
+ else
74
+ raise Informative, "Unable to find a specification for `#{name}`"
75
+ end
76
+ end
77
+
78
+ def download_component_to_path(component_name, version, source_path_hash={})
79
+ source_path_hash.each do |binary_name, source_path|
80
+ sandbox_path = source_path.split("Pods")[0] + "Pods"
81
+ sandbox_component_path = "#{sandbox_path}/#{component_name}"
82
+ binary_path = "#{sandbox_path}/#{binary_name}"
83
+ if File.exist? sandbox_component_path
84
+ UI.puts "using #{binary_name} #{version}"
85
+ UI.puts "\t#{source_path.to_s}"
86
+ FileUtils.copy_entry(sandbox_component_path, binary_path) unless File.exist? binary_path
87
+ else
88
+ UI.puts "downloading #{binary_name} #{version}"
89
+ UI.puts "\t #{source_path.to_s}"
90
+ FileUtils.mkdir_p [binary_path] unless File.exist? binary_path
91
+ sandbox = Sandbox.new(sandbox_path)
92
+ spec = spec_with_name(component_name, version)
93
+ specs = { :ios => [spec] }
94
+ installer = Installer::PodSourceInstaller.new(sandbox, config.podfile, specs, :can_cache => true)
95
+ installer.install!
96
+ #installer.clean!
97
+ # TODO validtarget
98
+ # 改名
99
+ if binary_name != component_name and File.exist? sandbox_component_path
100
+ FileUtils.copy_entry(sandbox_component_path, binary_path) unless File.exist? binary_name
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def local_have_source_files(component_name)
107
+ files = Dir::glob "Pods/#{component_name}/**/*.m"
108
+ if files and files.length > 0
109
+ return true
110
+ end
111
+ return false
112
+ end
113
+
114
+ def source_files(component_name)
115
+ return nil unless local_have_source_files component_name
116
+ souces = []
117
+ Dir::foreach "Pods/#{component_name}" do |path|
118
+ next if path.include? "Frameworks"
119
+ souces << path
120
+ end
121
+ souces
122
+ end
123
+
124
+ def get_file_list(path)
125
+ list = []
126
+ Dir.entries(path).each do |sub|
127
+ if sub != '.' && sub != '..'
128
+ if File.directory?("#{path}/#{sub}")
129
+ list << "#{path}/#{sub}"
130
+ list = list + get_file_list("#{path}/#{sub}")
131
+ else
132
+ list << "#{path}/#{sub}"
133
+ end
134
+ end
135
+ end
136
+ list
137
+ end
138
+
139
+ def local_component_to_path(component_name, version, source_paths_hash)
140
+ path = "Pods/#{component_name}"
141
+ # TODO 没有源码状态
142
+ if !File.exist? path
143
+ # copy files to source_paths
144
+ UI.puts "本地目录不存在,请执行pod install/update 或者输入远程仓库地址"
145
+ exit 1
146
+ end
147
+ #file_list = get_file_list path
148
+ source_paths_hash.each do |binary_name, source_path_list|
149
+ if source_path_list.length > 0
150
+ dest_file_path = source_path_list[0].split("Pods")[0] + "Pods/#{binary_name}"
151
+ UI.puts "copying #{binary_name} to #{dest_file_path}"
152
+ end
153
+ source_path_list.each do |dest_file_path|
154
+ origin_file = dest_file_path.split("Pods/#{binary_name}")[-1]
155
+ origin_file_path = path + origin_file
156
+ if !File.exist? origin_file_path
157
+ UI.warn "本地不存在#{origin_file_path}, 可能使用了虚拟subspec或者本地源码缓存有问题。推荐加上 --remote 参数"
158
+ exit 1
159
+ end
160
+ if File.directory? origin_file_path
161
+ FileUtils.mkdir_p [dest_file_path], :mode => 0700 unless File.exist? dest_file_path
162
+ else
163
+ parent_dir = File.dirname dest_file_path
164
+ FileUtils.mkdir_p [parent_dir], :mode => 0700 unless File.exist? parent_dir
165
+ FileUtils.copy origin_file_path, dest_file_path unless File.exist? dest_file_path
166
+ h_origin_file_path = origin_file_path.gsub(/(mm|m|c)$/, "h")
167
+ h_dest_file_path = dest_file_path.gsub(/(mm|m|c)$/, "h")
168
+ parent_dir = File.dirname h_dest_file_path
169
+ FileUtils.mkdir_p [parent_dir], :mode => 0700 unless File.exist? parent_dir
170
+ FileUtils.copy h_origin_file_path, h_dest_file_path unless File.exist? h_dest_file_path
171
+ end
172
+ end
173
+ end
174
+
175
+ end
176
+
177
+
178
+ def component_cache(component_name)
179
+ if @cache_dict.has_key? component_name
180
+ component_cache_dict = @cache_dict[component_name]
181
+ end
182
+ component_cache_dict
183
+ end
184
+
185
+
186
+ def local_source_paths(component_name, subspecs, source_paths_hash)
187
+ local_hash = {}
188
+ source_paths_hash.each do |binary_name, source_paths|
189
+ need_add = false
190
+ if binary_name != component_name
191
+ if subspecs.include? binary_name
192
+ need_add = true
193
+ end
194
+ if subspecs.length == 0
195
+ need_add = true
196
+ end
197
+ else
198
+ need_add = true
199
+ end
200
+ local_hash[binary_name] = source_paths if need_add
201
+ end
202
+ local_hash
203
+ end
204
+
205
+
206
+ def unite_source_paths_hash(source_paths_hash)
207
+ source_path_hash = {}
208
+ source_paths_hash.each do |binary_name, source_paths|
209
+ source_path_hash[binary_name] = source_paths[0].split("Pods")[0] + "Pods/#{binary_name}" if source_paths.length > 0
210
+ end
211
+ source_path_hash
212
+ end
213
+
38
214
  def run
39
- # 获取当前目录
40
- index = 0
41
- cache_dict = cache_object
215
+ # 在Pods 目录下创建Debug.xcodeproj 文件。
216
+ debug_xcodeproj = @manager.generate_debug_xcodeproj
217
+
218
+ # 获取源码,并添加到工程里。
42
219
  @names.each do |component_name|
43
- pod_cache_dict = {}
44
- if cache_dict.has_key? component_name
45
- pod_cache_dict = cache_dict[component_name]
46
- end
47
- version = component_version component_name
220
+ @index = @index + 1
221
+ version, subspecs = component_info component_name
48
222
  if version.length == 0
49
223
  UI.puts "#{component_name} 找不到对应的版本信息,不做任何处理"
50
224
  next
51
225
  end
52
- if pod_cache_dict.has_key? version
53
- UI.puts "#{component_name} #{version} 已经存在,缓存为 #{pod_cache_dict[version][:sourcelist].to_s}"
54
- next
55
- end
56
- git = @gits[index]
57
- # 获取subspec
58
- source_file_list = get_source_file_list_from_binary(component_name)
59
- if source_file_list.length == 0
60
- UI.puts "#{component_name} 找不到对应的二进制,不做任何处理"
61
- next
62
- end
63
- UI.puts "开始下载源码..."
64
- for source_path in source_file_list
65
- create_working_directory source_path
66
- cmd ="git clone -b #{component_name}-#{version} --depth 1 #{git} #{source_path} >/dev/null 2>&1"
67
- `#{cmd}`
226
+ source_path_hash = {}
227
+ if have_cached component_name, version, subspecs
228
+ UI.puts "Using #{component_name} #{version} in #{@cache_dict[component_name][version][:source_paths]}"
229
+ source_path_hash = @cache_dict[component_name][version][:source_paths]
230
+ else
231
+ source_paths_hash = source_paths_from_binary(component_name)
232
+ if source_paths_hash.length == 0
233
+ UI.puts "#{component_name} 找不到对应的二进制,不做任何处理"
234
+ next
235
+ end
236
+ source_paths_hash = local_source_paths component_name, subspecs, source_paths_hash
237
+ source_path_hash = unite_source_paths_hash source_paths_hash
238
+ if @remote
239
+ # 需要把值合并。
240
+ download_component_to_path component_name, version, source_path_hash
241
+ else
242
+ local_component_to_path component_name, version, source_paths_hash
243
+ end
244
+
68
245
  end
69
- pod_cache_dict[version] = {"git":git, "sourcelist":source_file_list, "version":version}
70
- cache_dict[component_name] = pod_cache_dict
71
- UI.puts "#{component_name} 源码创建成功,目录为 #{source_file_list.to_s}"
72
- index = index + 1
246
+
247
+ # add_component_to_debug
248
+ ENV["IS_SOURCE"] = "1"
249
+ spec = spec_with_name component_name, version
250
+ @manager.add_component_to_debug component_name, source_path_hash, debug_xcodeproj, spec
251
+ # add_component_to_cache
252
+ pod_cache_dict = {}
253
+ pod_cache_dict[version] = {"source_paths":source_path_hash, "version":version}
254
+ #if @use_remote
255
+ # pod_cache_dict[version]["git"] = @gits[@index]
256
+ #end
257
+ @cache_dict[component_name] = pod_cache_dict
258
+ UI.puts "#{component_name} 源码创建成功,目录为 #{source_path_hash.to_s}"
73
259
  end
74
- dump_to_yaml cache_dict
260
+ @manager.add_debug_to_workspace
261
+ dump_to_yaml @cache_dict
75
262
  end
76
263
 
77
- def component_version(component_name)
264
+ def component_info(component_name)
78
265
  return unless File.exist? config.lockfile_path
79
266
  version=""
80
267
  lockfile = Lockfile.from_file config.lockfile_path
81
268
  dependencies = lockfile.internal_data["DEPENDENCIES"]
269
+ subspecs = []
82
270
  dependencies.each do |dependency|
271
+ if !dependency.include? "(="
272
+ UI.puts "podfile中 #{component_name} 可能没有指定版本,需要指定确定的版本才能使用。"
273
+ exit 1
274
+ end
83
275
  list = dependency.split(" (")
84
276
  name = list[0]
277
+ if name.include? "/"
278
+ name_list = name.split("/")
279
+ subspec = name.gsub(/\//,"")
280
+ name = name_list[0]
281
+ end
85
282
  next unless name == component_name
86
- version = list[-1].sub(")", "").sub("=", "").sub(" ", "")
283
+ version = list[-1].split("/")[0].sub(")", "").sub("=", "").sub(" ", "") if version == ""
284
+ subspecs << subspec if subspec
285
+ spec = spec_with_name component_name, version
286
+ spec.default_subspecs.each do |subspec_spec|
287
+ subspecs = subspecs | ["#{component_name}#{subspec_spec}"]
288
+ end
87
289
  end
88
- version
290
+ return version, subspecs
89
291
  end
90
292
 
91
293
  # 根据组件名获取组件的源码调试地址
92
- def get_source_file_list_from_binary(component_name)
93
- source_file_list = []
294
+ def source_paths_from_binary(component_name)
295
+ source_path_hash = {}
94
296
  component_pod_path = config.sandbox_root + component_name
95
297
  binary_path_list = `find #{component_pod_path} -name "#{component_name}*" -type l`.strip.split("\n").sort
96
298
  binary_hash = {}
@@ -102,29 +304,38 @@ module Pod
102
304
  end
103
305
  if binary_hash.length == 0
104
306
  UI.puts "#{component_name} 找不到二进制组件或者找不到对应的版本信息,不做任何处理"
105
- return source_file_list
307
+ exit 1
106
308
  end
107
309
 
108
310
  binary_hash.each do |binary_name, binary_path|
109
311
  libbinary_file_name = "lib#{component_name}.a"
110
- source_path_list = []
111
- UI.puts "正在解析二进制#{binary_path}源码位置"
312
+ at_name_list = []
112
313
  if binary_name.to_s.end_with? libbinary_file_name
113
314
  # .a 文件
114
- source_path_list = `dwarfdump -arch x86_64 #{binary_path} | grep 'AT_name.*#{binary_name}'`.strip.split("\n").sort
315
+ at_name_list = `dwarfdump -arch x86_64 #{binary_path} | grep 'AT_name.*#{binary_name}'`.strip.split("\n").sort
115
316
  else
116
317
  # framework 文件
117
- source_path_list = `dwarfdump -arch x86_64 #{binary_path} | grep 'DW_AT_name.*#{binary_name}'`.strip.split("\n").sort
318
+ at_name_list = `dwarfdump -arch x86_64 #{binary_path} | grep 'DW_AT_name.*#{binary_name}'`.strip.split("\n").sort
118
319
  end
119
- source_path_list.each do |tmp_source_path|
320
+ #if source_file.length == 0
321
+ # UI.puts "在#{binary_path} 里没有找到合适的调试信息~"
322
+ # next
323
+ #end
324
+ source_list = []
325
+ at_name_list.each do |tmp_source_path|
120
326
  if tmp_source_path.include?("Pods/#{binary_name}")
121
- source_path = tmp_source_path.strip.split("(\"")[-1].split(binary_name)[0] + "#{binary_name}"
122
- source_file_list << source_path if source_path.to_s.length > 0
123
- break
327
+ source_path = tmp_source_path.strip.split("(\"")[-1].split("\")")[0]
328
+ source_list << source_path if source_path.to_s.length > 0
124
329
  end
125
330
  end
331
+ if source_list.length == 0
332
+ UI.puts "#{component_name} 没有找到调试信息, 可能是早期打的组件。建议这个组件重新生成。"
333
+ exit 1
334
+ else
335
+ source_path_hash[binary_name] = source_list
336
+ end
126
337
  end
127
- source_file_list
338
+ source_path_hash
128
339
  end
129
340
 
130
341
 
@@ -1,4 +1,3 @@
1
-
2
1
  require "cocoapods-core/lockfile"
3
2
  require 'fileutils'
4
3
 
@@ -6,10 +5,22 @@ module Pod
6
5
  class Command
7
6
  class Jsource < Command
8
7
  class Clean < Jsource
9
- self.summary = 'Add source debugging function'
8
+ self.summary = 'Clean source debugging function'
10
9
 
11
10
  self.description = <<-DESC
12
- Clean the cache of the jsource(s) whose name matches `NAME`.
11
+ Remove the cache for a given pod, or clear the cache completely.
12
+
13
+ If you want to clean Debug_xcodeproj, please add "NAME",
14
+ if you want to clean all, please add "—all"
15
+
16
+ If you want to clear the cache, add "--cache":
17
+
18
+ If there is multiple cache for various versions of the requested pod, you
19
+ will be asked which one to clean. Use `--all` to clean them all.
20
+
21
+ If you do not give a pod `NAME`, you need to specify the `--all` flag
22
+ (this is to avoid cleaning all the cache by mistake).
23
+
13
24
  DESC
14
25
 
15
26
  self.arguments = [
@@ -18,46 +29,57 @@ module Pod
18
29
 
19
30
  def self.options
20
31
  [
21
- ['--all', 'Remove all the cached pods without asking'],
32
+ ['--all', 'Remove all the project debug pods without asking'],
33
+ ['--cache', 'Remove all the cached pods without asking']
22
34
  ].concat(super)
23
35
  end
24
36
 
25
37
  def initialize(argv)
26
38
  @pod_name = argv.shift_argument
27
39
  @wipe_all = argv.flag?('all')
40
+ @wipe_cache = argv.flag?('cache')
28
41
  @cache_dict = cache_object
42
+ @manager = XcodeManager.new(argv)
29
43
  super
30
44
  end
31
45
 
32
46
  def validate!
33
47
  super
34
- if @pod_name.nil? && !@wipe_all
35
- # Security measure, to avoid removing the pod cache too agressively by mistake
36
- help! 'You should either specify a pod name or use the --all flag'
48
+ if @pod_name.nil?
49
+ help! 'You should use the --all flag' if @wipe_all.nil?
37
50
  end
38
51
  end
39
52
 
40
53
  def run
41
54
  if @pod_name.nil?
42
55
  # Note: at that point, @wipe_all is always true (thanks to `validate!`)
43
- # Remove all
44
- clear_cache
56
+ if @wipe_all
57
+ if @wipe_cache
58
+ clear_cache
59
+ else
60
+ @manager.clean_debug
61
+ end
62
+ end
45
63
  else
46
64
  # Remove only cache for this pod
47
- cache_descriptors = @cache_dict[@pod_name].values
48
- if cache_descriptors.nil?
49
- UI.notice("No cache for pod named #{@pod_name} found")
50
- elsif cache_descriptors.count > 1 && !@wipe_all
51
- # Ask which to remove
52
- choices = cache_descriptors.map { |c| "#{@pod_name} v#{c[:version]}" }
53
- index = UI.choose_from_array(choices, 'Which pod cache do you want to remove?')
54
- remove_caches([cache_descriptors[index]])
65
+ if @wipe_cache
66
+ cache_descriptors = @cache_dict[@pod_name].values
67
+ if cache_descriptors.nil?
68
+ UI.notice("No cache for pod named #{@pod_name} found")
69
+ elsif cache_descriptors.count > 1 && !@wipe_all
70
+ # Ask which to remove
71
+ choices = cache_descriptors.map { |c| "#{@pod_name} v#{c[:version]}" }
72
+ index = UI.choose_from_array(choices, 'Which pod cache do you want to remove?')
73
+ # 删除debug
74
+ remove_caches([cache_descriptors[index]])
75
+ else
76
+ # Remove all found cache of this pod
77
+ remove_caches(cache_descriptors)
78
+ end
55
79
  else
56
- # Remove all found cache of this pod
57
- remove_caches(cache_descriptors)
80
+ @manager.remove_component_from_debug(@pod_name)
58
81
  end
59
82
  end
60
- dump_to_yaml @cache_dict
61
83
  end
62
84
 
63
85
  private
@@ -70,11 +92,11 @@ module Pod
70
92
  #
71
93
  def remove_caches(cache_descriptors)
72
94
  cache_descriptors.each do |desc|
73
- sourcelist = desc[:sourcelist]
74
- next if sourcelist.length == 0
75
- sourcelist.each do |source|
76
- UI.puts "Removing cache #{source} (#{desc[:version]})"
77
- parent = source.split("Pods")[0]
95
+ source_paths = desc[:source_paths]
96
+ next if source_paths.length == 0
97
+ source_paths.each do |binary_name, source_path|
98
+ UI.puts "Removing cache #{source_path} (#{desc[:version]})"
99
+ parent = source_path.split("Pods")[0]
78
100
  FileUtils.rm_rf(parent) if File.exist? parent
79
101
  end
80
102
  if @cache_dict[@pod_name].has_key? desc[:version]
@@ -85,30 +107,27 @@ module Pod
85
107
  end
86
108
  end
87
109
  end
110
+ dump_to_yaml @cache_dict
88
111
  end
89
112
 
90
113
  def clear_cache
91
114
  @cache_dict.each do |pod_name, version_dict|
92
115
  version_dict.each do |version, pod_dict|
93
- git=""
94
- source=""
95
- git=pod_dict[:git] if pod_dict.has_key? :git
96
- sourcelist=pod_dict[:sourcelist] if pod_dict.has_key? :sourcelist
97
- sourcelist.each do |source|
98
- UI.message("Removing the #{pod_name} jsource cache dir #{cache_dir}") do
99
- parent = source.split("Pods")[0]
116
+ source_paths=pod_dict[:source_paths] if pod_dict.has_key? :source_paths
117
+ source_paths.each do |binary_name, source_path|
118
+ UI.message("Removing the #{binary_name} jsource cache dir #{source_path}") do
119
+ parent = source_path.split("Pods")[0]
100
120
  FileUtils.rm_rf(parent) if File.exist? parent
101
121
  end
102
122
  end
103
-
104
123
  end
105
124
  end
106
125
  UI.message("Removing the jsource configuration dir #{cache_file}") do
107
126
  FileUtils.rm_rf(cache_file)
108
127
  @cache_dict = {}
109
128
  end
129
+ dump_to_yaml @cache_dict
110
130
  end
111
-
112
131
  end
113
132
  end
114
133
  end
@@ -17,15 +17,18 @@ module Pod
17
17
  ]
18
18
 
19
19
  def self.options
20
- [[
21
- '--short', 'Only print the path relative to the cache root'
22
- ]].concat(super)
20
+ [
21
+ ['--short', 'Only print the path relative to the cache root'],
22
+ ['--cache', 'Only print the cached pods']
23
+ ].concat(super)
23
24
  end
24
25
 
25
26
  def initialize(argv)
26
27
  @pod_name = argv.shift_argument
27
28
  @short_output = argv.flag?('short')
29
+ @pod_cache = argv.flag?('cache')
28
30
  @cache_dict = cache_object
31
+ @manager = XcodeManager.new(argv)
29
32
  super
30
33
  end
31
34
 
@@ -33,9 +36,19 @@ module Pod
33
36
  return if @cache_dict.nil?
34
37
  result = ""
35
38
  if @pod_name
36
- result = @cache_dict[@pod_name] if @cache_dict.has_key? @pod_name
39
+ if @pod_cache
40
+ result = @cache_dict[@pod_name] if @cache_dict.has_key? @pod_name
41
+ else
42
+ # 获取debug里 这个pod_name 的关键信息
43
+ result = @manager.component_in_debug @pod_name
44
+ end
37
45
  else
38
- result = @cache_dict
46
+ if @pod_cache
47
+ result = @cache_dict
48
+ else
49
+ # 获取debug里所有的group信息。
50
+ result = @manager.all_components_in_debug
51
+ end
39
52
  end
40
53
  if @short_output
41
54
  result = result.keys
@@ -0,0 +1,218 @@
1
+ require 'xcodeproj'
2
+ require 'cocoapods'
3
+ require 'cocoapods-jsource/command/xcodeproj_extern'
4
+
5
+ module Pod
6
+ class Command
7
+ class Jsource < Command
8
+ class XcodeManager < Jsource
9
+
10
+ def initialize(argv)
11
+ @debug_path = 'Pods/Debug.xcodeproj'
12
+ @workspace_name = `ls | grep .xcworkspace`.strip
13
+ @cache_dict = cache_object
14
+ super
15
+ end
16
+
17
+ def validate!
18
+ super
19
+ help! '请先pod install/update 之后,在运行这个命令' if !File.exist? @workspace_name or !File.exist? "Pods"
20
+ end
21
+
22
+ def generate_debug_xcodeproj()
23
+ debug_xcodeproj = get_debug_xcodeproj
24
+ if debug_xcodeproj.nil?
25
+ debug_xcodeproj = Xcodeproj::Project.new(@debug_path)
26
+ # 去掉Frameworks和Products
27
+ #debug_xcodeproj.groups.each do |component_group|
28
+ # component_group.clear
29
+ # component_group.remove_from_project
30
+ #end
31
+ end
32
+ debug_xcodeproj
33
+ end
34
+
35
+ def get_debug_xcodeproj()
36
+ debug_xcodeproj = nil
37
+ if File.exist? @debug_path
38
+ debug_xcodeproj = Xcodeproj::Project.open(@debug_path)
39
+ end
40
+ debug_xcodeproj
41
+ end
42
+
43
+ def add_files_to_group(debug_xcodeproj, group, files=[])
44
+ Dir.foreach(group.real_path).sort_by { |object| object.to_s }.each do |entry|
45
+ filePath = File.join(group.real_path, entry)
46
+ next unless files.length == 0 or files.include? filePath
47
+ ext_name = File.extname(entry)
48
+ # 过滤目录和.DS_Store文件
49
+ if !File.directory?(filePath) && entry != ".DS_Store" then
50
+ # 向group中增加文件引用
51
+ group.new_reference(filePath)
52
+ # 目录情况下, 递归添加
53
+ elsif File.directory?(filePath) && entry != '.' && entry != '..' then
54
+ hierarchy_path = group.hierarchy_path[1, group.hierarchy_path.length]
55
+ subGroup = debug_xcodeproj.main_group.find_subpath(hierarchy_path + '/' + entry, true)
56
+ subGroup.set_source_tree('<group>')
57
+ subGroup.set_path(group.real_path + entry)
58
+ add_files_to_group(debug_xcodeproj, subGroup, files)
59
+ end
60
+ end
61
+ end
62
+
63
+ # 感觉没啥必要
64
+ def include_component(component_name, source_path, debug_xcodeproj)
65
+ debug_xcodeproj.groups.each do |group|
66
+ return true if group.name == component_name and File.exist? group.path
67
+ end
68
+ return false
69
+ end
70
+
71
+
72
+ def all_components_in_debug()
73
+ component_info = {}
74
+ debug_xcodeproj = get_debug_xcodeproj
75
+ if debug_xcodeproj.nil?
76
+ return component_info
77
+ end
78
+ debug_xcodeproj.groups.each do |component_group|
79
+ detail_hash = {}
80
+ source_path_hash = {}
81
+ source_path_hash[component_group.display_name] = component_group.real_path.to_s
82
+ detail_hash[:source_paths] = source_path_hash
83
+ version = version_from_path component_group.display_name, component_group.real_path.to_s
84
+ detail_hash[:version] = version if version
85
+ component_info[component_group.display_name] = detail_hash if detail_hash.length > 0
86
+ end
87
+ component_info
88
+ end
89
+
90
+ def component_in_debug(component_name)
91
+ all_component_hash = all_components_in_debug
92
+ component_hash = {}
93
+ all_component_hash.each do |binary_name, detail_hash|
94
+ if binary_name.start_with? component_name
95
+ component_hash[binary_name] = detail_hash
96
+ end
97
+ end
98
+ component_hash
99
+ end
100
+
101
+ def avaliable_dirs(file_path, dest_file_path)
102
+ dir_list = []
103
+ if file_path.to_s == dest_file_path.to_s
104
+ return dir_list
105
+ else
106
+ parent_dir = File.dirname file_path
107
+ dir_list << parent_dir
108
+ dir_list = dir_list | avaliable_dirs(parent_dir, dest_file_path)
109
+ end
110
+ dir_list
111
+ end
112
+
113
+ def avaliable_file(group, spec)
114
+ component_name = spec.name
115
+ subspec = group.name.gsub(/#{component_name}/, "")
116
+ files = []
117
+ if subspec
118
+ spec.subspecs.each do |subspec_spec|
119
+ next unless subspec_spec.name == "#{component_name}/#{subspec}"
120
+ source_files = "#{group.real_path}/#{subspec_spec.attributes_hash["source_files"]}"
121
+ tmp_files = Dir.glob (source_files)
122
+ # 找到所有的文件夹
123
+ tmp_files.each do |file_path|
124
+ files << file_path
125
+ parent_path = File.dirname file_path
126
+ if !files.include? parent_path
127
+ files = files | avaliable_dirs(file_path, group.real_path)
128
+ end
129
+ end
130
+ break
131
+ end
132
+ end
133
+ files
134
+ end
135
+
136
+ def add_component_to_debug(component_name, source_path_hash, debug_xcodeproj, spec)
137
+ source_path_hash.each do |binary_name, source_path|
138
+ # 将源码添加到Debug.xcodeproj 里。
139
+ UI.puts "add #{binary_name} to Debug.xcodeproj"
140
+ component_group = debug_xcodeproj.main_group.find_subpath(binary_name, true)
141
+ component_group.set_source_tree('<absolute>')
142
+ component_group.set_path(source_path)
143
+ component_group.clear
144
+ files = avaliable_file component_group, spec
145
+ add_files_to_group(debug_xcodeproj, component_group, files)
146
+ end
147
+ debug_xcodeproj.save
148
+ end
149
+
150
+ def remove_component_from_debug(component_name)
151
+ debug_xcodeproj = get_debug_xcodeproj
152
+ if debug_xcodeproj.nil?
153
+ return
154
+ end
155
+ debug_xcodeproj.groups.each do |component_group|
156
+ if component_group.display_name.start_with? component_name
157
+ UI.puts "removing #{component_group.display_name} from Debug.xcodeproj"
158
+ if !component_group.empty?
159
+ component_group.clear
160
+ component_group.remove_from_project
161
+ end
162
+ end
163
+ end
164
+ debug_xcodeproj.save
165
+ end
166
+
167
+ def clean_debug()
168
+ debug_xcodeproj = get_debug_xcodeproj
169
+ remove_debug_from_workspace
170
+ if debug_xcodeproj
171
+ FileUtils.rm_rf [@debug_path]
172
+ end
173
+
174
+ end
175
+
176
+ def component_count_in_debug(debug_xcodeproj)
177
+ count = debug_xcodeproj.groups.length
178
+ if count >= 2
179
+ return count - 2
180
+ else
181
+ UI.puts "获取component的个数可能发成错误"
182
+ end
183
+ return count
184
+ end
185
+
186
+ def add_debug_to_workspace()
187
+ # 获取主工程的名字。
188
+ if File.exist? @workspace_name
189
+ workspace = Xcodeproj::Workspace.new_from_xcworkspace @workspace_name
190
+ else
191
+ UI.puts "找不到对应的workspace: #{@workspace_name},请检查。"
192
+ exit 1
193
+ end
194
+ if !workspace.schemes.values.include? File.realdirpath @debug_path
195
+ workspace << @debug_path
196
+ workspace.save_as @workspace_name
197
+ end
198
+ end
199
+
200
+ def remove_debug_from_workspace()
201
+ if File.exist? @workspace_name
202
+ workspace = Xcodeproj::Workspace.new_from_xcworkspace @workspace_name
203
+ else
204
+ UI.puts "找不到对应的workspace: #{@workspace_name},请检查。"
205
+ exit 1
206
+ end
207
+ if workspace.schemes.values.include? File.realdirpath @debug_path
208
+ workspace >> @debug_path
209
+ workspace.save_as @workspace_name
210
+ end
211
+ end
212
+
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+
@@ -0,0 +1,34 @@
1
+ require 'xcodeproj'
2
+ require "rexml/xpath"
3
+
4
+ module Xcodeproj
5
+ # Provides support for generating, reading and serializing Xcode Workspace
6
+ # documents.
7
+ #
8
+ class Workspace
9
+
10
+ # removes a new path to the list of the of projects contained in the
11
+ # workspace.
12
+ # @param [String, Xcodeproj::Workspace::FileReference] path_or_reference
13
+ # A string or Xcode::Workspace::FileReference containing a path to an Xcode project
14
+ #
15
+ # @raise [ArgumentError] Raised if the input is neither a String nor a FileReference
16
+ #
17
+ # @return [void]
18
+ #
19
+ def >>(path_or_reference)
20
+ return unless @document && @document.respond_to?(:root)
21
+ debug_element = nil
22
+ @document.elements.each("*/FileRef") do |element|
23
+ location = element.attributes["location"]
24
+ if location == "group:#{path_or_reference}"
25
+ debug_element = element
26
+ end
27
+ end
28
+ @document.root.delete_element(debug_element)
29
+ load_schemes_from_project File.expand_path(path_or_reference)
30
+ end
31
+ end
32
+
33
+ end
34
+
@@ -1,3 +1,3 @@
1
1
  module CocoapodsJsource
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/test.rb CHANGED
@@ -1,7 +1,131 @@
1
+ require 'xcodeproj'
2
+ require 'cocoapods'
1
3
 
2
- `cd /Users/handa/Documents/lianjia/C/lianjia_ios_platc;pod jsource add --names=Lianjia_Beike_Home --gits=http://gerrit.lianjia.com/mobile_ios/Lianjia_Beike_Home`
3
-
4
+ `cd /Users/handa/Downloads/Example;pod jsource add --help`
5
+ #`cd /Users/handa/Downloads/Example;pod jsource clean LJRefresh --cache`
6
+ #`cd /Users/handa/Documents/lianjia/C/lianjia_ios_platc;pod jsource add LJBaseToolKit`
7
+ #`cd /Users/handa/Downloads/Example;pod jsource add LJRefresh http://gerrit.lianjia.com/mobile_ios/LJRefresh`
8
+ #
4
9
 
5
10
  # `cd /Users/handa/Documents/test/testZSource;pod jsource list`
6
11
 
7
- # `cd /Users/handa/Documents/test/testZSource;pod jsource clean LJCache`
12
+ # `cd /Users/handa/Documents/test/testZSource;pod jsource clean LJCache`
13
+ #
14
+ #`cd /Users/handa/Documents/lianjia/LJBaseContext/Example; pod update`
15
+
16
+ # require 'cocoapods-core'
17
+ # spec = Pod::Specification.from_file("/Users/handa/Documents/lianjia/LJPlatBPodSpecs/LJMessengerSDK/2.30.3.0/LJMessengerSDK.podspec")
18
+
19
+
20
+ def addFilesToGroup(project, aTarget, aGroup)
21
+ Dir.foreach(aGroup.real_path) do |entry|
22
+ filePath = File.join(aGroup.real_path, entry)
23
+ # 过滤目录和.DS_Store文件
24
+ if !File.directory?(filePath) && entry != ".DS_Store" then
25
+ # 向group中增加文件引用
26
+ fileReference = aGroup.new_reference(filePath)
27
+ # 如果不是头文件则继续增加到Build Phase中,PB文件需要加编译标志
28
+ if filePath.to_s.end_with?("pbobjc.m", "pbobjc.mm") then
29
+ aTarget.add_file_references([fileReference], '-fno-objc-arc')
30
+ elsif filePath.to_s.end_with?(".m", ".mm", ".cpp") then
31
+ aTarget.source_build_phase.add_file_reference(fileReference, true)
32
+ elsif filePath.to_s.end_with?(".plist") then
33
+ aTarget.resources_build_phase.add_file_reference(fileReference, true)
34
+ end
35
+ # 目录情况下, 递归添加
36
+ elsif File.directory?(filePath) && entry != '.' && entry != '..' then
37
+ hierarchy_path = aGroup.hierarchy_path[1, aGroup.hierarchy_path.length]
38
+ subGroup = project.main_group.find_subpath(hierarchy_path + '/' + entry, true)
39
+ subGroup.set_source_tree('<group>')
40
+ subGroup.set_path(aGroup.real_path + entry)
41
+ addFilesToGroup(project, aTarget, subGroup)
42
+ end
43
+ end
44
+ end
45
+
46
+
47
+
48
+ def addFilesToGroupNew(project, aGroup)
49
+ Dir.foreach(aGroup.real_path) do |entry|
50
+ filePath = File.join(aGroup.real_path, entry)
51
+ # 过滤目录和.DS_Store文件
52
+ if !File.directory?(filePath) && entry != ".DS_Store" then
53
+ # 向group中增加文件引用
54
+ aGroup.new_reference(filePath)
55
+ # 目录情况下, 递归添加
56
+ elsif File.directory?(filePath) && entry != '.' && entry != '..' then
57
+ hierarchy_path = aGroup.hierarchy_path[1, aGroup.hierarchy_path.length]
58
+ subGroup = project.main_group.find_subpath(hierarchy_path + '/' + entry, true)
59
+ subGroup.set_source_tree('<group>')
60
+ subGroup.set_path(aGroup.real_path + entry)
61
+ addFilesToGroupNew(project,subGroup)
62
+ end
63
+ end
64
+ end
65
+
66
+ # 添加Pods/JSources.xcodeproj
67
+
68
+ # 在Pods/JSources.xcodeproj 添加group Pods
69
+ #
70
+ # 在Pods目录添加每个组件的调试信息。
71
+ #
72
+ # 如果不指定-u 则用的是本地的源码,如果指定-u,则用的是网络里的源码。
73
+ #
74
+ #
75
+ #
76
+ #project_path = '/Users/handa/Documents/lianjia/cocoapods-jsource/test.xcodeproj'
77
+ #project = ""
78
+ #if File.exist? project_path
79
+ # project = Xcodeproj::Project.open(project_path)
80
+ #else
81
+ # project = Xcodeproj::Project.new(project_path)
82
+ #end
83
+ #
84
+ #path = "/var/folders/qb/qcgb09sx36l65jj4vglxz3nw0000gq/T/cocoapods-uru6ziwd/Pods/LJRefresh/LJRefresh/"
85
+ #exampleGroup=project.main_group.new_group("LJRefresh", path)
86
+ #exampleGroup.set_source_tree('<absolute>')
87
+ ##target = project.new_target(:application,"LJRefresh",:ios)
88
+ #addFilesToGroupNew(project, exampleGroup)
89
+ #
90
+ #
91
+ #
92
+ #project.save
93
+ #
94
+ #
95
+ #new_path = "/Users/handa/Downloads/Example/"
96
+ #file = "Example.xcworkspace"
97
+ #Dir.chdir(new_path)
98
+ ##workspace = Xcodeproj::Workspace.new_from_xcworkspace(file)
99
+ #
100
+ #ref = Xcodeproj::Workspace::FileReference.new(project_path)
101
+ ##if workspace.include? ref
102
+ ## print "already included"
103
+ ##else
104
+ ## workspace << project_path
105
+ ##end
106
+ ##workspace.save_as(file)
107
+ #
108
+ #new_path = "/Users/handa/Downloads/Example/Pods/Pods.xcodeproj"
109
+ #if File.exist? new_path
110
+ # pod_project = Xcodeproj::Project.open(new_path)
111
+ #end
112
+
113
+
114
+ #
115
+ #`mkdir Pods/JSources`
116
+ #jsource_group=pod_project.main_group.new_group("JSources", "./Pods/JSources")
117
+ #
118
+ #
119
+ #jsource_group.add_referrer(ref)
120
+ #
121
+ #
122
+ #
123
+ #pod_project.save
124
+
125
+
126
+
127
+ #config = Pod::Config.instance
128
+ #installer = Pod::Installer.new(config.sandbox, config.podfile, config.lockfile)
129
+ #installer.repo_update = false
130
+ #installer.update = false
131
+ #installer.generated_pod_targets
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocoapods-jsource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - handa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-04 00:00:00.000000000 Z
11
+ date: 2020-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cocoapods
@@ -72,6 +72,8 @@ files:
72
72
  - lib/cocoapods-jsource/command/jsource/add.rb
73
73
  - lib/cocoapods-jsource/command/jsource/clean.rb
74
74
  - lib/cocoapods-jsource/command/jsource/list.rb
75
+ - lib/cocoapods-jsource/command/xcode_manager.rb
76
+ - lib/cocoapods-jsource/command/xcodeproj_extern.rb
75
77
  - lib/cocoapods-jsource/gem_version.rb
76
78
  - lib/cocoapods_plugin.rb
77
79
  - spec/command/jsource_spec.rb