u3d 1.1.5 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/u3d.rb CHANGED
@@ -32,11 +32,13 @@ require 'u3d/commands_generator'
32
32
  require 'u3d/download_validator'
33
33
  require 'u3d/downloader'
34
34
  require 'u3d/failure_reporter'
35
- require 'u3d/iniparser'
35
+ require 'u3d/ini_modules_parser'
36
36
  require 'u3d/installation'
37
37
  require 'u3d/installer'
38
+ require 'u3d/hub_modules_parser'
38
39
  require 'u3d/log_analyzer'
39
40
  require 'u3d/unity_license'
41
+ require 'u3d/unity_module'
40
42
  require 'u3d/unity_project'
41
43
  require 'u3d/unity_runner'
42
44
  require 'u3d/unity_version_definition'
@@ -124,19 +124,16 @@ module U3d
124
124
  end
125
125
  sorted_keys = vcomparators.sort.map { |v| v.version.to_s }
126
126
 
127
+ show_packages = options[:packages]
128
+ packages = UnityModule.load_modules(sorted_keys, cache_versions, os: os) if show_packages
129
+
127
130
  sorted_keys.each do |k|
128
131
  v = cache_versions[k]
129
132
  UI.message "Version #{k}: " + v.to_s.cyan.underline
130
- next unless options[:packages]
131
- inif = nil
132
- begin
133
- inif = U3d::INIparser.load_ini(k, cache_versions, os: os)
134
- rescue StandardError => e
135
- UI.error "Could not load packages for this version (#{e})"
136
- else
137
- UI.message 'Packages:'
138
- inif.each_key { |pack| UI.message " - #{pack}" }
139
- end
133
+ next unless show_packages
134
+ version_packages = packages[k]
135
+ UI.message 'Packages:'
136
+ version_packages.each { |package| UI.message " - #{package.id.capitalize}" }
140
137
  end
141
138
  end
142
139
 
@@ -146,8 +143,6 @@ module U3d
146
143
  UI.user_error!("You cannot use the --operating_system and the --install options together") if options[:install] && options[:operating_system]
147
144
  os = valid_os_or_current(options[:operating_system])
148
145
 
149
- packages = packages_with_unity_first(options)
150
-
151
146
  cache_versions = cache_versions(os, offline: !options[:download])
152
147
  version = interpret_latest(version, cache_versions)
153
148
  unless cache_versions[version]
@@ -157,7 +152,13 @@ module U3d
157
152
 
158
153
  definition = UnityVersionDefinition.new(version, os, cache_versions)
159
154
  unity = check_unity_presence(version: version)
160
- return unless enforce_setup_coherence(packages, options, unity, definition)
155
+ packages = options[:packages] || ['Unity']
156
+
157
+ begin
158
+ packages = enforce_setup_coherence(packages, options, unity, definition)
159
+ rescue InstallationSetupError
160
+ return
161
+ end
161
162
 
162
163
  verify_package_names(definition, packages)
163
164
 
@@ -332,12 +333,6 @@ module U3d
332
333
  iversion
333
334
  end
334
335
 
335
- def packages_with_unity_first(options)
336
- temp = options[:packages] || ['Unity']
337
- temp.insert(0, 'Unity') if temp.delete('Unity')
338
- temp
339
- end
340
-
341
336
  def check_unity_presence(version: nil)
342
337
  # idea: we could support matching 5.3.6p3 if passed 5.3.6
343
338
  installed = Installer.create.installed
@@ -357,6 +352,7 @@ module U3d
357
352
  packages.clear
358
353
  packages.concat(definition.available_packages)
359
354
  end
355
+ packages = sort_packages(packages, definition)
360
356
  if options[:install]
361
357
  if unity
362
358
  UI.important "Unity #{unity.version} is already installed"
@@ -369,22 +365,64 @@ module U3d
369
365
  # FIXME: Move me to the WindowsInstaller
370
366
  options[:installation_path] ||= unity.root_path if definition.os == :win
371
367
  end
