u3d 0.9
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/.gitignore +2 -0
- data/.licenses.json +19 -0
- data/.rspec +1 -0
- data/.rubocop.yml +43 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +98 -0
- data/LICENSE +21 -0
- data/LICENSE.fastlane +22 -0
- data/LOG_RULES.md +170 -0
- data/README.md +72 -0
- data/Rakefile +28 -0
- data/TODO.md +15 -0
- data/build.sh +5 -0
- data/config/log_rules.json +230 -0
- data/examples/Example1/.gitignore +19 -0
- data/examples/Example1/Assets/Editor.meta +9 -0
- data/examples/Example1/Assets/Editor/EditorRun.cs +23 -0
- data/examples/Example1/Assets/Editor/EditorRun.cs.meta +12 -0
- data/examples/Example1/Assets/Editor/FileSystemUtil.cs +26 -0
- data/examples/Example1/Assets/Editor/FileSystemUtil.cs.meta +12 -0
- data/examples/Example1/Assets/Scene1.unity +264 -0
- data/examples/Example1/Assets/Scene1.unity.meta +8 -0
- data/examples/Example1/Gemfile +8 -0
- data/examples/Example1/Gemfile.lock +165 -0
- data/examples/Example1/ProjectSettings/AudioManager.asset +16 -0
- data/examples/Example1/ProjectSettings/ClusterInputManager.asset +6 -0
- data/examples/Example1/ProjectSettings/DynamicsManager.asset +18 -0
- data/examples/Example1/ProjectSettings/EditorBuildSettings.asset +7 -0
- data/examples/Example1/ProjectSettings/EditorSettings.asset +14 -0
- data/examples/Example1/ProjectSettings/GraphicsSettings.asset +61 -0
- data/examples/Example1/ProjectSettings/InputManager.asset +295 -0
- data/examples/Example1/ProjectSettings/NavMeshAreas.asset +89 -0
- data/examples/Example1/ProjectSettings/NetworkManager.asset +8 -0
- data/examples/Example1/ProjectSettings/Physics2DSettings.asset +35 -0
- data/examples/Example1/ProjectSettings/ProjectSettings.asset +591 -0
- data/examples/Example1/ProjectSettings/ProjectVersion.txt +1 -0
- data/examples/Example1/ProjectSettings/QualitySettings.asset +180 -0
- data/examples/Example1/ProjectSettings/TagManager.asset +43 -0
- data/examples/Example1/ProjectSettings/TimeManager.asset +9 -0
- data/examples/Example1/ProjectSettings/UnityConnectSettings.asset +32 -0
- data/examples/Example1/README.md +5 -0
- data/examples/Example1/Rakefile +5 -0
- data/examples/Example1/fastlane/Fastfile +4 -0
- data/examples/Example1/fastlane/Pluginfile +1 -0
- data/examples/Example1/run.sh +1 -0
- data/examples/Example2/.gitignore +20 -0
- data/examples/Example2/Assets/Editor.meta +9 -0
- data/examples/Example2/Assets/Editor/EditorRun.cs +33 -0
- data/examples/Example2/Assets/Editor/EditorRun.cs.meta +12 -0
- data/examples/Example2/Assets/Editor/PostprocessBuildPlayer.cs +92 -0
- data/examples/Example2/Assets/Editor/PostprocessBuildPlayer.cs.meta +8 -0
- data/examples/Example2/Assets/Editor/PostprocessBuildPlayer_log.sh +31 -0
- data/examples/Example2/Assets/Editor/PostprocessBuildPlayer_log.sh.meta +8 -0
- data/examples/Example2/Assets/Editor/SimpleBuildSetup.cs +20 -0
- data/examples/Example2/Assets/Editor/SimpleBuildSetup.cs.meta +12 -0
- data/examples/Example2/Assets/Scene.unity +278 -0
- data/examples/Example2/Assets/Scene.unity.meta +8 -0
- data/examples/Example2/Gemfile +8 -0
- data/examples/Example2/Gemfile.lock +165 -0
- data/examples/Example2/ProjectSettings/AudioManager.asset +17 -0
- data/examples/Example2/ProjectSettings/ClusterInputManager.asset +6 -0
- data/examples/Example2/ProjectSettings/DynamicsManager.asset +19 -0
- data/examples/Example2/ProjectSettings/EditorBuildSettings.asset +10 -0
- data/examples/Example2/ProjectSettings/EditorSettings.asset +14 -0
- data/examples/Example2/ProjectSettings/GraphicsSettings.asset +63 -0
- data/examples/Example2/ProjectSettings/InputManager.asset +295 -0
- data/examples/Example2/ProjectSettings/NavMeshAreas.asset +89 -0
- data/examples/Example2/ProjectSettings/NetworkManager.asset +8 -0
- data/examples/Example2/ProjectSettings/Physics2DSettings.asset +36 -0
- data/examples/Example2/ProjectSettings/ProjectSettings.asset +591 -0
- data/examples/Example2/ProjectSettings/ProjectVersion.txt +1 -0
- data/examples/Example2/ProjectSettings/QualitySettings.asset +193 -0
- data/examples/Example2/ProjectSettings/TagManager.asset +43 -0
- data/examples/Example2/ProjectSettings/TimeManager.asset +9 -0
- data/examples/Example2/ProjectSettings/UnityConnectSettings.asset +34 -0
- data/examples/Example2/README.md +10 -0
- data/examples/Example2/fastlane/Fastfile +4 -0
- data/examples/Example2/fastlane/Pluginfile +1 -0
- data/exe/u3d +7 -0
- data/fastlane-plugin-u3d/.gitignore +10 -0
- data/fastlane-plugin-u3d/.licenses.json +9 -0
- data/fastlane-plugin-u3d/.rspec +3 -0
- data/fastlane-plugin-u3d/.rubocop.yml +253 -0
- data/fastlane-plugin-u3d/.travis.yml +4 -0
- data/fastlane-plugin-u3d/Gemfile +6 -0
- data/fastlane-plugin-u3d/LICENSE +21 -0
- data/fastlane-plugin-u3d/README.md +52 -0
- data/fastlane-plugin-u3d/Rakefile +9 -0
- data/fastlane-plugin-u3d/circle.yml +9 -0
- data/fastlane-plugin-u3d/fastlane-plugin-u3d.gemspec +31 -0
- data/fastlane-plugin-u3d/fastlane/Fastfile +3 -0
- data/fastlane-plugin-u3d/fastlane/Pluginfile +1 -0
- data/fastlane-plugin-u3d/lib/fastlane/plugin/u3d.rb +38 -0
- data/fastlane-plugin-u3d/lib/fastlane/plugin/u3d/actions/u3d_action.rb +80 -0
- data/fastlane-plugin-u3d/lib/fastlane/plugin/u3d/helper/u3d_helper.rb +34 -0
- data/fastlane-plugin-u3d/lib/fastlane/plugin/u3d/version.rb +27 -0
- data/fastlane-plugin-u3d/spec/spec_helper.rb +32 -0
- data/lib/u3d.rb +33 -0
- data/lib/u3d/cache.rb +120 -0
- data/lib/u3d/commands.rb +307 -0
- data/lib/u3d/commands_generator.rb +163 -0
- data/lib/u3d/downloader.rb +363 -0
- data/lib/u3d/iniparser.rb +83 -0
- data/lib/u3d/installer.rb +445 -0
- data/lib/u3d/log_analyzer.rb +221 -0
- data/lib/u3d/unity_version_number.rb +71 -0
- data/lib/u3d/unity_versions.rb +207 -0
- data/lib/u3d/utils.rb +121 -0
- data/lib/u3d/version.rb +31 -0
- data/lib/u3d_core.rb +30 -0
- data/lib/u3d_core/command_executor.rb +134 -0
- data/lib/u3d_core/command_runner.rb +93 -0
- data/lib/u3d_core/credentials.rb +116 -0
- data/lib/u3d_core/globals.rb +84 -0
- data/lib/u3d_core/helper.rb +149 -0
- data/lib/u3d_core/ui/disable_colors.rb +40 -0
- data/lib/u3d_core/ui/implementations/shell.rb +157 -0
- data/lib/u3d_core/ui/interface.rb +182 -0
- data/lib/u3d_core/ui/ui.rb +49 -0
- data/local_gem_install.sh +6 -0
- data/scripts/be +14 -0
- data/u3d.gemspec +41 -0
- metadata +388 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
## --- BEGIN LICENSE BLOCK ---
|
|
2
|
+
# Copyright (c) 2016-present WeWantToKnow AS
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
# furnished to do so, subject to the following conditions:
|
|
10
|
+
#
|
|
11
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
# copies or substantial portions of the Software.
|
|
13
|
+
#
|
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
# SOFTWARE.
|
|
21
|
+
## --- END LICENSE BLOCK ---
|
|
22
|
+
|
|
23
|
+
require 'net/http'
|
|
24
|
+
require 'u3d/iniparser'
|
|
25
|
+
require 'u3d/utils'
|
|
26
|
+
|
|
27
|
+
module U3d
|
|
28
|
+
# Take care of downloading files and packages
|
|
29
|
+
module Downloader
|
|
30
|
+
# Name of the directory for the package downloading
|
|
31
|
+
DOWNLOAD_DIRECTORY = 'Unity_Packages'.freeze
|
|
32
|
+
# Path to the directory for the package downloading
|
|
33
|
+
DOWNLOAD_PATH = "#{ENV['HOME']}/Downloads".freeze
|
|
34
|
+
# Regex to get the name of a package out of its file name
|
|
35
|
+
UNITY_MODULE_FILE_REGEX = %r{\/([\w\-_\.\+]+\.(?:pkg|exe|zip|sh|deb))}
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
def hash_validation(expected: nil, actual: nil)
|
|
39
|
+
if expected
|
|
40
|
+
if expected != actual
|
|
41
|
+
UI.verbose "Expected hash is #{expected}, file hash is #{actual}"
|
|
42
|
+
UI.important 'File looks corrupted (wrong hash)'
|
|
43
|
+
return false
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
UI.verbose 'No hash validation available. File is assumed correct but may not be.'
|
|
47
|
+
end
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def size_validation(expected: nil, actual: nil)
|
|
52
|
+
if expected
|
|
53
|
+
if expected != actual
|
|
54
|
+
UI.verbose "Expected size is #{expected}, file size is #{actual}"
|
|
55
|
+
UI.important 'File looks corrupted (wrong size)'
|
|
56
|
+
return false
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
UI.verbose 'No size validation available. File is assumed correct but may not be.'
|
|
60
|
+
end
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def download_package(path, url, size: nil)
|
|
65
|
+
File.open(path, 'wb') do |f|
|
|
66
|
+
uri = URI(url)
|
|
67
|
+
current = 0
|
|
68
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
|
69
|
+
request = Net::HTTP::Get.new uri
|
|
70
|
+
http.request request do |response|
|
|
71
|
+
begin
|
|
72
|
+
size ||= Integer(response['Content-Length'])
|
|
73
|
+
rescue ArgumentError
|
|
74
|
+
UI.verbose 'Unable to get length of file in download'
|
|
75
|
+
end
|
|
76
|
+
started_at = Time.now.to_i - 1
|
|
77
|
+
response.read_body do |segment|
|
|
78
|
+
f.write(segment)
|
|
79
|
+
current += segment.length
|
|
80
|
+
next unless UI.interactive?
|
|
81
|
+
if size
|
|
82
|
+
Utils.print_progress(current, size, started_at)
|
|
83
|
+
else
|
|
84
|
+
Utils.print_progress_nosize(current, started_at)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
print "\n" if UI.interactive?
|
|
90
|
+
end
|
|
91
|
+
rescue Interrupt => e
|
|
92
|
+
# Ensure that the file is deleted if download is aborted
|
|
93
|
+
File.delete path
|
|
94
|
+
raise e
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class MacDownloader
|
|
99
|
+
class << self
|
|
100
|
+
# Downloads all packages available for given version
|
|
101
|
+
def download_all(version, cached_versions)
|
|
102
|
+
if cached_versions[version].nil?
|
|
103
|
+
UI.error "No version #{version} was found in cache. It might need updating."
|
|
104
|
+
return nil
|
|
105
|
+
end
|
|
106
|
+
files = []
|
|
107
|
+
ini_file = INIparser.load_ini(version, cached_versions)
|
|
108
|
+
ini_file.keys.each do |k|
|
|
109
|
+
result = download_specific(k, version, cached_versions)
|
|
110
|
+
files << [k, result[0], result[1]] unless result.nil?
|
|
111
|
+
end
|
|
112
|
+
files
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Downloads a specific package for given version
|
|
116
|
+
def download_specific(package, version, cached_versions)
|
|
117
|
+
if cached_versions[version].nil?
|
|
118
|
+
UI.error "No version #{version} was found in cache. It might need updating."
|
|
119
|
+
return nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
ini_file = INIparser.load_ini(version, cached_versions)
|
|
123
|
+
if ini_file[package].empty?
|
|
124
|
+
UI.error "No package \"#{package}\" was found for version #{version}."
|
|
125
|
+
return nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
url = cached_versions[version]
|
|
129
|
+
dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
|
|
130
|
+
Utils.ensure_dir(dir)
|
|
131
|
+
return [get_package(package, ini_file, dir, url), ini_file[package]]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private #---------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
def get_package(name, ini_file, main_dir, base_url)
|
|
137
|
+
file_name = UNITY_MODULE_FILE_REGEX.match(ini_file[name]['url'])[1]
|
|
138
|
+
file_path = File.expand_path(file_name, main_dir)
|
|
139
|
+
|
|
140
|
+
# Check if file already exists and validate it
|
|
141
|
+
if File.file?(file_path)
|
|
142
|
+
if Downloader.size_validation(expected: ini_file[name]['size'], actual: File.size(file_path)) &&
|
|
143
|
+
Downloader.hash_validation(expected: ini_file[name]['md5'], actual: Utils.hashfile(file_path))
|
|
144
|
+
UI.important "#{name.capitalize} already downloaded at #{file_path}"
|
|
145
|
+
return file_path
|
|
146
|
+
else
|
|
147
|
+
UI.verbose "Deleting existing file at #{file_path}"
|
|
148
|
+
File.delete(file_path)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Download file
|
|
153
|
+
url = base_url + ini_file[name]['url']
|
|
154
|
+
UI.header "Downloading #{name}"
|
|
155
|
+
UI.verbose 'Downloading from ' + url.to_s.cyan.underline
|
|
156
|
+
Downloader.download_package(file_path, url, size: ini_file[name]['size'])
|
|
157
|
+
|
|
158
|
+
# Validation download
|
|
159
|
+
if Downloader.size_validation(expected: ini_file[name]['size'], actual: File.size(file_path)) &&
|
|
160
|
+
Downloader.hash_validation(expected: ini_file[name]['md5'], actual: Utils.hashfile(file_path))
|
|
161
|
+
UI.success "Successfully downloaded #{name}."
|
|
162
|
+
else
|
|
163
|
+
File.delete(file_path)
|
|
164
|
+
raise 'Download failed: file is corrupted, deleting it.'
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
file_path
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def all_local_files(version)
|
|
171
|
+
files = []
|
|
172
|
+
ini_file = INIparser.load_ini(version, {}, offline: true)
|
|
173
|
+
ini_file.keys.each do |k|
|
|
174
|
+
result = local_file(k, version)
|
|
175
|
+
files << [k, result[0], result[1]] unless result.nil?
|
|
176
|
+
end
|
|
177
|
+
files
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def local_file(package, version)
|
|
181
|
+
ini_file = INIparser.load_ini(version, {}, offline: true)
|
|
182
|
+
if ini_file[package].empty?
|
|
183
|
+
UI.error "No package \"#{package}\" was found for version #{version}."
|
|
184
|
+
return nil
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
|
|
188
|
+
raise "Main directory #{dir} does not exist. Nothing has been downloaded for version #{version}" unless Dir.exist?(dir)
|
|
189
|
+
|
|
190
|
+
file_name = UNITY_MODULE_FILE_REGEX.match(ini_file[package]['url'])[1]
|
|
191
|
+
file_path = File.expand_path(file_name, dir)
|
|
192
|
+
|
|
193
|
+
unless File.file?(file_path)
|
|
194
|
+
UI.error "Package #{package} has not been downloaded"
|
|
195
|
+
return nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
unless Downloader.size_validation(expected: ini_file[package]['size'], actual: File.size(file_path)) &&
|
|
199
|
+
Downloader.hash_validation(expected: ini_file[package]['md5'], actual: Utils.hashfile(file_path))
|
|
200
|
+
UI.error "File at #{file_path} is corrupted, deleting it"
|
|
201
|
+
File.delete(file_path)
|
|
202
|
+
return nil
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
return [file_path, ini_file[package]]
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
class LinuxDownloader
|
|
211
|
+
class << self
|
|
212
|
+
def download(version, cached_versions)
|
|
213
|
+
if cached_versions[version].nil?
|
|
214
|
+
UI.error "No version #{version} was found in cache. It might need updating."
|
|
215
|
+
return nil
|
|
216
|
+
end
|
|
217
|
+
url = cached_versions[version]
|
|
218
|
+
dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
|
|
219
|
+
Utils.ensure_dir(dir)
|
|
220
|
+
file_name = UNITY_MODULE_FILE_REGEX.match(url)[1]
|
|
221
|
+
file_path = File.expand_path(file_name, dir)
|
|
222
|
+
|
|
223
|
+
# Check if file already exists
|
|
224
|
+
# Note: without size or hash validation, the file is assumed to be correct
|
|
225
|
+
if File.file?(file_path)
|
|
226
|
+
UI.important "File already downloaded at #{file_path}"
|
|
227
|
+
return file_path
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Download file
|
|
231
|
+
UI.header "Downloading Unity #{version}"
|
|
232
|
+
UI.verbose 'Downloading from ' + url.to_s.cyan.underline
|
|
233
|
+
Downloader.download_package(file_path, url)
|
|
234
|
+
U3dCore::CommandExecutor.execute(command: "chmod a+x #{file_path}")
|
|
235
|
+
file_path
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def local_file(version)
|
|
239
|
+
dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
|
|
240
|
+
raise "Main directory #{dir} does not exist. Nothing has been downloaded for version #{version}" unless Dir.exist?(dir)
|
|
241
|
+
find_cmd = "find #{dir}/ -maxdepth 2 -name '*.sh'"
|
|
242
|
+
files = U3dCore::CommandExecutor.execute(command: find_cmd).split("\n")
|
|
243
|
+
return files[0] unless files.empty?
|
|
244
|
+
raise 'No file has been downloaded'
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
class WindowsDownloader
|
|
250
|
+
class << self
|
|
251
|
+
def download_all(version, cached_versions)
|
|
252
|
+
if cached_versions[version].nil?
|
|
253
|
+
UI.error "No version #{version} was found in cache. It might need updating."
|
|
254
|
+
return nil
|
|
255
|
+
end
|
|
256
|
+
files = []
|
|
257
|
+
ini_file = INIparser.load_ini(version, cached_versions)
|
|
258
|
+
ini_file.keys.each do |k|
|
|
259
|
+
result = download_specific(k, version, cached_versions)
|
|
260
|
+
files << [k, result[0], result[1]] unless result.nil?
|
|
261
|
+
end
|
|
262
|
+
files
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Downloads a specific package for given version
|
|
266
|
+
def download_specific(package, version, cached_versions)
|
|
267
|
+
if cached_versions[version].nil?
|
|
268
|
+
UI.error "No version #{version} was found in cache. It might need updating."
|
|
269
|
+
return nil
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
ini_file = INIparser.load_ini(version, cached_versions)
|
|
273
|
+
if ini_file[package].empty?
|
|
274
|
+
UI.error "No package \"#{package}\" was found for version #{version}."
|
|
275
|
+
return nil
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
url = cached_versions[version]
|
|
279
|
+
dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
|
|
280
|
+
Utils.ensure_dir(dir)
|
|
281
|
+
return [get_package(package, ini_file, dir, url), ini_file[package]]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def all_local_files(version)
|
|
285
|
+
files = []
|
|
286
|
+
ini_file = INIparser.load_ini(version, {}, offline: true)
|
|
287
|
+
ini_file.keys.each do |k|
|
|
288
|
+
result = local_file(k, version)
|
|
289
|
+
files << [k, result[0], result[1]] unless result.nil?
|
|
290
|
+
end
|
|
291
|
+
files
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def local_file(package, version)
|
|
295
|
+
ini_file = INIparser.load_ini(version, {}, offline: true)
|
|
296
|
+
if ini_file[package].empty?
|
|
297
|
+
UI.error "No package \"#{package}\" was found for version #{version}."
|
|
298
|
+
return nil
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
dir = File.join(DOWNLOAD_PATH, DOWNLOAD_DIRECTORY, version)
|
|
302
|
+
raise "Main directory #{dir} does not exist. Nothing has been downloaded for version #{version}" unless Dir.exist?(dir)
|
|
303
|
+
|
|
304
|
+
file_name = UNITY_MODULE_FILE_REGEX.match(ini_file[package]['url'])[1]
|
|
305
|
+
file_path = File.expand_path(file_name, dir)
|
|
306
|
+
|
|
307
|
+
unless File.file?(file_path)
|
|
308
|
+
UI.error "Package #{package} has not been downloaded"
|
|
309
|
+
return nil
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
rounded_size = (File.size(file_path).to_f / 1024).floor
|
|
313
|
+
unless Downloader.size_validation(expected: ini_file[package]['size'], actual: rounded_size) &&
|
|
314
|
+
Downloader.hash_validation(expected: ini_file[package]['md5'], actual: Utils.hashfile(file_path))
|
|
315
|
+
UI.error "File at #{file_path} is corrupted, deleting it"
|
|
316
|
+
File.delete(file_path)
|
|
317
|
+
return nil
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
return [file_path, ini_file[package]]
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
private #---------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
def get_package(name, ini_file, main_dir, base_url)
|
|
326
|
+
file_name = UNITY_MODULE_FILE_REGEX.match(ini_file[name]['url'])[1]
|
|
327
|
+
file_path = File.expand_path(file_name, main_dir)
|
|
328
|
+
|
|
329
|
+
# Check if file already exists and validate it
|
|
330
|
+
if File.file?(file_path)
|
|
331
|
+
rounded_size = (File.size(file_path).to_f / 1024).floor
|
|
332
|
+
if Downloader.size_validation(expected: ini_file[name]['size'], actual: rounded_size) &&
|
|
333
|
+
Downloader.hash_validation(expected: ini_file[name]['md5'], actual: Utils.hashfile(file_path))
|
|
334
|
+
UI.important "File already downloaded at #{file_path}"
|
|
335
|
+
return file_path
|
|
336
|
+
else
|
|
337
|
+
UI.verbose 'Deleting existing file'
|
|
338
|
+
File.delete(file_path)
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Download file
|
|
343
|
+
url = base_url + ini_file[name]['url']
|
|
344
|
+
UI.header "Downloading #{name}"
|
|
345
|
+
UI.verbose 'Downloading from ' + url.to_s.cyan.underline
|
|
346
|
+
Downloader.download_package(file_path, url, size: ini_file[name]['size'] * 1024)
|
|
347
|
+
|
|
348
|
+
# Validation download
|
|
349
|
+
rounded_size = (File.size(file_path).to_f / 1024).floor
|
|
350
|
+
if Downloader.size_validation(expected: ini_file[name]['size'], actual: rounded_size) &&
|
|
351
|
+
Downloader.hash_validation(expected: ini_file[name]['md5'], actual: Utils.hashfile(file_path))
|
|
352
|
+
UI.success "Successfully downloaded #{name}."
|
|
353
|
+
else
|
|
354
|
+
File.delete(file_path)
|
|
355
|
+
raise 'Download failed: file is corrupted, deleting it.'
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
file_path
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
## --- BEGIN LICENSE BLOCK ---
|
|
2
|
+
# Copyright (c) 2016-present WeWantToKnow AS
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
# furnished to do so, subject to the following conditions:
|
|
10
|
+
#
|
|
11
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
# copies or substantial portions of the Software.
|
|
13
|
+
#
|
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
# SOFTWARE.
|
|
21
|
+
## --- END LICENSE BLOCK ---
|
|
22
|
+
|
|
23
|
+
require 'inifile'
|
|
24
|
+
require 'u3d/utils'
|
|
25
|
+
require 'u3d_core/helper'
|
|
26
|
+
|
|
27
|
+
module U3d
|
|
28
|
+
# Load and parse INI files
|
|
29
|
+
module INIparser
|
|
30
|
+
#####################################################
|
|
31
|
+
# @!group INI parameters to load and save ini files
|
|
32
|
+
#####################################################
|
|
33
|
+
INI_NAME = 'unity-%{version}-%{os}.ini'.freeze
|
|
34
|
+
INI_LINUX_PATH = File.join(ENV['HOME'], '.u3d', 'ini_files').freeze
|
|
35
|
+
INI_MAC_PATH = File.join(ENV['HOME'], 'Library', 'Application Support', 'u3d', 'ini_files').freeze
|
|
36
|
+
INI_WIN_PATH = File.join(ENV['HOME'], 'AppData', 'Local', 'u3d', 'ini_files').freeze
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
def load_ini(version, cached_versions, os: U3dCore::Helper.operating_system, offline: false)
|
|
40
|
+
unless os == :win || os == :mac
|
|
41
|
+
raise ArgumentError, "OS #{os.id2name} does not use ini files"
|
|
42
|
+
end
|
|
43
|
+
os = if os == :mac
|
|
44
|
+
'osx'
|
|
45
|
+
else
|
|
46
|
+
os.id2name
|
|
47
|
+
end
|
|
48
|
+
ini_name = INI_NAME % { version: version, os: os }
|
|
49
|
+
Utils.ensure_dir(default_ini_path)
|
|
50
|
+
ini_path = File.expand_path(ini_name, default_ini_path)
|
|
51
|
+
unless File.file?(ini_path)
|
|
52
|
+
raise "INI file does not exist at #{ini_path}" if offline
|
|
53
|
+
uri = URI(cached_versions[version] + ini_name)
|
|
54
|
+
File.open(ini_path, 'wb') do |f|
|
|
55
|
+
data = Net::HTTP.get(uri)
|
|
56
|
+
data.tr!("\"", '')
|
|
57
|
+
data.gsub!(/Note:.+\n/, '')
|
|
58
|
+
f.write(data)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
begin
|
|
62
|
+
result = IniFile.load(ini_path).to_h
|
|
63
|
+
rescue => e
|
|
64
|
+
raise "Could not parse INI data (#{e})"
|
|
65
|
+
end
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def default_ini_path
|
|
72
|
+
case U3dCore::Helper.operating_system
|
|
73
|
+
when :linux
|
|
74
|
+
INI_LINUX_PATH
|
|
75
|
+
when :mac
|
|
76
|
+
INI_MAC_PATH
|
|
77
|
+
when :win
|
|
78
|
+
INI_WIN_PATH
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
## --- BEGIN LICENSE BLOCK ---
|
|
2
|
+
# Copyright (c) 2016-present WeWantToKnow AS
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
# furnished to do so, subject to the following conditions:
|
|
10
|
+
#
|
|
11
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
# copies or substantial portions of the Software.
|
|
13
|
+
#
|
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
# SOFTWARE.
|
|
21
|
+
## --- END LICENSE BLOCK ---
|
|
22
|
+
|
|
23
|
+
require 'u3d/utils'
|
|
24
|
+
require 'fileutils'
|
|
25
|
+
require 'file-tail'
|
|
26
|
+
|
|
27
|
+
# Mac specific only right now
|
|
28
|
+
module U3d
|
|
29
|
+
DEFAULT_LINUX_INSTALL = '/opt/'.freeze
|
|
30
|
+
DEFAULT_MAC_INSTALL = '/'.freeze
|
|
31
|
+
DEFAULT_WINDOWS_INSTALL = 'C:/Program Files/'.freeze
|
|
32
|
+
UNITY_DIR = "Unity_%s".freeze
|
|
33
|
+
UNITY_DIR_CHECK = /Unity_\d+\.\d+\.\d+[a-z]\d+/
|
|
34
|
+
|
|
35
|
+
class Installation
|
|
36
|
+
def self.create(path: nil)
|
|
37
|
+
if Helper.mac?
|
|
38
|
+
MacInstallation.new path
|
|
39
|
+
elsif Helper.linux?
|
|
40
|
+
LinuxInstallation.new path
|
|
41
|
+
else
|
|
42
|
+
WindowsInstallation.new path
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class MacInstallation < Installation
|
|
48
|
+
attr_reader :path
|
|
49
|
+
|
|
50
|
+
require 'plist'
|
|
51
|
+
|
|
52
|
+
def initialize(path: nil)
|
|
53
|
+
@path = path
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def version
|
|
57
|
+
plist['CFBundleVersion']
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def default_log_file
|
|
61
|
+
"#{ENV['HOME']}/Library/Logs/Unity/Editor.log"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def exe_path
|
|
65
|
+
"#{path}/Contents/MacOS/Unity"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def packages
|
|
69
|
+
if Utils.parse_unity_version(version)[0].to_i <= 4
|
|
70
|
+
# Unity < 5 doesn't have packages
|
|
71
|
+
return []
|
|
72
|
+
end
|
|
73
|
+
fpath = File.expand_path('../PlaybackEngines', path)
|
|
74
|
+
raise "Unity installation does not seem correct. Couldn't locate PlaybackEngines." unless Dir.exist? fpath
|
|
75
|
+
Dir.entries(fpath).select { |e| File.directory?(File.join(fpath, e)) && !(e == '.' || e == '..') }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def plist
|
|
81
|
+
@plist ||= Plist.parse_xml("#{@path}/Contents/Info.plist")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class LinuxInstallation < Installation
|
|
86
|
+
attr_reader :path
|
|
87
|
+
|
|
88
|
+
def initialize(path: nil)
|
|
89
|
+
@path = path
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def version
|
|
93
|
+
# I don't find an easy way to extract the version on Linux
|
|
94
|
+
require 'rexml/document'
|
|
95
|
+
fpath = "#{path}/Data/PlaybackEngines/LinuxStandaloneSupport/ivy.xml"
|
|
96
|
+
raise "Couldn't find file #{fpath}" unless File.exist? fpath
|
|
97
|
+
doc = REXML::Document.new(File.read(fpath))
|
|
98
|
+
version = REXML::XPath.first(doc, 'ivy-module/info/@e:unityVersion').value
|
|
99
|
+
if m = version.match(/^(.*)x(.*)Linux$/)
|
|
100
|
+
version = "#{m[1]}#{m[2]}"
|
|
101
|
+
end
|
|
102
|
+
version
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def default_log_file
|
|
106
|
+
"#{ENV['HOME']}/.config/unity3d/Editor.log"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def exe_path
|
|
110
|
+
"#{path}/Unity"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def packages
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class WindowsInstallation < Installation
|
|
119
|
+
attr_reader :path
|
|
120
|
+
|
|
121
|
+
def initialize(path: nil)
|
|
122
|
+
@path = path
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def version
|
|
126
|
+
require 'rexml/document'
|
|
127
|
+
fpath = "#{path}/Editor/Data/PlaybackEngines/windowsstandalonesupport/ivy.xml"
|
|
128
|
+
raise "Couldn't find file #{fpath}" unless File.exist? fpath
|
|
129
|
+
doc = REXML::Document.new(File.read(fpath))
|
|
130
|
+
version = REXML::XPath.first(doc, 'ivy-module/info/@e:unityVersion').value
|
|
131
|
+
|
|
132
|
+
version
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def default_log_file
|
|
136
|
+
if @logfile.nil?
|
|
137
|
+
begin
|
|
138
|
+
loc_appdata = Utils.windows_local_appdata
|
|
139
|
+
log_dir = File.expand_path('Unity/Editor/', loc_appdata)
|
|
140
|
+
UI.important "Log directory (#{log_dir}) does not exist" unless Dir.exist? log_dir
|
|
141
|
+
@logfile = File.expand_path('Editor.log', log_dir)
|
|
142
|
+
rescue RuntimeError => ex
|
|
143
|
+
UI.error "Unable to retrieve the editor logfile: #{ex}"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
@logfile
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def exe_path
|
|
150
|
+
File.join(@path, 'Editor', 'Unity.exe')
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def packages
|
|
154
|
+
# Unity prior to Unity5 did not have package
|
|
155
|
+
return [] if Utils.parse_unity_version(version)[0].to_i <= 4
|
|
156
|
+
fpath = "#{path}/Editor/Data/PlaybackEngines/"
|
|
157
|
+
raise "Unity installation does not seem correct. Couldn't locate PlaybackEngines." unless Dir.exist? fpath
|
|
158
|
+
Dir.entries(fpath).select { |e| File.directory?(File.join(fpath, e)) && !(e == '.' || e == '..') }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
class Runner
|
|
163
|
+
def run(installation, args, raw_logs: false)
|
|
164
|
+
require 'fileutils'
|
|
165
|
+
|
|
166
|
+
log_file = find_logFile_in_args(args)
|
|
167
|
+
|
|
168
|
+
if log_file # we wouldn't want to do that for the default log file.
|
|
169
|
+
File.delete(log_file) if File.exist?(log_file)
|
|
170
|
+
else
|
|
171
|
+
log_file = installation.default_log_file
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
FileUtils.touch(log_file)
|
|
175
|
+
|
|
176
|
+
tail_thread = Thread.new do
|
|
177
|
+
begin
|
|
178
|
+
if raw_logs
|
|
179
|
+
pipe(log_file) { |l| UI.message l.rstrip }
|
|
180
|
+
else
|
|
181
|
+
analyzer = LogAnalyzer.new
|
|
182
|
+
pipe(log_file) { |l| analyzer.parse_line l }
|
|
183
|
+
end
|
|
184
|
+
rescue => e
|
|
185
|
+
UI.error "Failure while trying to pipe #{log_file}: #{e.message}"
|
|
186
|
+
e.backtrace.each { |l| UI.error " #{l}" }
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
begin
|
|
191
|
+
args.unshift(installation.exe_path)
|
|
192
|
+
if Helper.windows?
|
|
193
|
+
args.map! { |a| a =~ / / ? "\"#{a}\"" : a }
|
|
194
|
+
else
|
|
195
|
+
args.map!(&:shellescape)
|
|
196
|
+
end
|
|
197
|
+
U3dCore::CommandExecutor.execute(command: args)
|
|
198
|
+
ensure
|
|
199
|
+
sleep 0.5
|
|
200
|
+
Thread.kill(tail_thread)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def find_logFile_in_args(args)
|
|
205
|
+
find_arg_in_args('-logFile', args)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def find_projectpath_in_args(args)
|
|
209
|
+
find_arg_in_args('-projectpath', args)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def find_arg_in_args(arg_to_find, args)
|
|
213
|
+
raise 'Only arguments of type array supported right now' unless args.is_a?(Array)
|
|
214
|
+
args.each_with_index do |arg, index|
|
|
215
|
+
return args[index + 1] if arg == arg_to_find && index < args.count - 1
|
|
216
|
+
end
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def pipe(file)
|
|
223
|
+
File.open(file, 'r') do |f|
|
|
224
|
+
f.extend File::Tail
|
|
225
|
+
f.interval = 0.1
|
|
226
|
+
f.max_interval = 0.4
|
|
227
|
+
f.backward 100
|
|
228
|
+
f.tail { |l| yield l }
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
class Installer
|
|
234
|
+
def self.create
|
|
235
|
+
installer = if Helper.mac?
|
|
236
|
+
MacInstaller.new
|
|
237
|
+
elsif Helper.linux?
|
|
238
|
+
LinuxInstaller.new
|
|
239
|
+
else
|
|
240
|
+
WindowsInstaller.new
|
|
241
|
+
end
|
|
242
|
+
if UI.interactive?
|
|
243
|
+
unclean = []
|
|
244
|
+
installer.installed.each { |unity| unclean << unity unless unity.path =~ UNITY_DIR_CHECK }
|
|
245
|
+
if !unclean.empty? && UI.confirm("#{unclean.count} Unity installation should be moved. Proceed?")
|
|
246
|
+
unclean.each { |unity| installer.sanitize_install(unity) }
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
installer
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def self.install_module(file_path, version, installation_path: nil, info: {})
|
|
253
|
+
extension = File.extname(file_path)
|
|
254
|
+
if extension == '.pkg'
|
|
255
|
+
path = installation_path || DEFAULT_MAC_INSTALL
|
|
256
|
+
MacInstaller.install_pkg(
|
|
257
|
+
file_path,
|
|
258
|
+
version: version,
|
|
259
|
+
target_path: path
|
|
260
|
+
)
|
|
261
|
+
elsif extension == '.exe'
|
|
262
|
+
path = installation_path || File.join(DEFAULT_WINDOWS_INSTALL, UNITY_DIR % version)
|
|
263
|
+
WindowsInstaller.install_exe(
|
|
264
|
+
file_path,
|
|
265
|
+
installation_path: path,
|
|
266
|
+
info: info
|
|
267
|
+
)
|
|
268
|
+
elsif extension == '.sh'
|
|
269
|
+
path = installation_path || File.join(DEFAULT_LINUX_INSTALL, UNITY_DIR % version)
|
|
270
|
+
LinuxInstaller.install_sh(
|
|
271
|
+
file_path,
|
|
272
|
+
installation_path: path
|
|
273
|
+
)
|
|
274
|
+
else
|
|
275
|
+
raise "File type #{extension} not yet supported"
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
class MacInstaller
|
|
281
|
+
def sanitize_install(unity)
|
|
282
|
+
source_path = File.expand_path('..', unity.path)
|
|
283
|
+
parent = File.expand_path('..', source_path)
|
|
284
|
+
new_path = File.join(parent, UNITY_DIR % unity.version)
|
|
285
|
+
UI.important "Moving #{source_path} to #{new_path}..."
|
|
286
|
+
source_path = "\"#{source_path}\"" if source_path =~ / /
|
|
287
|
+
new_path = "\"#{new_path}\"" if new_path =~ / /
|
|
288
|
+
U3dCore::CommandExecutor.execute(command: "mv #{source_path} #{new_path}", admin: true)
|
|
289
|
+
rescue => e
|
|
290
|
+
UI.error "Unable to move #{source_path} to #{new_path}: #{e}"
|
|
291
|
+
else
|
|
292
|
+
UI.success "Successfully moved #{source_path} to #{new_path}"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def installed
|
|
296
|
+
unless (`mdutil -s /` =~ /disabled/).nil?
|
|
297
|
+
$stderr.puts 'Please enable Spotlight indexing for /Applications.'
|
|
298
|
+
exit(1)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
bundle_identifiers = ['com.unity3d.UnityEditor4.x', 'com.unity3d.UnityEditor5.x']
|
|
302
|
+
|
|
303
|
+
mdfind_args = bundle_identifiers.map { |bi| "kMDItemCFBundleIdentifier == '#{bi}'" }.join(' || ')
|
|
304
|
+
|
|
305
|
+
cmd = "mdfind \"#{mdfind_args}\" 2>/dev/null"
|
|
306
|
+
UI.verbose cmd
|
|
307
|
+
versions = `#{cmd}`.split("\n").map { |path| MacInstallation.new(path: path) }
|
|
308
|
+
|
|
309
|
+
# sorting should take into account stable/patch etc
|
|
310
|
+
versions.sort! { |x, y| x.version <=> y.version }
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def self.install_pkg(file_path, version: nil, target_path: nil)
|
|
314
|
+
target_path ||= DEFAULT_MAC_INSTALL
|
|
315
|
+
command = "installer -pkg #{file_path.shellescape} -target #{target_path.shellescape}"
|
|
316
|
+
unity = Installer.create.installed.find { |u| u.version == version }
|
|
317
|
+
if unity.nil?
|
|
318
|
+
UI.verbose "No Unity install for version #{version} was found"
|
|
319
|
+
U3dCore::CommandExecutor.execute(command: command, admin: true)
|
|
320
|
+
else
|
|
321
|
+
begin
|
|
322
|
+
path = File.expand_path('..', unity.path)
|
|
323
|
+
temp_path = File.join(target_path, 'Applications', 'Unity')
|
|
324
|
+
move_to_temp = (temp_path != path)
|
|
325
|
+
if move_to_temp
|
|
326
|
+
UI.verbose "Temporary switching location of #{path} to #{temp_path} for installation purpose"
|
|
327
|
+
FileUtils.mv path, temp_path
|
|
328
|
+
end
|
|
329
|
+
U3dCore::CommandExecutor.execute(command: command, admin: true)
|
|
330
|
+
ensure
|
|
331
|
+
FileUtils.mv temp_path, path if move_to_temp
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
rescue => e
|
|
335
|
+
UI.error "Failed to install pkg at #{file_path}: #{e}"
|
|
336
|
+
else
|
|
337
|
+
UI.success "Successfully installed package from #{file_path}"
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
class LinuxInstaller
|
|
342
|
+
def sanitize_install(unity)
|
|
343
|
+
source_path = File.expand_path(unity.path)
|
|
344
|
+
parent = File.expand_path('..', source_path)
|
|
345
|
+
new_path = File.join(parent, UNITY_DIR % unity.version)
|
|
346
|
+
UI.important "Moving #{source_path} to #{new_path}..."
|
|
347
|
+
source_path = "\"#{source_path}\"" if source_path =~ / /
|
|
348
|
+
new_path = "\"#{new_path}\"" if new_path =~ / /
|
|
349
|
+
U3dCore::CommandExecutor.execute(command: "mv #{source_path} #{new_path}", admin: true)
|
|
350
|
+
rescue => e
|
|
351
|
+
UI.error "Unable to move #{source_path} to #{new_path}: #{e}"
|
|
352
|
+
else
|
|
353
|
+
UI.success "Successfully moved #{source_path} to #{new_path}"
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def installed
|
|
357
|
+
find = File.join(DEFAULT_LINUX_INSTALL, 'Unity*')
|
|
358
|
+
versions = Dir[find].map { |path| LinuxInstallation.new(path: path) }
|
|
359
|
+
|
|
360
|
+
# sorting should take into account stable/patch etc
|
|
361
|
+
versions.sort! { |x, y| x.version <=> y.version }
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def self.install_sh(file, installation_path: nil)
|
|
365
|
+
cmd = file.shellescape
|
|
366
|
+
if installation_path
|
|
367
|
+
Utils.ensure_dir(installation_path)
|
|
368
|
+
U3dCore::CommandExecutor.execute(command: "cd #{installation_path}; #{cmd}", admin: true)
|
|
369
|
+
else
|
|
370
|
+
U3dCore::CommandExecutor.execute(command: cmd, admin: true)
|
|
371
|
+
end
|
|
372
|
+
rescue => e
|
|
373
|
+
UI.error "Failed to install bash file at #{file_path}: #{e}"
|
|
374
|
+
else
|
|
375
|
+
UI.success 'Installation successful'
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
class WindowsInstaller
|
|
380
|
+
def sanitize_install(unity)
|
|
381
|
+
source_path = File.expand_path(unity.path)
|
|
382
|
+
parent = File.expand_path('..', source_path)
|
|
383
|
+
new_path = File.join(parent, UNITY_DIR % unity.version)
|
|
384
|
+
UI.important "Moving #{source_path} to #{new_path}..."
|
|
385
|
+
source_path.tr!('/', '\\')
|
|
386
|
+
new_path.tr!('/', '\\')
|
|
387
|
+
source_path = "\"" + source_path + "\"" if source_path =~ / /
|
|
388
|
+
new_path = "\"" + new_path + "\"" if new_path =~ / /
|
|
389
|
+
U3dCore::CommandExecutor.execute(command: "move #{source_path} #{new_path}", admin: true)
|
|
390
|
+
rescue => e
|
|
391
|
+
UI.error "Unable to move #{source_path} to #{new_path}: #{e}"
|
|
392
|
+
else
|
|
393
|
+
UI.success "Successfully moved #{source_path} to #{new_path}"
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def installed
|
|
397
|
+
find = File.join(DEFAULT_WINDOWS_INSTALL, 'Unity*', 'Editor', 'Uninstall.exe')
|
|
398
|
+
versions = Dir[find].map { |path| WindowsInstallation.new(path: File.expand_path('../..', path)) }
|
|
399
|
+
|
|
400
|
+
# sorting should take into account stable/patch etc
|
|
401
|
+
versions.sort! { |x, y| x.version <=> y.version }
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def self.install_exe(file_path, installation_path: nil, info: {})
|
|
405
|
+
installation_path ||= DEFAULT_WINDOWS_INSTALL
|
|
406
|
+
final_path = installation_path.tr('/', '\\')
|
|
407
|
+
Utils.ensure_dir(final_path)
|
|
408
|
+
begin
|
|
409
|
+
command = nil
|
|
410
|
+
if info['cmd']
|
|
411
|
+
command = info['cmd']
|
|
412
|
+
command.sub!(/{FILENAME}/, file_path)
|
|
413
|
+
command.sub!(/{INSTDIR}/, final_path)
|
|
414
|
+
command.sub!(/{DOCDIR}/, final_path)
|
|
415
|
+
command.sub!(/{MODULEDIR}/, final_path)
|
|
416
|
+
command.sub!(/\/D=/, '/S /D=') unless /\/S/ =~ command
|
|
417
|
+
end
|
|
418
|
+
command ||= file_path.to_s
|
|
419
|
+
U3dCore::CommandExecutor.execute(command: command, admin: true)
|
|
420
|
+
rescue => e
|
|
421
|
+
UI.error "Failed to install exe at #{file_path}: #{e}"
|
|
422
|
+
else
|
|
423
|
+
UI.success "Successfully installed #{info['title']}"
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
class UnityProject
|
|
429
|
+
attr_reader :path
|
|
430
|
+
|
|
431
|
+
def initialize(path)
|
|
432
|
+
@path = path
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def exist?
|
|
436
|
+
Dir.exist?("#{@path}/Assets") && Dir.exist?("#{@path}/ProjectSettings")
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def editor_version
|
|
440
|
+
require 'yaml'
|
|
441
|
+
yaml = YAML.load(File.read("#{@path}/ProjectSettings/ProjectVersion.txt"))
|
|
442
|
+
yaml['m_EditorVersion']
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
end
|