xcactivitylog 0.1.0 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc90141684fae80212880fb48cfb68fa36773c2d27428df4f1e2e7eccb89d988
4
- data.tar.gz: 074f11361b4312d978ac19e14fb6f4c56fc958308866a92f63c0f4a2f4fddac0
3
+ metadata.gz: 86a13d7e86ed544e21d0bf9a1a84fb981671f8100160ec82fb59ba48ee0eb270
4
+ data.tar.gz: '0333681b5f17012ba1ac5850256c81965901262a93357449852874afe5bac997'
5
5
  SHA512:
6
- metadata.gz: 450ac2460c8789d8efba482f009ff7813fe5e238b5f647bc6fe1bbf9ec249a364920aed7c2fce81a13c74c8c30d54407fdff2d981451bf78740b6c8f0bd90dc8
7
- data.tar.gz: f5711987622f8ea0589f4b8249867d447923ebbe6b775384c3113eec42b39960618d2472efecf196e7b9b04f61c8422912b36429c414605139a8d485912cc114
6
+ metadata.gz: 1eb8f4baed133593e91db5554b0d2dd02e1fd52d5b2f7b1cf2a6e957438490a3cb0349c51ed273b36a533fe5730981f5b5fb2cb692c2673f3a00380c5263b108
7
+ data.tar.gz: 2beb9f7c54b34ba0c91a37a044732b53d14e7657127a8aa6219ae1bbc057838fe1bf29b0db8ae3eab2480cce5a3e5f44ed62a2c1061866ed9c0d2c71c507a098
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -40,53 +40,62 @@ module SLF0
40
40
  @class_deserializer = class_deserializer
41
41
  end
42
42
 
43
- def int(reason = nil)
44
- shift(Int, reason).value
43
+ def int(&reason_blk)
44
+ shift(Int, &reason_blk).value
45
45
  end
46
46
 
47
- def string(reason = nil)
48
- return if shift_nil?(reason)
47
+ def string(&reason_blk)
48
+ return if shift_nil?(&reason_blk)
49
49
 
50
- shift(String, reason).value
50
+ shift(String, &reason_blk).value
51
51
  end
52
52
 
53
- def double(reason = nil)
53
+ def double(&reason_blk)
54
54
  return if shift_nil?
55
55
 
56
- shift(Double, reason).value
56
+ shift(Double, &reason_blk).value
57
57
  end
58
58
 
59
- def object_list(reason = nil)
60
- return if shift_nil?(reason)
59
+ def object_list(&reason_blk)
60
+ return if shift_nil?(&reason_blk)
61
61
 
62
- shift(ObjectList, reason).length.times.map { object(reason && "object in object list for #{reason}") }
62
+ length = shift(ObjectList, &reason_blk).length
63
+ Array.new(length) do
64
+ object { reason_blk && "object #{length} in object list for #{reason_blk&.call}" }
65
+ end
63
66
  end
64
67
 
65
- def object(reason = nil)
66
- deserializer_for(shift(ClassNameRef, reason).value)[self]
68
+ def object(&reason_blk)
69
+ return if shift_nil?(&reason_blk)
70
+
71
+ deserializer_for(shift(ClassNameRef, &reason_blk).value)[self]
67
72
  end
68
73
 
69
74
  def deserializer_for(class_ref_num)
70
75
  @class_deserializer[class_ref_num].last
71
76
  end
72
77
 
73
- def shift_nil?(reason = nil)
74
- shift(ObjectListNil, reason, raise: false)
78
+ def shift_nil?(&reason_blk)
79
+ shift(ObjectListNil, raise: false, &reason_blk)
75
80
  end
76
81
 
77
- def shift(cls, reason = nil, raise: true)
82
+ def shift(cls, raise: true, &reason_blk)
78
83
  unless (token = @tokens.shift)
79
84
  raise 'no more tokens'
80
85
  end
81
86
 