372
- packages.select { |pack| unity.package_installed?(pack) }.each do |pack|
373
- packages.delete pack
374
- UI.important "Ignoring #{pack} module, it is already installed"
375
- end
376
- return false if packages.empty?
368
+ # FIXME: unity.package_installed? is not reliable
369
+ packages = detect_installed_packages(packages, unity)
370
+ packages = detect_missing_dependencies(packages, unity, definition)
371
+ raise InstallationSetupError if packages.empty?
377
372
  else
378
- unless packages.include?('Unity')
373
+ unless packages.map(&:downcase).include?('unity')
379
374
  UI.error 'Please install Unity before any of its packages'
380
- return false
375
+ raise InstallationSetupError
381
376
  end
382
377
  end
383
378
  end
384
- true
379
+ packages
385
380
  end
386
381
  # rubocop:enable Metrics/BlockNesting
387
382
 
383
+ def sort_packages(packages, definition)
384
+ packages.sort do |a, b|
385
+ package_a = definition[a]
386
+ package_b = definition[b]
387
+ if package_a.depends_on?(package_b) # b must come first
388
+ 1
389
+ elsif package_b.depends_on?(package_a) # a must come first
390
+ -1
391
+ else
392
+ a <=> b # Resort to alphabetical sorting
393
+ end
394
+ end
395
+ end
396
+
397
+ def detect_installed_packages(packages, unity)
398
+ result = packages
399
+ packages.select { |pack| unity.package_installed?(pack) }.each do |pack|
400
+ result.delete pack
401
+ UI.important "Ignoring #{pack} module, it is already installed"
402
+ end
403
+ result
404
+ end
405
+
406
+ def detect_missing_dependencies(packages, unity, definition)
407
+ result = packages
408
+ packages.reject { |package| can_install?(package, unity, definition, packages) }.each do |pack|
409
+ # See FIXME for package_installed?
410
+ # result.delete pack
411
+ package = definition[pack]
412
+ UI.important "#{package.name} depends on #{package.depends_on}, but it's neither installed nor being installed."
413
+ end
414
+ result
415
+ end
416
+
417
+ def can_install?(package_name, unity, definition, installing)
418
+ package = definition[package_name]
419
+ return true unless package.depends_on
420
+ return true if unity.package_installed?(package.depends_on)
421
+ installing.map { |other| definition[other] }.any? do |other|
422
+ other.id == package.depends_on || other.name == package.depends_on
423
+ end
424
+ end
425
+
388
426
  def get_administrative_privileges(options)
389
427
  U3dCore::Globals.use_keychain = true if options[:keychain] && Helper.mac?
390
428
  UI.important 'Root privileges are required'
@@ -394,3 +432,6 @@ module U3d
394
432
  end
395
433
  # rubocop:enable ClassLength
396
434
  end
435
+
436
+ class InstallationSetupError < StandardError
437
+ end
@@ -51,7 +51,7 @@ module U3d
51
51
 
52
52
  class LinuxValidator < DownloadValidator
53
53
  def validate(package, file, definition)
54
- return size_validation(expected: definition[package]['size'], actual: File.size(file)) if definition.ini && definition[package]['size']
54
+ return size_validation(expected: definition[package].download_size, actual: File.size(file)) if definition[package] && definition[package].download_size
55
55
  UI.important "No file validation available, #{file} is assumed to be correct"
56
56
  true
57
57
  end
@@ -59,25 +59,25 @@ module U3d
59
59
 
60
60
  class MacValidator < DownloadValidator
61
61
  def validate(package, file, definition)
62
- if definition[package]['size'] % 1000 && definition[package]['md5'].nil?
63
- UI.verbose "File '#{definition[package]['title']}' seems external. Validation skipped"
62
+ if definition[package].download_size % 1000 && definition[package].checksum.nil?
63
+ UI.verbose "File '#{definition[package].name}' seems external. Validation skipped"
64
64
  return true
65
65
  end
66
- size_validation(expected: definition[package]['size'], actual: File.size(file)) &&
67
- hash_validation(expected: definition[package]['md5'], actual: Utils.hashfile(file))
66
+ size_validation(expected: definition[package].download_size, actual: File.size(file)) &&
67
+ hash_validation(expected: definition[package].checksum, actual: Utils.hashfile(file))
68
68
  end
69
69
  end
