highway 0.0.1 → 1.0.1

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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/lib/highway.rb +8 -4
  3. data/lib/highway/compiler/analyze/analyzer.rb +249 -0
  4. data/lib/highway/compiler/analyze/tree/root.rb +95 -0
  5. data/lib/highway/compiler/analyze/tree/segments/text.rb +36 -0
  6. data/lib/highway/compiler/analyze/tree/segments/variable.rb +43 -0
  7. data/lib/highway/compiler/analyze/tree/stage.rb +48 -0
  8. data/lib/highway/compiler/analyze/tree/step.rb +69 -0
  9. data/lib/highway/compiler/analyze/tree/values/array.rb +45 -0
  10. data/lib/highway/compiler/analyze/tree/values/base.rb +67 -0
  11. data/lib/highway/compiler/analyze/tree/values/hash.rb +45 -0
  12. data/lib/highway/compiler/analyze/tree/values/primitive.rb +43 -0
  13. data/lib/highway/compiler/analyze/tree/variable.rb +48 -0
  14. data/lib/highway/compiler/build/builder.rb +154 -0
  15. data/lib/highway/compiler/build/output/invocation.rb +70 -0
  16. data/lib/highway/compiler/build/output/manifest.rb +52 -0
  17. data/lib/highway/compiler/parse/parser.rb +92 -0
  18. data/lib/highway/compiler/parse/tree/root.rb +73 -0
  19. data/lib/highway/compiler/parse/tree/step.rb +62 -0
  20. data/lib/highway/compiler/parse/tree/variable.rb +48 -0
  21. data/lib/highway/compiler/parse/versions/v1.rb +110 -0
  22. data/lib/highway/compiler/suite.rb +56 -0
  23. data/lib/highway/environment.rb +282 -0
  24. data/lib/highway/fastlane/action.rb +67 -0
  25. data/lib/highway/interface.rb +135 -0
  26. data/lib/highway/main.rb +173 -0
  27. data/lib/highway/runtime/context.rb +229 -0
  28. data/lib/highway/runtime/report.rb +80 -0
  29. data/lib/highway/runtime/runner.rb +286 -0
  30. data/lib/highway/steps/infrastructure.rb +20 -0
  31. data/lib/highway/steps/library/action.rb +42 -0
  32. data/lib/highway/steps/library/appcenter.rb +106 -0
  33. data/lib/highway/steps/library/appstore.rb +137 -0
  34. data/lib/highway/steps/library/carthage.rb +67 -0
  35. data/lib/highway/steps/library/cocoapods.rb +76 -0
  36. data/lib/highway/steps/library/copy_artifacts.rb +36 -0
  37. data/lib/highway/steps/library/lane.rb +42 -0
  38. data/lib/highway/steps/library/sh.rb +36 -0
  39. data/lib/highway/steps/library/slack.rb +381 -0
  40. data/lib/highway/steps/library/testflight.rb +105 -0
  41. data/lib/highway/steps/library/xcode_archive.rb +162 -0
  42. data/lib/highway/steps/library/xcode_test.rb +264 -0
  43. data/lib/highway/steps/parameters/base.rb +52 -0
  44. data/lib/highway/steps/parameters/compound.rb +141 -0
  45. data/lib/highway/steps/parameters/single.rb +74 -0
  46. data/lib/highway/steps/registry.rb +92 -0
  47. data/lib/highway/steps/step.rb +52 -0
  48. data/lib/highway/steps/types/any.rb +65 -0
  49. data/lib/highway/steps/types/anyof.rb +64 -0
  50. data/lib/highway/steps/types/array.rb +44 -0
  51. data/lib/highway/steps/types/bool.rb +36 -0
  52. data/lib/highway/steps/types/enum.rb +44 -0
  53. data/lib/highway/steps/types/hash.rb +45 -0
  54. data/lib/highway/steps/types/number.rb +38 -0
  55. data/lib/highway/steps/types/set.rb +39 -0
  56. data/lib/highway/steps/types/string.rb +47 -0
  57. data/lib/highway/steps/types/url.rb +35 -0
  58. data/lib/highway/utilities.rb +51 -0
  59. data/lib/highway/version.rb +9 -1
  60. metadata +194 -22
  61. data/.gitignore +0 -4
  62. data/Gemfile +0 -4
  63. data/Rakefile +0 -1
  64. data/highway.gemspec +0 -24
