u3d 1.1.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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