70
70
 
71
71
  class WindowsValidator < DownloadValidator
72
72
  def validate(package, file, definition)
73
73
  # External packages have no md5 and a false size value
74
- if definition[package]['size'] % 1000 && definition[package]['md5'].nil?
75
- UI.verbose "File '#{definition[package]['title']}' seems external. Validation skipped"
74
+ if definition[package].download_size % 1000 && definition[package].checksum.nil?
75
+ UI.verbose "File '#{definition[package].name}' seems external. Validation skipped"
76
76
  return true
77
77
  end
78
78
  rounded_size = (File.size(file).to_f / 1024).floor
79
- size_validation(expected: definition[package]['size'], actual: rounded_size) &&
80
- hash_validation(expected: definition[package]['md5'], actual: Utils.hashfile(file))
79
+ size_validation(expected: definition[package].download_size, actual: rounded_size) &&
80
+ hash_validation(expected: definition[package].checksum, actual: Utils.hashfile(file))
81
81
  end
82
82
  end
83
83
  end
@@ -30,6 +30,8 @@ module U3d
30
30
  DOWNLOAD_DIRECTORY = 'Unity_Packages'.freeze
31
31
  # Path to the directory for the package downloading
32
32
  DOWNLOAD_PATH = "#{ENV['HOME']}/Downloads".freeze
33
+ # Regex to get the name of a localization asset
34
+ UNITY_LANGUAGE_FILE_REGEX = %r{\/\d+/[0-9\.]+\/([\w-]+)$}
33
35
  # Regex to get the name of a package out of its file name
34
36
  UNITY_MODULE_FILE_REGEX = %r{\/([\w\-_\.\+]+\.(?:pkg|exe|zip|sh|deb|msi|xz))[^\/]*$}
35
37
 
@@ -101,11 +103,12 @@ module U3d
101
103
 
102
104
  def get_package(downloader, validator, package, definition, files)
103
105
  path, url = downloader.destination_and_url_for(package, definition)
106
+ package_info = definition[package]
104
107
  if File.file?(path)
105
- UI.verbose "Installer file for #{package} seems to be present at #{path}"
108
+ UI.verbose "Installer file for #{package_info.name} seems to be present at #{path}"
106
109
  if validator.validate(package, path, definition)
107
- UI.message "#{package.capitalize} is already downloaded"
108
- files << [package, path, definition[package]]
110
+ UI.message "#{package_info.name} is already downloaded"
111
+ files << [package, path, package_info]
109
112
  return
110
113
  else
111
114
  extension = File.extname(path)
@@ -115,14 +118,14 @@ module U3d
115
118
  end
116
119
  end
117
120
 
118
- UI.header "Downloading #{package} version #{definition.version}"
121
+ UI.header "Downloading #{package_info.name} version #{definition.version}"
119
122
  UI.message 'Downloading from ' + url.to_s.cyan.underline
120
123
  UI.message 'Download will be found at ' + path
121
- download_package(path, url, size: definition.size_in_bytes(package))
124
+ download_package(path, url, size: package_info.download_size)
122
125
 
123
126
  if validator.validate(package, path, definition)
124
127
  UI.success "Successfully downloaded #{package}."
125
- files << [package, path, definition[package]]
128
+ files << [package, path, package_info]
126
129
  else
127
130
  UI.error "Failed to download #{package}"
128
131
  end
@@ -148,7 +151,14 @@ module U3d
148
151
 
149
152
  dir = File.join(Downloader.download_directory, definition.version)
150
153
  Utils.ensure_dir(dir)
151
- file_name = UNITY_MODULE_FILE_REGEX.match(final_url)[1]
154
+
155
+ file_name = if (language_match = UNITY_LANGUAGE_FILE_REGEX.match(final_url))
156
+ language_match[1] + '.po' # Unity uses PO (Portable object files) for localization
157
+ elsif (module_match = UNITY_MODULE_FILE_REGEX.match(final_url))
158
+ module_match[1]
159
+ else
160
+ raise "Unable to download file at #{final_url}. Please report it to the u3d issues on Github: https://github.com/DragonBox/u3d/issues/new"
161
+ end
152
162
 