82
87
  unless token.is_a?(cls)
83
- raise "expected #{cls} got #{token.inspect} for #{reason}" if raise
88
+ unexpected_token!(cls, token, &reason_blk) if raise
84
89
 
85
90
  @tokens.unshift(token)
86
91
  return
87
92
  end
88
93
  token
89
94
  end
95
+
96
+ def unexpected_token!(expected_class, token, &reason_blk)
97
+ raise "expected #{expected_class} got #{token.inspect} for #{reason_blk&.call}"
98
+ end
90
99
  end
91
100
  end
92
101
  end
@@ -24,7 +24,7 @@ module SLF0
24
24
  def tokenize_body!
25
25
  body = []
26
26
  until scanner.eos?
27
- object = tokenize_field || tokenize_double_field || tokenize_object_list_nil
27
+ object = tokenize_double_field || tokenize_field || tokenize_object_list_nil
28
28
  raise "malformed no object: #{scanner.rest.inspect}\n\nafter: #{body.inspect}" unless object
29
29
 
30
30
  body << object
@@ -39,11 +39,11 @@ module SLF0
39
39
  when '#'
40
40
  SLF0::Token::Int.new int
41
41
  when '%'
42
- SLF0::Token::ClassName.new scanner.scan(/.{#{int}}/).freeze
42
+ SLF0::Token::ClassName.new scan_length(int).freeze
43
43
  when '@'
44
44
  SLF0::Token::ClassNameRef.new int
45
45
  when '"'
46
- SLF0::Token::String.new scanner.scan(/.{#{int}}/).tr("\r", "\n").freeze
46
+ SLF0::Token::String.new scan_length(int).tr("\r", "\n").freeze
47
47
  when '('
48
48
  SLF0::Token::ObjectList.new int
49
49
  else
@@ -52,8 +52,14 @@ module SLF0
52
52
  end
53
53
  end
54
54
 
55
+ def scan_length(length)
56
+ string = scanner.string[scanner.pos, length]
57
+ scanner.pos += length
58
+ string
59
+ end
60
+
55
61
  def tokenize_double_field
56
- return unless (hex = scanner.scan(/[0-9a-fA-F]*\^/)&.chomp('^'))
62
+ return unless (hex = scanner.scan(/\h*\^/)&.chop)
57
63
 
58
64
  double = [hex.to_i(16)].pack('Q<').unpack1('G')
59
65
  SLF0::Token::Double.new double
@@ -2,8 +2,8 @@
2
2
 
3
3
  module XCActivityLog
4
4
  class SerializedObject
5
- Attribute = Struct.new(:name, :type, :first_version, :last_version)
6
- def self.attribute(name, type, first_version = 0, last_version = 99_999)
5
+ Attribute = Struct.new(:name, :type, :first_version, :first_version_without)
6
+ def self.attribute(name, type, first_version = 0, first_version_without = 99_999)
7
7
  attr_reader name
8
8
  alias_method "#{name}?", name if type == :boolean
9
9
  if type == :time
@@ -12,7 +12,7 @@ module XCActivityLog
12
12
  time.to_i * 1_000_000 + time.usec
13
13
  end
14
14
  end
15
- attributes << Attribute.new(name, type, first_version, last_version).freeze
15
+ attributes << Attribute.new(name, type, first_version, first_version_without).freeze
16
16
  end
17
17
 
18
18
  def self.attributes
@@ -47,6 +47,47 @@ module XCActivityLog
47
47
  end
48
48
 
49
49
  class IDEActivityLogSection < SerializedObject
50
+ class Severity
51
+ protected
52
+
53
+ attr_reader :severity
54
+
55
+ public
56
+
57
+ def initialize(severity)
58
+ @severity = severity
59
+ freeze
60
+ end
61
+
62
+ SUCCESS = new(0)
63
+ WARNING = new(1)
64
+ ERROR = new(2)
65
+ TEST_FAILURE = new(3)
66
+
67
+ def to_s
68
+ case severity
69
+ when 0
70
+ 'Success'
71
+ when 1
72
+ 'Warning'
73
+ when 2
74
+ 'Error'
75
+ when 3
76
+ 'Test Failure'
77
+ else
78
+ "Unknown (#{severity})"
79
+ end
80
+ end
81
+
82
+ include Comparable
83
+
84
+ def <=>(other)
85
+ severity <=> other.severity
86
+ end
87
+ end
88
+
89
+ TargetInfo = Struct.new(:name, :configuration, :workspace, keyword_init: true)
90
+
50
91
  include Enumerable
51
92
  def each(&blk)
52
93
  return enum_for(__method__) unless block_given?
@@ -55,9 +96,82 @@ module XCActivityLog
55
96
  subsections&.each { |s| s.each(&blk) }
56
97
  end
57
98
 
99
+ def each_with_parent(parent: nil, &blk)
100
+ return enum_for(__method__) unless block_given?
101
+
102
+ yield self, parent
103
+ subsections&.each { |s| s.each_with_parent(parent: self, &blk) }
104
+ end
105
+
58
106
  def duration_usec
59
107
  time_stopped_recording_usec - time_started_recording_usec
60
108
  end
109
+
110
+ def target_info(parent: nil)
111
+ parent&.target_info ||
112
+ (title =~ /=== BUILD TARGET (.+?) OF PROJECT (.+?) WITH CONFIGURATION (.+?) ===/ &&
113
+ TargetInfo.new(name: Regexp.last_match(1), configuration: Regexp.last_match(3), workspace: Regexp.last_match(2)))
114
+ end
115
+
116
+ def each_trace_event
117
+ thread_id_map_by_section_type = Hash.new { |h, k| h[k] = [] }
118
+ each_with_parent.sort_by { |s, _| s.time_started_recording }.each do |section, parent|
119
+ thread_id_map = thread_id_map_by_section_type[section.section_type]
120
+ best_thread, thread_id = thread_id_map.each_with_index.select do |thread, _tid|
121
+ section.time_started_recording > thread.last.time_stopped_recording
122
+ end.min_by do |thread, _tid|
123
+ (section.time_started_recording - thread.last.time_stopped_recording) +
124
+ (thread.last.time_stopped_recording - thread_id_map.map(&:last).map(&:time_stopped_recording).min)
125
+ end
126
+ unless thread_id
127
+ thread_id = thread_id_map.size
128
+ best_thread = []
129
+ thread_id_map << best_thread
130
+ end
131
+ best_thread << section
132
+
133
+ yield(section: section, parent: parent, thread_id: thread_id)
134
+ end
135
+ end
136
+
137
+ def write_chrome_trace_file(section_type:, to:)
138
+ to << '{"traceEvents":[' << "\n"
139
+ written_comma = false
140
+ each_trace_event do |section:, parent:, thread_id:|
141
+ case section.section_type
142
+ when section_type
143
+ if written_comma
144
+ to << ",\n"
145
+ else
146
+ written_comma = true
147
+ end
148
+ require 'json'
149
+ to << JSON.generate(
150
+ pid: section.section_type.to_s,
151
+ tid: thread_id,
152
+ ts: section.time_started_recording_usec,
153
+ ph: 'X',
154
+ name: section.title.dup&.force_encoding('UTF-8'),
155
+ dur: section.duration_usec,
156
+ args: {
157
+ subtitle: section.subtitle.dup&.force_encoding('UTF-8'),
158
+ target: section.target_info(parent: parent).to_h,
159
+ severity: section.severity
160
+ }
161
+ )
162
+ end
163
+ end
164
+
165
+ to << "\n]}"
166
+
167
+ to
168
+ end
169
+
170
+ def severity
171
+ severity = (messages || []).reduce(Severity::SUCCESS) { |a, e| [a, Severity.new(e.severity)].max }
172
+ (subsections || []).reduce(severity) { |a, e| [a, e.severity].max }
173
+ end
174
+
61
175
  attribute :section_type, :int
62
176
  attribute :domain_type, :string
63
177
  attribute :title, :string
@@ -75,13 +189,28 @@ module XCActivityLog
75
189
  attribute :command_detail_description, :string
76
190
  attribute :unique_identifier, :string
77
191
  attribute :localized_result_string, :string
78
- attribute :xcbuild_signature, :string
79
- attribute :collect_metrics, :boolean, 9
192
+ attribute :xcbuild_signature, :string, 8
193
+ attribute :collect_metrics, :boolean, 9, 10
80
194
  attributes.freeze
81
195
  end
82
196
  class IDECommandLineBuildLog < IDEActivityLogSection
83
197
  attributes.freeze
84
198
  end
199
+ class IDEActivityLogCommandInvocationSection < IDEActivityLogSection
200
+ attributes.freeze
201
+ end
202
+ class IDEActivityLogUnitTestSection < IDEActivityLogSection
203
+ attribute :tests_passes, :string
204
+ attribute :duration, :string
205
+ attribute :summary, :string
206
+ attribute :suite_name, :string
207
+ attribute :test_name, :string
208
+ attribute :performance_test_output, :string
209
+ attributes.freeze
210
+ end
211
+ class IDEActivityLogMajorGroupSection < IDEActivityLogSection
212
+ attributes.freeze
213
+ end
85
214
 
86
215
  class IDEActivityLogMessage < SerializedObject
87
216
  include Enumerable
@@ -98,7 +227,7 @@ module XCActivityLog
98
227
  attribute :submessages, :object_list
99
228
  attribute :severity, :int
100
229
  attribute :type, :string
101
- attribute :location, :object
230
+ attribute :location, :document_location
102
231
  attribute :category_identifier, :string
103
232
  attribute :secondary_locations, :object_list
104
233
  attribute :additional_description, :string
@@ -107,6 +236,34 @@ module XCActivityLog
107
236
  class IDEClangDiagnosticActivityLogMessage < IDEActivityLogMessage
108
237
  attributes.freeze
109
238
  end
239
+ class IDEActivityLogAnalyzerResultMessage < IDEActivityLogMessage
240
+ attribute :result_type, :string
241
+ attribute :key_event_index, :int
242
+ attributes.freeze
243
+ end
244
+ class IDEActivityLogAnalyzerStepMessage < IDEActivityLogMessage
245
+ attribute :parent_index, :int
246
+ attributes.freeze
247
+ end
248
+ class IDEActivityLogAnalyzerEventStepMessage < IDEActivityLogAnalyzerStepMessage
249
+ attribute :result_type, :string
250
+ attribute :key_event_index, :int
251
+ attributes.freeze
252
+ end
253
+ class IDEActivityLogAnalyzerControlFlowStepMessage < IDEActivityLogAnalyzerStepMessage
254
+ attribute :end_location, :document_location
255
+ attribute :edges, :object_list
256
+ attributes.freeze
257
+ end
258
+ class IDEActivityLogAnalyzerWarningMessage < IDEActivityLogMessage
259
+ attributes.freeze
260
+ end
261
+
262
+ class IDEActivityLogAnalyzerControlFlowStepEdge < SerializedObject
263
+ attribute :start_location, :document_location
264
+ attribute :end_location, :document_location
265
+ attributes.freeze
266
+ end
110
267
 
111
268
  class DVTDocumentLocation < SerializedObject
112
269
  attribute :document_url_string, :string
@@ -123,7 +280,7 @@ module XCActivityLog
123
280
  attribute :ending_line_number, :int
124
281
  attribute :ending_column_number, :int
125
282
  attribute :character_range, :nsrange
126
- attribute :location_encoding, :int
283
+ attribute :location_encoding, :int, 7
127
284
  attributes.freeze
128
285
  end
129
286
  end
@@ -8,7 +8,7 @@ module XCActivityLog
8
8
  class S < SLF0::Token::Stream
9
9
  def initialize(tokens, class_deserializer:)
10
10
  super(tokens, class_deserializer: class_deserializer)
11
- @version = int('activity log version')
11
+ @version = int { 'activity log version' }
12
12
  end
13
13
 
14
14
  def deserializer_for(class_ref_num)
@@ -24,34 +24,38 @@ module XCActivityLog
24
24
  def deserialize_instance_of(stream, cls)
25
25
  instance = cls.new
26
26
  cls.attributes.each do |attr|
27
- next if attr.first_version > @version || attr.last_version < @version
27
+ next if attr.first_version > @version || attr.first_version_without <= @version
28
28
 
29
- value = stream.send(attr.type, "#{attr.name} for #{cls.name.split('::').last}")
29
+ value = stream.send(attr.type) { "#{attr.name} for #{cls.name.split('::').last} #{instance.inspect}" }
30
30
  instance.instance_variable_set(:"@#{attr.name}", value)
31
31
  end
32
32
  instance.freeze
33
33
  end
34
34
 
35
- def boolean(reason = nil)
36
- int(reason) != 0
35
+ def boolean(&reason_blk)
36
+ int(&reason_blk) != 0
37
37
  end
38
38
 
39
- def nsrange(_reason = nil)
39
+ def nsrange(&_reason_blk)
40
40
  deserialize_instance_of(self, NSRange)
41
41
  end
42
42
 
43
- def document_location(reason = nil)
43
+ def document_location(&reason_blk)
44
44
  return if shift_nil?
45
45
 
46
- object(reason).tap do |o|
47
- raise "expected location, got #{o.class.name} for #{reason}" unless o.is_a?(DVTDocumentLocation)
46
+ object(&reason_blk).tap do |o|
47
+ raise "expected location, got #{o.class.name} for #{reason_blk&.call}" unless o.is_a?(DVTDocumentLocation)
48
48
  end
49
49
  end
50
50
 
51
51
  EPOCH = Time.new(2001, 1, 1, 0, 0, 0, '+00:00').freeze
52
52
 
53
- def time(reason = nil)
54
- EPOCH.+(double(reason)).freeze
53
+ def time(&reason_blk)
54
+ EPOCH.+(double(&reason_blk)).freeze
55
+ end
56
+
57
+ def unexpected_token!(expected_class, token, &reason_blk)
58
+ raise "expected #{expected_class} got #{token.inspect} for #{reason_blk&.call} (XCActivityLog version #{@version})"
55
59
  end
56
60
  end
57
61
  def make_stream(tokens, class_deserializer)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcactivitylog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Giddins
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-14 00:00:00.000000000 Z
11
+ date: 2020-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,7 +52,7 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
- description:
55
+ description:
56
56
  email:
57
57
  - segiddins@segiddins.me
58
58
  executables: []
@@ -76,7 +76,7 @@ licenses:
76
76
  metadata:
77
77
  homepage_uri: https://github.com/segiddins/xcactivitylog
78
78
  source_code_uri: https://github.com/segiddins/xcactivitylog
79
- post_install_message:
79
+ post_install_message:
80
80
  rdoc_options: []
81
81
  require_paths:
82
82
  - lib
@@ -91,9 +91,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
91
  - !ruby/object:Gem::Version
92
92
  version: '0'
93
93
  requirements: []
94
- rubyforge_project:
95
- rubygems_version: 2.7.6
96
- signing_key:
94
+ rubygems_version: 3.0.1
95
+ signing_key:
97
96
  specification_version: 4
98
97
  summary: Parse Xcode's xcactivitylog files (and other SLF0-serialized files)
99
98
  test_files: []