fastlane-plugin-sapfire 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +135 -0
- data/lib/fastlane/plugin/sapfire/actions/app_certification_action.rb +79 -0
- data/lib/fastlane/plugin/sapfire/actions/associate_ms_store_action.rb +263 -0
- data/lib/fastlane/plugin/sapfire/actions/build_uwp_app_action.rb +55 -0
- data/lib/fastlane/plugin/sapfire/actions/dotnet_select_action.rb +46 -0
- data/lib/fastlane/plugin/sapfire/actions/ensure_dotnet_version_action.rb +123 -0
- data/lib/fastlane/plugin/sapfire/actions/ms_credentials_action.rb +91 -0
- data/lib/fastlane/plugin/sapfire/actions/msbuild_action.rb +45 -0
- data/lib/fastlane/plugin/sapfire/actions/msbuild_select_action.rb +53 -0
- data/lib/fastlane/plugin/sapfire/actions/nuget_pack_action.rb +46 -0
- data/lib/fastlane/plugin/sapfire/actions/update_uwp_signing_settings_action.rb +73 -0
- data/lib/fastlane/plugin/sapfire/actions/upload_ms_store_action.rb +218 -0
- data/lib/fastlane/plugin/sapfire/actions/upload_nuget_action.rb +95 -0
- data/lib/fastlane/plugin/sapfire/actions_base/msbuild_action_base.rb +42 -0
- data/lib/fastlane/plugin/sapfire/helper/ms_devcenter_helper.rb +292 -0
- data/lib/fastlane/plugin/sapfire/helper/sapfire_helper.rb +55 -0
- data/lib/fastlane/plugin/sapfire/msbuild/module.rb +34 -0
- data/lib/fastlane/plugin/sapfire/msbuild/options.rb +123 -0
- data/lib/fastlane/plugin/sapfire/msbuild/runner.rb +171 -0
- data/lib/fastlane/plugin/sapfire/sln_project/block.rb +30 -0
- data/lib/fastlane/plugin/sapfire/sln_project/global_block.rb +147 -0
- data/lib/fastlane/plugin/sapfire/sln_project/module.rb +14 -0
- data/lib/fastlane/plugin/sapfire/sln_project/project_block.rb +82 -0
- data/lib/fastlane/plugin/sapfire/sln_project/root_block.rb +72 -0
- data/lib/fastlane/plugin/sapfire/version.rb +5 -0
- data/lib/fastlane/plugin/sapfire.rb +16 -0
- metadata +182 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
require "fastlane/action"
|
2
|
+
require_relative "../helper/sapfire_helper"
|
3
|
+
|
4
|
+
module Fastlane
|
5
|
+
module Actions
|
6
|
+
class UploadNugetAction < Action
|
7
|
+
def self.run(params)
|
8
|
+
UI.user_error!("Can't find dotnet") unless Helper::SapfireHelper.dotnet_specified?
|
9
|
+
|
10
|
+
dotnet_path = Actions.lane_context[SharedValues::SF_DOTNET_PATH]
|
11
|
+
dotnet_args = get_dotnet_args(params)
|
12
|
+
cmd = "\"#{dotnet_path}\" nuget push #{dotnet_args.join(" ")}"
|
13
|
+
|
14
|
+
UI.command(cmd)
|
15
|
+
|
16
|
+
Open3.popen2(cmd) do |_, stdout, wait_thr|
|
17
|
+
until stdout.eof?
|
18
|
+
stdout.each do |l|
|
19
|
+
line = l.force_encoding("utf-8").chomp
|
20
|
+
puts line
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
UI.user_error!("NuGet package pushing failed. See the log above.") unless wait_thr.value.success?
|
25
|
+
UI.success("Package has successfully uploaded") if wait_thr.value.success?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.description
|
30
|
+
"Pushes a package to the server and publishes it"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.authors
|
34
|
+
["CheeryLee"]
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.is_supported?(platform)
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.available_options
|
42
|
+
[
|
43
|
+
FastlaneCore::ConfigItem.new(
|
44
|
+
key: :api_key,
|
45
|
+
description: "The API key for the server",
|
46
|
+
optional: false,
|
47
|
+
type: String,
|
48
|
+
verify_block: proc do |value|
|
49
|
+
UI.user_error!("The API key for the server must be specified and must not be empty") unless value && !value.empty?
|
50
|
+
end
|
51
|
+
),
|
52
|
+
FastlaneCore::ConfigItem.new(
|
53
|
+
key: :source,
|
54
|
+
description: "The server URL. NuGet identifies a UNC or local folder source and simply copies the file there instead of pushing it using HTTP",
|
55
|
+
optional: false,
|
56
|
+
type: String
|
57
|
+
),
|
58
|
+
FastlaneCore::ConfigItem.new(
|
59
|
+
key: :timeout,
|
60
|
+
description: "The timeout for pushing to a server in seconds",
|
61
|
+
optional: true,
|
62
|
+
default_value: 0,
|
63
|
+
type: Integer
|
64
|
+
),
|
65
|
+
FastlaneCore::ConfigItem.new(
|
66
|
+
key: :path,
|
67
|
+
description: "The file path to the package to be uploaded",
|
68
|
+
optional: false,
|
69
|
+
type: String,
|
70
|
+
verify_block: proc do |value|
|
71
|
+
UI.user_error!("Path to NuGet package is invalid") unless value && !value.empty?
|
72
|
+
UI.user_error!("The provided path doesn't point to NUPKG-file") unless
|
73
|
+
File.exist?(File.expand_path(value)) && value.end_with?(".nupkg")
|
74
|
+
end
|
75
|
+
)
|
76
|
+
]
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.category
|
80
|
+
:production
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.get_dotnet_args(params)
|
84
|
+
args = []
|
85
|
+
|
86
|
+
args.append(params[:path])
|
87
|
+
args.append("--api-key #{params[:api_key]}")
|
88
|
+
args.append("--source #{params[:source]}")
|
89
|
+
args.append("--timeout #{params[:timeout]}")
|
90
|
+
|
91
|
+
args
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "fastlane/action"
|
2
|
+
require_relative "../msbuild/runner"
|
3
|
+
require_relative "../msbuild/options"
|
4
|
+
require_relative "../msbuild/module"
|
5
|
+
|
6
|
+
module Fastlane
|
7
|
+
module Actions
|
8
|
+
class MsbuildActionBase < Action
|
9
|
+
def self.run(params)
|
10
|
+
Msbuild.config.params = params
|
11
|
+
Msbuild.config.msbuild_type = Fastlane::Actions.lane_context[SharedValues::SF_MSBUILD_TYPE]
|
12
|
+
Msbuild.config.overwritten_props = overwritten_msbuild_properties
|
13
|
+
msbuild_path = Fastlane::Actions.lane_context[SharedValues::SF_MSBUILD_PATH]
|
14
|
+
|
15
|
+
Msbuild.config.msbuild_path = if Msbuild.config.msbuild_type == Msbuild::MsbuildType::LIBRARY
|
16
|
+
"\"#{Fastlane::Actions.lane_context[SharedValues::SF_DOTNET_PATH]}\" \"#{msbuild_path}\""
|
17
|
+
else
|
18
|
+
"\"#{msbuild_path}\""
|
19
|
+
end
|
20
|
+
|
21
|
+
runner = Msbuild::Runner.new
|
22
|
+
runner.run
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.overwritten_msbuild_properties
|
26
|
+
{
|
27
|
+
Configuration: "configuration",
|
28
|
+
Platform: "platform"
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.available_options
|
33
|
+
rejected_options_array = rejected_options
|
34
|
+
return Msbuild::Options.available_options if rejected_options_array.nil?
|
35
|
+
|
36
|
+
Msbuild::Options.available_options.reject do |option|
|
37
|
+
rejected_options_array.include?(option.key)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Helper
|
3
|
+
class MsDevCenterHelper
|
4
|
+
HOST = "https://manage.devcenter.microsoft.com".freeze
|
5
|
+
API_VERSION = "v1.0".freeze
|
6
|
+
API_ROOT = "my/applications".freeze
|
7
|
+
REQUEST_HEADERS = {
|
8
|
+
"Accept": "application/json"
|
9
|
+
}.freeze
|
10
|
+
FILE_CHUNK_SIZE = 26_214_400
|
11
|
+
UPLOAD_RETRIES = 3
|
12
|
+
|
13
|
+
def self.upload_blob(url, zip_path, timeout = 0)
|
14
|
+
UI.user_error!("File upload URL need to be provided") if !url.is_a?(String) || url.nil? || url.empty?
|
15
|
+
UI.user_error!("File path is invalid") if !zip_path.is_a?(String) || zip_path.nil? || zip_path.empty?
|
16
|
+
|
17
|
+
expand_path = File.expand_path(zip_path)
|
18
|
+
UI.user_error!("The provided path doesn't point to ZIP file") unless File.exist?(expand_path) && zip_path.end_with?(".zip")
|
19
|
+
|
20
|
+
File.open(expand_path) do |file|
|
21
|
+
block_list = []
|
22
|
+
chunks_count = (file.size.to_f / FILE_CHUNK_SIZE).ceil
|
23
|
+
current_chunk = 1
|
24
|
+
|
25
|
+
until file.eof?
|
26
|
+
bytes = file.read(FILE_CHUNK_SIZE)
|
27
|
+
id = SecureRandom.uuid.delete("-")
|
28
|
+
block_list.append(id)
|
29
|
+
retry_count = 0
|
30
|
+
result = false
|
31
|
+
|
32
|
+
UI.message("Upload chunk [#{current_chunk} / #{chunks_count}]")
|
33
|
+
|
34
|
+
while !result && retry_count < UPLOAD_RETRIES
|
35
|
+
result = upload_block(url, bytes, id, timeout)
|
36
|
+
retry_count += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
UI.user_error!("Uploading failed: some chunks have not been uploaded") unless result
|
40
|
+
current_chunk += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
result = upload_block_list(url, block_list, timeout)
|
44
|
+
UI.user_error!("Uploading failed: block list hasn't been uploaded") unless result
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.upload_block(url, bytes, id, timeout = 0)
|
49
|
+
headers = {
|
50
|
+
"Content-Length": bytes.length.to_s
|
51
|
+
}
|
52
|
+
|
53
|
+
url_data = parse_upload_url(url)
|
54
|
+
url_data[:query]["comp"] = "block"
|
55
|
+
url_data[:query]["blockid"] = id
|
56
|
+
|
57
|
+
connection = Faraday.new(url_data[:host])
|
58
|
+
response = connection.put(url_data[:path]) do |req|
|
59
|
+
req.headers = headers
|
60
|
+
req.params = url_data[:query]
|
61
|
+
req.body = bytes
|
62
|
+
req.options.timeout = timeout if timeout.positive?
|
63
|
+
end
|
64
|
+
|
65
|
+
return true if response.status == 201
|
66
|
+
|
67
|
+
error = response.body.to_s
|
68
|
+
UI.error("Upload request failed.\nCode: #{response.status}\nError: #{error}")
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.upload_block_list(url, list, timeout = 0)
|
73
|
+
document = REXML::Document.new
|
74
|
+
document.xml_decl.version = "1.0"
|
75
|
+
document.xml_decl.encoding = "utf-8"
|
76
|
+
block_list = document.add_element("BlockList")
|
77
|
+
|
78
|
+
list.each do |block|
|
79
|
+
block_list.add_element("Latest").text = block
|
80
|
+
end
|
81
|
+
|
82
|
+
url_data = parse_upload_url(url)
|
83
|
+
url_data[:query]["comp"] = "blocklist"
|
84
|
+
|
85
|
+
connection = Faraday.new(url_data[:host])
|
86
|
+
response = connection.put(url_data[:path]) do |req|
|
87
|
+
req.params = url_data[:query]
|
88
|
+
req.body = document.to_s
|
89
|
+
req.options.timeout = timeout if timeout.positive?
|
90
|
+
end
|
91
|
+
|
92
|
+
return true if response.status == 201
|
93
|
+
|
94
|
+
error = response.body.to_s
|
95
|
+
UI.error("Upload block list request failed.\nCode: #{response.status}\nError: #{error}")
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.get_app_info(app_id, auth_token, timeout = 0)
|
100
|
+
check_app_id(app_id)
|
101
|
+
|
102
|
+
connection = Faraday.new(HOST)
|
103
|
+
response = connection.get("/#{API_VERSION}/#{API_ROOT}/#{app_id}") do |req|
|
104
|
+
req.headers = build_headers(auth_token)
|
105
|
+
req.options.timeout = timeout if timeout.positive?
|
106
|
+
end
|
107
|
+
|
108
|
+
begin
|
109
|
+
data = JSON.parse(response.body)
|
110
|
+
return data if response.status == 200
|
111
|
+
|
112
|
+
UI.user_error!("Request returned the error.\nCode: #{response.status}")
|
113
|
+
rescue StandardError => ex
|
114
|
+
UI.user_error!("Getting app info process failed: #{ex}")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.create_submission(app_id, auth_token, timeout = 0)
|
119
|
+
check_app_id(app_id)
|
120
|
+
if non_published_submission?(app_id, auth_token, timeout)
|
121
|
+
UI.user_error!([
|
122
|
+
"There is a pending submission has already been created.",
|
123
|
+
"You need to either proceed it or remove before creating a new one."
|
124
|
+
].join(" "))
|
125
|
+
end
|
126
|
+
|
127
|
+
connection = Faraday.new(HOST)
|
128
|
+
response = connection.post("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions") do |req|
|
129
|
+
req.headers = build_headers(auth_token)
|
130
|
+
req.options.timeout = timeout if timeout.positive?
|
131
|
+
end
|
132
|
+
|
133
|
+
begin
|
134
|
+
data = JSON.parse(response.body)
|
135
|
+
return data if response.status == 201
|
136
|
+
|
137
|
+
code = data["code"]
|
138
|
+
message = data["message"]
|
139
|
+
|
140
|
+
UI.user_error!("Request returned the error.\nCode: #{response.status} #{code}.\nDescription: #{message}")
|
141
|
+
rescue StandardError => ex
|
142
|
+
UI.user_error!("Creating submission process failed: #{ex}")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.update_submission(app_id, submission_obj, auth_token, timeout = 0)
|
147
|
+
check_app_id(app_id)
|
148
|
+
UI.user_error!("Submission data object need to be provided") if submission_obj.nil?
|
149
|
+
|
150
|
+
submission_id = submission_obj["id"]
|
151
|
+
connection = Faraday.new(HOST)
|
152
|
+
response = connection.put("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}") do |req|
|
153
|
+
req.headers = build_headers(auth_token)
|
154
|
+
req.body = submission_obj.to_json
|
155
|
+
req.options.timeout = timeout if timeout.positive?
|
156
|
+
end
|
157
|
+
|
158
|
+
begin
|
159
|
+
data = JSON.parse(response.body)
|
160
|
+
return data if response.status == 200
|
161
|
+
|
162
|
+
UI.user_error!("Request returned the error.\nCode: #{response.status}")
|
163
|
+
rescue StandardError => ex
|
164
|
+
UI.user_error!("Updating submission process failed: #{ex}")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.commit_submission(app_id, submission_id, auth_token, timeout = 0)
|
169
|
+
check_app_id(app_id)
|
170
|
+
check_submission_id(submission_id)
|
171
|
+
|
172
|
+
connection = Faraday.new(HOST)
|
173
|
+
response = connection.post("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}/commit") do |req|
|
174
|
+
req.headers = build_headers(auth_token)
|
175
|
+
req.options.timeout = timeout if timeout.positive?
|
176
|
+
end
|
177
|
+
|
178
|
+
UI.user_error!("Committing submission request returned the error.\nCode: #{response.status}") unless response.status == 202
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.get_submission_status(app_id, submission_id, auth_token, timeout = 0)
|
182
|
+
check_app_id(app_id)
|
183
|
+
check_submission_id(submission_id)
|
184
|
+
|
185
|
+
connection = Faraday.new(HOST)
|
186
|
+
response = connection.get("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}/status") do |req|
|
187
|
+
req.headers = build_headers(auth_token)
|
188
|
+
req.options.timeout = timeout if timeout.positive?
|
189
|
+
end
|
190
|
+
|
191
|
+
begin
|
192
|
+
data = JSON.parse(response.body)
|
193
|
+
return data if response.status == 200
|
194
|
+
|
195
|
+
UI.user_error!("Request returned the error.\nCode: #{response.status}")
|
196
|
+
rescue StandardError => ex
|
197
|
+
UI.user_error!("Submission status obtaining process failed: #{ex}")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.acquire_authorization_token(tenant_id, client_id, client_secret, timeout = 0)
|
202
|
+
body = {
|
203
|
+
client_id: client_id,
|
204
|
+
client_secret: client_secret,
|
205
|
+
grant_type: "client_credentials",
|
206
|
+
resource: HOST
|
207
|
+
}
|
208
|
+
headers = {
|
209
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
210
|
+
}.merge(REQUEST_HEADERS)
|
211
|
+
|
212
|
+
connection = Faraday.new("https://login.microsoftonline.com")
|
213
|
+
response = connection.post("/#{tenant_id}/oauth2/token") do |req|
|
214
|
+
req.headers = headers
|
215
|
+
req.body = body
|
216
|
+
req.options.timeout = timeout if timeout.positive?
|
217
|
+
end
|
218
|
+
|
219
|
+
begin
|
220
|
+
data = JSON.parse(response.body)
|
221
|
+
return data["access_token"] if response.status == 200
|
222
|
+
|
223
|
+
error = data["error"]
|
224
|
+
error_description = data["error_description"]
|
225
|
+
|
226
|
+
UI.user_error!("Request returned the error.\nCode: #{error}.\nDescription: #{error_description}")
|
227
|
+
rescue StandardError => ex
|
228
|
+
UI.user_error!("Authorization failed: #{ex}")
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.non_published_submission?(app_id, auth_token, timeout = 0)
|
233
|
+
check_app_id(app_id)
|
234
|
+
app_info = get_app_info(app_id, auth_token, timeout)
|
235
|
+
app_info.key?("pendingApplicationSubmission")
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.build_headers(auth_token)
|
239
|
+
{
|
240
|
+
"Authorization": "Bearer #{auth_token}",
|
241
|
+
"Content-Type": "application/json"
|
242
|
+
}.merge(REQUEST_HEADERS)
|
243
|
+
end
|
244
|
+
|
245
|
+
def self.parse_upload_url(url)
|
246
|
+
url = URI.parse(url)
|
247
|
+
query_parts = {}
|
248
|
+
url.query.split("&").each do |x|
|
249
|
+
parts = x.split("=")
|
250
|
+
query_parts[parts[0]] = CGI.unescape(parts[1])
|
251
|
+
end
|
252
|
+
|
253
|
+
{
|
254
|
+
host: "https://#{url.host}",
|
255
|
+
path: url.path,
|
256
|
+
query: query_parts
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
def self.check_app_id(id)
|
261
|
+
UI.user_error!("App ID need to be provided") if !id.is_a?(String) || id.nil? || id.empty?
|
262
|
+
end
|
263
|
+
|
264
|
+
def self.check_submission_id(id)
|
265
|
+
UI.user_error!("Submission ID need to be provided") if !id.is_a?(String) || id.nil? || id.empty?
|
266
|
+
end
|
267
|
+
|
268
|
+
public_class_method(:upload_blob)
|
269
|
+
public_class_method(:get_app_info)
|
270
|
+
public_class_method(:create_submission)
|
271
|
+
public_class_method(:update_submission)
|
272
|
+
public_class_method(:commit_submission)
|
273
|
+
public_class_method(:get_submission_status)
|
274
|
+
public_class_method(:acquire_authorization_token)
|
275
|
+
|
276
|
+
private_class_method(:upload_block)
|
277
|
+
private_class_method(:upload_block_list)
|
278
|
+
private_class_method(:non_published_submission?)
|
279
|
+
private_class_method(:build_headers)
|
280
|
+
private_class_method(:parse_upload_url)
|
281
|
+
private_class_method(:check_app_id)
|
282
|
+
private_class_method(:check_submission_id)
|
283
|
+
|
284
|
+
private_constant(:HOST)
|
285
|
+
private_constant(:API_VERSION)
|
286
|
+
private_constant(:API_ROOT)
|
287
|
+
private_constant(:REQUEST_HEADERS)
|
288
|
+
private_constant(:FILE_CHUNK_SIZE)
|
289
|
+
private_constant(:UPLOAD_RETRIES)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Helper
|
3
|
+
class SapfireHelper
|
4
|
+
def self.dotnet_specified?
|
5
|
+
path = Actions.lane_context[Fastlane::Actions::SharedValues::SF_DOTNET_PATH] || ""
|
6
|
+
path = File.expand_path(path) unless path.empty?
|
7
|
+
|
8
|
+
if path.empty?
|
9
|
+
UI.error("The path to dotnet executable is not specified")
|
10
|
+
return false
|
11
|
+
elsif !File.exist?(path)
|
12
|
+
UI.error("File '#{path}' doesn't exist")
|
13
|
+
return false
|
14
|
+
elsif !path.end_with?("dotnet") && !path.end_with?("dotnet.exe")
|
15
|
+
UI.error("The path to dotnet doesn't point to executable file: #{path}")
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.msbuild_specified?
|
23
|
+
path = Actions.lane_context[Fastlane::Actions::SharedValues::SF_MSBUILD_PATH] || ""
|
24
|
+
path = File.expand_path(path) unless path.empty?
|
25
|
+
|
26
|
+
if path.empty?
|
27
|
+
UI.error("The path to MSBuild executable is not specified")
|
28
|
+
return false
|
29
|
+
elsif !File.exist?(path)
|
30
|
+
UI.error("File '#{path}' doesn't exist")
|
31
|
+
return false
|
32
|
+
elsif !path.end_with?("MSBuild.dll") && !path.end_with?("MSBuild.exe")
|
33
|
+
UI.error("The path to MSBuild doesn't point to executable or DLL file: #{path}")
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.kits_10_location
|
41
|
+
require "win32/registry"
|
42
|
+
|
43
|
+
Win32::Registry::HKEY_LOCAL_MACHINE.open("SOFTWARE\\Wow6432Node\\Microsoft\\Windows Kits\\Installed Roots") do |reg|
|
44
|
+
value = reg["KitsRoot10"]
|
45
|
+
return value.strip
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.root_plugin_location
|
50
|
+
path = File.join(File.dirname(__dir__), "../../../../")
|
51
|
+
File.expand_path(path)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "fastlane/boolean"
|
2
|
+
|
3
|
+
module Msbuild
|
4
|
+
class BuildType
|
5
|
+
NONE = 0
|
6
|
+
GENERIC = 1
|
7
|
+
UWP = 2
|
8
|
+
NUGET = 3
|
9
|
+
end
|
10
|
+
|
11
|
+
class MsbuildType
|
12
|
+
EXE = 0
|
13
|
+
LIBRARY = 1
|
14
|
+
end
|
15
|
+
|
16
|
+
class Config
|
17
|
+
attr_accessor :params
|
18
|
+
attr_accessor :msbuild_path
|
19
|
+
attr_accessor :overwritten_props
|
20
|
+
attr_accessor :certificate
|
21
|
+
attr_accessor :certificate_password
|
22
|
+
attr_accessor :certificate_thumbprint
|
23
|
+
attr_accessor :build_type
|
24
|
+
attr_accessor :msbuild_type
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
attr_accessor :config
|
29
|
+
end
|
30
|
+
|
31
|
+
UI = FastlaneCore::UI
|
32
|
+
self.config = Config.new
|
33
|
+
self.config.build_type = BuildType::NONE
|
34
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require "fastlane_core/configuration/config_item"
|
2
|
+
require_relative "module"
|
3
|
+
|
4
|
+
module Msbuild
|
5
|
+
class Options
|
6
|
+
PACKAGE_FORMATS = %w[.appx .appxbundle .appxupload .msix .msixbundle .msixupload].freeze
|
7
|
+
|
8
|
+
def self.available_options
|
9
|
+
return @options if @options
|
10
|
+
|
11
|
+
@options = plain_options
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.plain_options
|
15
|
+
[
|
16
|
+
FastlaneCore::ConfigItem.new(
|
17
|
+
key: :project,
|
18
|
+
description: "Path to the SLN-solution file",
|
19
|
+
optional: false,
|
20
|
+
type: String,
|
21
|
+
verify_block: proc do |value|
|
22
|
+
UI.user_error!("Path to SLN-file is invalid") unless value && !value.empty?
|
23
|
+
UI.user_error!("The provided path doesn't point to SLN-file") unless
|
24
|
+
File.exist?(File.expand_path(value)) && value.end_with?(".sln")
|
25
|
+
end
|
26
|
+
),
|
27
|
+
FastlaneCore::ConfigItem.new(
|
28
|
+
key: :configuration,
|
29
|
+
description: "Build configuration",
|
30
|
+
optional: false,
|
31
|
+
type: String
|
32
|
+
),
|
33
|
+
FastlaneCore::ConfigItem.new(
|
34
|
+
key: :platform,
|
35
|
+
description: "Target platform",
|
36
|
+
optional: false,
|
37
|
+
type: String
|
38
|
+
),
|
39
|
+
FastlaneCore::ConfigItem.new(
|
40
|
+
key: :restore,
|
41
|
+
description: "Restore project prior to build the actual targets",
|
42
|
+
optional: true,
|
43
|
+
default_value: false,
|
44
|
+
type: Fastlane::Boolean
|
45
|
+
),
|
46
|
+
FastlaneCore::ConfigItem.new(
|
47
|
+
key: :clean,
|
48
|
+
description: "Should the project be cleaned before building it?",
|
49
|
+
optional: true,
|
50
|
+
default_value: false,
|
51
|
+
type: Fastlane::Boolean
|
52
|
+
),
|
53
|
+
FastlaneCore::ConfigItem.new(
|
54
|
+
key: :appx_output_path,
|
55
|
+
description: "Defines the folder to store the generated package artifacts. Relative path is a root folder where project is located",
|
56
|
+
optional: true,
|
57
|
+
default_value: "",
|
58
|
+
type: String
|
59
|
+
),
|
60
|
+
FastlaneCore::ConfigItem.new(
|
61
|
+
key: :appx_output_name,
|
62
|
+
description: "Defines the name of the resulting package",
|
63
|
+
optional: true,
|
64
|
+
default_value: "",
|
65
|
+
type: String
|
66
|
+
),
|
67
|
+
FastlaneCore::ConfigItem.new(
|
68
|
+
key: :appx_bundle_platforms,
|
69
|
+
description: [
|
70
|
+
"Enables you to define the platforms to include in the bundle.",
|
71
|
+
"It's possible to define multiple platforms divided by vertical line, e.g. 'x86|ARM'"
|
72
|
+
].join("\n"),
|
73
|
+
optional: false,
|
74
|
+
type: String
|
75
|
+
),
|
76
|
+
FastlaneCore::ConfigItem.new(
|
77
|
+
key: :build_mode,
|
78
|
+
description: "Package build mode. Use `SideloadOnly` for sideloading only or `StoreUpload` for generating the .msixupload/.appxupload file",
|
79
|
+
optional: false,
|
80
|
+
type: String
|
81
|
+
),
|
82
|
+
FastlaneCore::ConfigItem.new(
|
83
|
+
key: :skip_codesigning,
|
84
|
+
description: "Build without package signing",
|
85
|
+
optional: true,
|
86
|
+
default_value: false,
|
87
|
+
type: Fastlane::Boolean
|
88
|
+
),
|
89
|
+
FastlaneCore::ConfigItem.new(
|
90
|
+
key: :jobs,
|
91
|
+
description: [
|
92
|
+
"A number of concurrent processes to use when building.",
|
93
|
+
"Set it to -1 if you want to use up to the number of processors in the computer"
|
94
|
+
].join("\n"),
|
95
|
+
optional: true,
|
96
|
+
default_value: 1,
|
97
|
+
type: Integer,
|
98
|
+
verify_block: proc do |value|
|
99
|
+
UI.important("A number of parallel jobs can't equals to zero. Using default value.") if value.zero?
|
100
|
+
end
|
101
|
+
),
|
102
|
+
FastlaneCore::ConfigItem.new(
|
103
|
+
key: :properties,
|
104
|
+
description: "A hash of project properties to be set up, where the key is a property name and the value is it's value",
|
105
|
+
optional: true,
|
106
|
+
default_value: {},
|
107
|
+
type: Hash,
|
108
|
+
verify_block: proc do |value|
|
109
|
+
counter = 0
|
110
|
+
|
111
|
+
value.each do |key, _|
|
112
|
+
UI.user_error!("Item #{counter}: key type must be Symbol") unless key.is_a?(Symbol)
|
113
|
+
counter += 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
)
|
117
|
+
]
|
118
|
+
end
|
119
|
+
|
120
|
+
public_class_method(:available_options)
|
121
|
+
private_class_method(:plain_options)
|
122
|
+
end
|
123
|
+
end
|