153
163
  destination = File.expand_path(file_name, dir)
154
164
 
@@ -156,7 +166,7 @@ module U3d
156
166
  end
157
167
 
158
168
  def url_for(package, definition)
159
- url = definition[package]['url']
169
+ url = definition[package].url
160
170
  if url
161
171
  if url =~ /^http/
162
172
  Utils.final_url(url)
@@ -0,0 +1,88 @@
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 'json'
24
+ require 'u3d/unity_versions'
25
+ require 'u3d/utils'
26
+ require 'u3d_core/helper'
27
+
28
+ module U3d
29
+ module HubModulesParser
30
+ class << self
31
+ HUB_MODULES_NAME = '%<version>s-%<os>s-modules.json'.freeze
32
+
33
+ def load_modules(version, os: U3dCore::Helper.operating_system, offline: false)
34
+ path = modules_path(version, os)
35
+
36
+ unless File.file?(path) && File.size(path) > 0
37
+ return [] if offline # Should not raise, not all versions have hub modules
38
+ versions = download_modules(os: os)
39
+
40
+ unless versions.include? version
41
+ UI.verbose "No modules registered for UnityHub for version #{version}"
42
+ return []
43
+ end
44
+ end
45
+
46
+ return JSON.parse(File.read(path))
47
+ end
48
+
49
+ def download_modules(os: U3dCore::Helper.operating_system)
50
+ url = UnityVersions.json_url_for(json_os(os))
51
+ builds = UnityVersions.fetch_json(url, UnityVersions::UNITY_LATEST_JSON)
52
+ builds.each { |build| write_modules(build, os) }
53
+ return builds.map { |build| build['versions'] }
54
+ end
55
+
56
+ private
57
+
58
+ def json_os(os)
59
+ platform_versions = case os
60
+ when :win
61
+ UnityVersions::WindowsVersions
62
+ when :linux
63
+ UnityVersions::LinuxVersions
64
+ when :mac
65
+ UnityVersions::MacVersions
66
+ end
67
+
68
+ return platform_versions::JSON_OS
69
+ end
70
+
71
+ def modules_path(version, os)
72
+ file_name = format(HUB_MODULES_NAME, version: version, os: os)
73
+ File.join(default_modules_path, file_name)
74
+ end
75
+
76
+ def default_modules_path
77
+ File.join(U3dCore::Helper.data_path, 'unity_hub_modules')
78
+ end
79
+
80
+ def write_modules(build, os)
81
+ path = modules_path(build['version'], os)
82
+ Utils.ensure_dir(File.dirname(path))
83
+
84
+ File.open(path, 'w') { |file| file.write build['modules'].to_json }
85
+ end
86
+ end
87
+ end
88
+ end
@@ -25,8 +25,32 @@ require 'u3d/utils'
25
25
  require 'u3d_core/helper'
26
26
 
27
27
  module U3d
28
+ # Backwards compatibility
29
+ module INIParser
30
+ class << self
31
+ def method_missing(method_name, *arguments, &block)
32
+ UI.deprecated 'INIParser is obsolete, Use INIModulesParser instead'
33
+ if INIModulesParser.respond_to?(method_name)
34
+ INIModulesParser.send(method_name, *arguments, &block)
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def respond_to?(method_name, include_private = false)
41
+ UI.deprecated 'INIParser is obsolete, Use INIModulesParser instead'
42
+ INIModulesParser.respond_to?(method_name, include_private)
43
+ end
44
+
45
+ def respond_to_missing?(method_name, include_private = false)
46
+ UI.deprecated 'INIParser is obsolete, Use INIModulesParser instead'
47
+ INIModulesParser.respond_to_missing?(method_name, include_private)
48
+ end
49
+ end
50
+ end
51
+
28
52
  # Load and parse INI files
29
- module INIparser
53
+ module INIModulesParser
30
54
  #####################################################
31
55
  # @!group INI parameters to load and save ini files
32
56
  #####################################################
@@ -42,7 +66,7 @@ module U3d
42
66
  ini_name = format(INI_NAME, version: version, os: os)
43
67
  Utils.ensure_dir(default_ini_path)
44
68
  ini_path = File.expand_path(ini_name, default_ini_path)