@@ -0,0 +1,105 @@
1
+ #
2
+ # testflight.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "fastlane"
7
+
8
+ require "highway/steps/infrastructure"
9
+
10
+ module Highway
11
+ module Steps
12
+ module Library
13
+
14
+ class TestFlightStep < Step
15
+
16
+ def self.name
17
+ "testflight"
18
+ end
19
+
20
+ def self.parameters
21
+ [
22
+ Parameters::Single.new(
23
+ name: "apple_id",
24
+ required: true,
25
+ type: Types::String.new()
26
+ ),
27
+ Parameters::Single.new(
28
+ name: "app_specific_password",
29
+ required: false,
30
+ type: Types::String.new()
31
+ ),
32
+ Parameters::Single.new(
33
+ name: "password",
34
+ required: false,
35
+ type: Types::String.new()
36
+ ),
37
+ Parameters::Single.new(
38
+ name: "session",
39
+ required: false,
40
+ type: Types::String.new()
41
+ ),
42
+ Parameters::Single.new(
43
+ name: "skip_submission",
44
+ required: false,
45
+ default: true,
46
+ type: Types::Bool.new()
47
+ ),
48
+ Parameters::Single.new(
49
+ name: "skip_waiting_for_build_processing",
50
+ required: false,
51
+ default: true,
52
+ type: Types::Bool.new()
53
+ ),
54
+ Parameters::Single.new(
55
+ name: "team_name",
56
+ required: false,
57
+ type: Types::String.new()
58
+ ),
59
+ Parameters::Single.new(
60
+ name: "username",
61
+ required: true,
62
+ type: Types::String.regex(/^\S+@\S+\.\S+$/)
63
+ )
64
+ ]
65
+ end
66
+
67
+ def self.run(parameters:, context:, report:)
68
+
69
+ password = parameters["password"]
70
+ app_specific_password = parameters["app_specific_password"]
71
+ session = parameters["session"]
72
+
73
+ if password.nil? && app_specific_password.nil?
74
+ context.interface.fatal!("You need to provide an account password or application specific password! Additionally if you have enabled two-step verification, you will need to provide generated session.")
75
+ end
76
+
77
+ username = parameters["username"]
78
+ apple_id = parameters["apple_id"]
79
+ team_name = parameters["team_name"]
80
+ skip_submission = parameters["skip_submission"]
81
+ skip_waiting_for_build_processing = parameters["skip_waiting_for_build_processing"]
82
+
83
+ env = {
84
+ "FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD" => app_specific_password,
85
+ "FASTLANE_PASSWORD" => password,
86
+ "FASTLANE_SESSION" => session
87
+ }
88
+
89
+ context.with_modified_env(env) {
90
+ context.run_action("upload_to_testflight", options: {
91
+ username: username,
92
+ skip_submission: skip_submission,
93
+ skip_waiting_for_build_processing: skip_waiting_for_build_processing,
94
+ apple_id: apple_id,
95
+ team_name: team_name
96
+ })
97
+ }
98
+
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,162 @@
1
+ #
2
+ # xcode_archive.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "fastlane"
7
+ require "xcodeproj"
8
+
9
+ require "highway/steps/infrastructure"
10
+
11
+ module Highway
12
+ module Steps
13
+ module Library
14
+
15
+ class XcodeArchiveStep < Step
16
+
17
+ def self.name
18
+ "xcode_archive"
19
+ end
20
+
21
+ def self.parameters
22
+ [
23
+ Parameters::Single.new(
24
+ name: "clean",
25
+ required: false,
26
+ type: Types::Bool.new(),
27
+ default: true,
28
+ ),
29
+ Parameters::Single.new(
30
+ name: "configuration",
31
+ required: false,
32
+ type: Types::String.new(),
33
+ ),
34
+ Parameters::Single.new(
35
+ name: "flags",
36
+ required: false,
37
+ type: Types::Array.new(Types::String.new()),
38
+ default: [],
39
+ ),
40
+ Parameters::Single.new(
41
+ name: "method",
42
+ required: true,
43
+ type: Types::Enum.new("ad-hoc", "app-store", "development", "developer-id", "enterprise", "package"),
44
+ ),
45
+ Parameters::Single.new(
46
+ name: "project",
47
+ required: true,
48
+ type: Types::AnyOf.new(
49
+ project: Types::String.regex(/.+\.xcodeproj/),
50
+ workspace: Types::String.regex(/.+\.xcworkspace/),
51
+ ),
52
+ ),
53
+ Parameters::Single.new(
54
+ name: "scheme",
55
+ required: true,
56
+ type: Types::String.new(),
57
+ ),
58
+ Parameters::Single.new(
59
+ name: "settings",
60
+ required: false,
61
+ type: Types::Hash.new(Types::String.new(), validate: lambda { |dict| dict.keys.all? { |key| /[A-Z_][A-Z0-9_]*/ =~ key } }),
62
+ default: {},
63
+ ),
64
+ ]
65
+ end
66
+
67
+ def self.run(parameters:, context:, report:)
68
+
69
+ # Interpret the parameters. At this point they are parsed and
70
+ # transformed to be recognizable by Fastlane.
71
+
72
+ clean = parameters["clean"]
73
+ scheme = parameters["scheme"]
74
+ method = parameters["method"]
75
+
76
+ configuration = parameters["configuration"]
77
+ configuration ||= detect_configuration(parameters)
78
+
79
+ flags = parameters["flags"].join(" ")
80
+ settings = parameters["settings"].map { |setting, value| "#{setting}=\"#{value.shellescape}\"" }.join(" ")
81
+
82
+ xcargs = flags + settings
83
+ xcargs = nil if xcargs.empty?
84
+
85
+ project_key = parameters["project"][:tag]
86
+ project_value = parameters["project"][:value]
87
+
88
+ # Prepare artifacts. Create temporary directories, get file names that
89
+ # will be later passed to the build command.
90
+
91
+ output_raw_temp_dir = Dir.mktmpdir()
92
+ output_raw_path = report.prepare_artifact("raw.log")
93
+
94
+ output_ipa_path = report.prepare_artifact("archive.ipa")
95
+ output_ipa_dir = File.dirname(output_ipa_path)
96
+ output_ipa_file = File.basename(output_ipa_path)
97
+
98
+ # Run the build and archival.
99
+
100
+ context.run_action("build_ios_app", options: {
101
+
102
+ project_key => project_value,
103
+
104
+ clean: clean,
105
+ configuration: configuration,
106
+ scheme: scheme,
107
+ export_method: method,
108
+
109
+ xcargs: xcargs,
110
+ export_xcargs: xcargs,
111
+
112
+ buildlog_path: output_raw_temp_dir,
113
+ output_directory: output_ipa_dir,
114
+ output_name: output_ipa_file,
115
+
116
+ })
117
+
118
+ # Save the archive and artifacts subreports in the report.
119
+
120
+ report[:archive] = {
121
+ result: :success
122
+ }
123
+
124
+ report[:artifacts] = {
125
+ ipa: context.fastlane_lane_context[:IPA_OUTPUT_PATH],
126
+ dsym: context.fastlane_lane_context[:DSYM_OUTPUT_PATH],
127
+ }
128
+
129
+ end
130
+
131
+ private
132
+
133
+ def self.detect_configuration(parameters)
134
+ if parameters["project"][:tag] == :project
135
+ detect_configuration_from_project(parameters["project"][:value], parameters["scheme"])
136
+ elsif parameters["project"][:tag] == :workspace
137
+ detect_configuration_from_workspace(parameters["project"][:value], parameters["scheme"])
138
+ end
139
+ end
140
+
141
+ def self.detect_configuration_from_project(project_path, scheme_name)
142
+ return nil unless File.exist?(project_path)
143
+ project_schemes = Xcodeproj::Project.schemes(project_path)
144
+ return nil unless project_schemes.include?(scheme_name)
145
+ scheme_path = File.join(project_path, "xcshareddata", "xcschemes", "#{scheme_name}.xcscheme")
146
+ return nil unless File.exist?(scheme_path)
147
+ Xcodeproj::XCScheme.new(scheme_path).archive_action.build_configuration
148
+ end
149
+
150
+ def self.detect_configuration_from_workspace(workspace_path, scheme_name)
151
+ return nil unless File.exist?(workspace_path)
152
+ workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
153
+ workspace_schemes = workspace.schemes.reject { |k, v| v.include?("Pods/Pods.xcodeproj") }
154
+ return nil unless workspace_schemes.keys.include?(scheme_name)
155
+ detect_configuration_from_project(workspace_schemes[scheme_name], scheme_name)
156
+ end
157
+
158
+ end
159
+
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,264 @@
1
+ #
2
+ # xcode_test.rb
3
+ # Copyright © 2019 Netguru S.A. All rights reserved.
4
+ #
5
+
6
+ require "highway/steps/infrastructure"
7
+
8
+ module Highway
9
+ module Steps
10
+ module Library
11
+
12
+ class XcodeTestStep < Step
13
+
14
+ def self.name
15
+ "xcode_test"
16
+ end
17
+
18
+ def self.parameters
19
+ [
20
+ Parameters::Single.new(
21
+ name: "clean",
22
+ type: Types::Bool.new(),
23
+ required: false,
24
+ default: true,
25
+ ),
26
+ Parameters::Single.new(
27
+ name: "configuration",
28
+ type: Types::String.new(),
29
+ required: false,
30
+ ),
31
+ Parameters::Single.new(
32
+ name: "device",
33
+ type: Types::String.new(),
34
+ required: false,
35
+ ),
36
+ Parameters::Single.new(
37
+ name: "flags",
38
+ type: Types::Array.new(Types::String.new()),
39
+ required: false,
40
+ default: [],
41
+ ),
42
+ Parameters::Single.new(
43
+ name: "project",
44
+ type: Types::AnyOf.new(
45
+ project: Types::String.regex(/.+\.xcodeproj/),
46
+ workspace: Types::String.regex(/.+\.xcworkspace/),
47
+ ),
48
+ required: true,
49
+ ),
50
+ Parameters::Single.new(
51
+ name: "scheme",
52
+ type: Types::String.new(),
53
+ required: true,
54
+ ),
55
+ Parameters::Single.new(
56
+ name: "skip_build",
57
+ type: Types::Bool.new(),
58
+ required: false,
59
+ default: false,
60
+ ),
61
+ Parameters::Single.new(
62
+ name: "settings",
63
+ type: Types::Hash.new(Types::String.new(), validate: lambda { |dict| dict.keys.all? { |key| /[A-Z_][A-Z0-9_]*/ =~ key } }),
64
+ required: false,
65
+ default: {},
66
+ ),
67
+ Parameters::Single.new(
68
+ name: "code_coverage",
69
+ type: Types::Bool.new(),
70
+ required: false,
71
+ )
72
+ ]
73
+ end
74
+
75
+ def self.run(parameters:, context:, report:)
76
+
77
+ # Interpret the parameters. At this point they are parsed and
78
+ # transformed to be recognizable by Fastlane.
79
+
80
+ clean = parameters["clean"]
81
+ configuration = parameters["configuration"]
82
+ device = parameters["device"]
83
+ scheme = parameters["scheme"]
84
+ skip_build = parameters["skip_build"]
85
+ code_coverage = parameters["code_coverage"]
86
+
87
+ flags = parameters["flags"].join(" ")
88
+ settings = parameters["settings"].map { |setting, value| "#{setting}=\"#{value.shellescape}\"" }.join(" ")
89
+
90
+ xcargs = flags + settings
91
+ xcargs = nil if xcargs.empty?
92
+
93
+ project_key = parameters["project"][:tag]
94
+ project_value = parameters["project"][:value]
95
+
96
+ # Prepare artifacts. Create temporary directories, get file names that
97
+ # will be later passed to the build command.
98
+
99
+ output_raw_temp_dir = Dir.mktmpdir()
100
+ output_raw_path = report.prepare_artifact("raw.log")
101
+
102
+ output_html_path = report.prepare_artifact("report.html")
103
+ output_junit_path = report.prepare_artifact("report.junit")
104
+
105
+ output_html_file = File.basename(output_html_path)
106
+ output_junit_file = File.basename(output_junit_path)
107
+
108
+ # Configure xcpretty. Set custom locations of report artifacts so that
109
+ # we can track them accurately.
110
+
111
+ output_dir = context.artifacts_dir
112
+ output_types = ["html", "junit"].join(",")
113
+ output_files = [output_html_file, output_junit_file].join(",")
114
+
115
+ # Prepare temporary variables.
116
+
117
+ report_test = {}
118
+ report_artifacts = {}
119
+ rescued_error = nil
120
+
121
+ # Run the build and test.
122
+
123
+ begin
124
+
125
+ context.run_action("run_tests", options: {
126
+
127
+ project_key => project_value,
128
+
129
+ clean: clean,
130
+ configuration: configuration,
131
+ device: device,
132
+ scheme: scheme,
133
+ code_coverage: code_coverage,
134
+ skip_build: skip_build,
135
+
136
+ xcargs: xcargs,
137
+
138
+ buildlog_path: output_raw_temp_dir,
139
+ output_directory: output_dir,
140
+ output_types: output_types,
141
+ output_files: output_files,
142
+
143
+ })
144
+
145
+ rescue FastlaneCore::Interface::FastlaneBuildFailure => error
146
+
147
+ # A compile error occured. Save it to be re-raised later.
148
+
149
+ report_test[:result] = :error
150
+ rescued_error = error
151
+
152
+ rescue FastlaneCore::Interface::FastlaneTestFailure => error
153
+
154
+ # A test failure occured. Save it to be re-raised later.
155
+
156
+ report_test[:result] = :failure
157
+ rescued_error = error
158
+
159
+ else
160
+
161
+ # Build succeeded!
162
+
163
+ report_test[:result] = :success
164
+
165
+ end
166
+
167
+ # Now the real fun begins. Move the raw xcodebuild log from temporary
168
+ # directory to artifacts directory.
169
+
170
+ output_raw_temp_path = Dir.glob(File.join(output_raw_temp_dir, "*.log")).first
171
+ unless output_raw_temp_path.nil?
172
+ FileUtils.mv(output_raw_temp_path, output_raw_path)
173
+ end
174
+
175
+ # Save the artifact paths in the subreport.
176
+
177
+ report_artifacts[:raw] = output_raw_path
178
+ report_artifacts[:html] = output_html_path
179
+ report_artifacts[:junit] = output_junit_path
180
+
181
+ # Load the raw log and pipe it through xcpretty with a JSON formatter.
182
+ # That will output a machine-readable information about everything
183
+ # that happened in the build.
184
+
185
+ xcpretty_json_formatter_path = context.run_sh("xcpretty-json-formatter", bundle_exec: true, silent: true)
186
+ temp_json_report_path = File.join(Dir.mktmpdir(), "report.json")
187
+
188
+ context.with_modified_env({"XCPRETTY_JSON_FILE_OUTPUT" => temp_json_report_path}) do
189
+ context.run_sh(["cat", output_raw_path, "| xcpretty --formatter", xcpretty_json_formatter_path], bundle_exec: true, silent: true)
190
+ end
191
+
192
+ # Export JSON file report to environment variable
193
+
194
+ context.env["XCODE_TEST_JSON_REPORT_PATH"] = temp_json_report_path
195
+
196
+ # Load the build report and a JUnit report into memory.
197
+
198
+ junit_report = Scan::TestResultParser.new.parse_result(File.read(output_junit_path))
199
+ if File.exist?(temp_json_report_path)
200
+ xcode_report = JSON.parse(File.read(temp_json_report_path))
201
+ else
202
+ xcode_report = Hash.new()
203
+ end
204
+
205
+ # Extract test numbers from JUnit report.
206
+
207
+ report_test_count = {}
208
+
209
+ report_test_count[:all] = junit_report[:tests]
210
+ report_test_count[:failed] = junit_report[:failures]
211
+ report_test_count[:succeeded] = report_test_count[:all] - report_test_count[:failed]
212
+
213
+ report_test[:count] = report_test_count
214
+
215
+ # Extract compile errors from the build report.
216
+
217
+ report_test_errors = []
218
+
219
+ report_test_errors += xcode_report.fetch("file_missing_errors", []).map { |entry|
220
+ {location: File.basename(entry["file_path"]), reason: entry["reason"]}
221
+ }
222
+
223
+ report_test_errors += xcode_report.fetch("compile_errors", []).map { |entry|
224
+ {location: File.basename(entry["file_path"]), reason: entry["reason"]}
225
+ }
226
+
227
+ report_test_errors += xcode_report.fetch("undefined_symbols_errors", []).map { |entry|
228
+ {location: entry["symbol"], reason: entry["message"]}
229
+ }
230
+
231
+ report_test_errors += xcode_report.fetch("format_duplicate_symbols", []).map {
232
+ {location: nil, reason: entry["message"]}
233
+ }
234
+
235
+ report_test_errors += xcode_report.fetch("errors", []).map { |entry|
236
+ {location: nil, reason: entry}
237
+ }
238
+
239
+ report_test[:errors] = report_test_errors
240
+
241
+ # Extract test failures from the build report.
242
+
243
+ report_test_failures = xcode_report.fetch("tests_failures", {}).values.flatten.map { |entry|
244
+ {location: entry["test_case"], reason: entry["reason"]}
245
+ }
246
+
247
+ report_test[:failures] = report_test_failures
248
+
249
+ # Save the test and artifacts subreports in the report.
250
+
251
+ report[:test] = report_test
252
+ report[:artifacts] = report_artifacts
253
+
254
+ # Re-raise the error after the report is finally prepared.
255
+
256
+ raise rescued_error if rescued_error != nil
257
+
258
+ end
259
+
260
+ end
261
+
262
+ end
263
+ end
264
+ end