45
- unless File.file?(ini_path)
69
+ unless File.file?(ini_path) && File.size(ini_path) > 0
46
70
  raise "INI file does not exist at #{ini_path}" if offline
47
71
  download_ini(version, cached_versions, os, ini_name, ini_path)
48
72
  end
@@ -58,9 +82,8 @@ module U3d
58
82
  ini_name = format(INI_NAME, version: version, os: 'linux')
59
83
  Utils.ensure_dir(default_ini_path)
60
84
  ini_path = File.expand_path(ini_name, default_ini_path)
61
- return if File.file? ini_path
62
- File.open(ini_path, 'wb') do |f|
63
- f.write %([Unity]
85
+ return if File.file?(ini_path) && File.size(ini_path) > 0
86
+ data = %([Unity]
64
87
  ; -- NOTE --
65
88
  ; This is not an official Unity file
66
89
  ; This has been created by u3d
@@ -69,7 +92,7 @@ title=Unity
69
92
  size=#{size}
70
93
  url=#{url}
71
94
  )
72
- end
95
+ write_ini_file(ini_path, data)
73
96
  end
74
97
 
75
98
  private
@@ -89,10 +112,16 @@ url=#{url}
89
112
  end
90
113
  uri = URI(cached_versions[version] + ini_name)
91
114
  UI.verbose("Searching for ini file at #{uri}")
115
+
116
+ data = Net::HTTP.get(uri)
117
+ data.tr!("\"", '')
118
+ data.gsub!(/Note:.+\n/, '')
119
+
120
+ write_ini_file(ini_path, data)
121
+ end
122
+
123
+ def write_ini_file(ini_path, data)
92
124
  File.open(ini_path, 'wb') do |f|
93
- data = Net::HTTP.get(uri)
94
- data.tr!("\"", '')
95
- data.gsub!(/Note:.+\n/, '')
96
125
  f.write(data)
97
126
  end
98
127
  end
@@ -296,8 +296,21 @@ module U3d
296
296
  end
297
297
 
298
298
  class WindowsInstallationHelper
299
- def build_number(exe_path)
300
- s = string_file_info("Unity Version", exe_path)
299
+ def initialize(exe_path)
300
+ @exe_path = exe_path
301
+ end
302
+
303
+ def version
304
+ s = unity_version_info
305
+ if s
306
+ a = s.split("_")
307
+ return a[0] unless a.empty?
308
+ end
309
+ nil
310
+ end
311
+
312
+ def build_number
313
+ s = unity_version_info
301
314
  if s
302
315
  a = s.split("_")
303
316
  return a[1] if a.count > 1
@@ -307,6 +320,10 @@ module U3d
307
320
 
308
321
  private
309
322
 
323
+ def unity_version_info
324
+ @uvf ||= string_file_info('Unity Version', @exe_path)
325
+ end
326
+
310
327
  def string_file_info(info, path)
311
328
  require "Win32API"
312
329
  get_file_version_info_size = Win32API.new('version.dll', 'GetFileVersionInfoSize', 'PP', 'L')
@@ -347,6 +364,9 @@ module U3d
347
364
 
348
365
  class WindowsInstallation < Installation
349
366
  def version
367
+ version = helper.version
368
+ return version unless version.nil?
369
+
350
370
  path = "#{root_path}/Editor/Data/"
351
371
  package = PlaybackEngineUtils.list_module_configs(path).first
352
372
  raise "Couldn't find a module under #{path}" unless package
@@ -354,7 +374,7 @@ module U3d
354
374
  end
355
375
 
356
376
  def build_number
357
- @build_number ||= WindowsInstallationHelper.new.build_number(exe_path)
377
+ helper.build_number
358
378
  end
359
379
 
360
380
  def default_log_file
@@ -408,5 +428,11 @@ module U3d
408
428
  def clean_install?
409
429
  do_not_move? || !(root_path =~ UNITY_DIR_CHECK).nil?
410
430
  end
431
+
432
+ private
433
+
434
+ def helper
435
+ @helper ||= WindowsInstallationHelper.new(exe_path)
436
+ end
411
437
  end
412
438
  end