pdk 1.10.0 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +50 -1
  3. data/lib/pdk.rb +16 -1
  4. data/lib/pdk/analytics.rb +28 -0
  5. data/lib/pdk/analytics/client/google_analytics.rb +138 -0
  6. data/lib/pdk/analytics/client/noop.rb +23 -0
  7. data/lib/pdk/analytics/util.rb +17 -0
  8. data/lib/pdk/cli.rb +37 -0
  9. data/lib/pdk/cli/build.rb +2 -0
  10. data/lib/pdk/cli/bundle.rb +2 -1
  11. data/lib/pdk/cli/convert.rb +2 -0
  12. data/lib/pdk/cli/exec.rb +28 -1
  13. data/lib/pdk/cli/new/class.rb +2 -0
  14. data/lib/pdk/cli/new/defined_type.rb +2 -0
  15. data/lib/pdk/cli/new/module.rb +2 -0
  16. data/lib/pdk/cli/new/provider.rb +2 -0
  17. data/lib/pdk/cli/new/task.rb +2 -0
  18. data/lib/pdk/cli/test.rb +0 -1
  19. data/lib/pdk/cli/test/unit.rb +13 -10
  20. data/lib/pdk/cli/update.rb +21 -0
  21. data/lib/pdk/cli/util.rb +35 -0
  22. data/lib/pdk/cli/util/interview.rb +7 -1
  23. data/lib/pdk/cli/validate.rb +9 -2
  24. data/lib/pdk/config.rb +94 -0
  25. data/lib/pdk/config/errors.rb +5 -0
  26. data/lib/pdk/config/json.rb +23 -0
  27. data/lib/pdk/config/namespace.rb +273 -0
  28. data/lib/pdk/config/validator.rb +31 -0
  29. data/lib/pdk/config/value.rb +94 -0
  30. data/lib/pdk/config/yaml.rb +31 -0
  31. data/lib/pdk/generate/module.rb +3 -2
  32. data/lib/pdk/logger.rb +21 -1
  33. data/lib/pdk/module/build.rb +58 -0
  34. data/lib/pdk/module/convert.rb +1 -1
  35. data/lib/pdk/module/metadata.rb +1 -0
  36. data/lib/pdk/module/templatedir.rb +24 -5
  37. data/lib/pdk/module/update_manager.rb +2 -2
  38. data/lib/pdk/report/event.rb +3 -3
  39. data/lib/pdk/template_file.rb +1 -1
  40. data/lib/pdk/tests/unit.rb +10 -12
  41. data/lib/pdk/util.rb +9 -0
  42. data/lib/pdk/util/bundler.rb +5 -9
  43. data/lib/pdk/util/filesystem.rb +37 -0
  44. data/lib/pdk/util/puppet_version.rb +1 -1
  45. data/lib/pdk/util/ruby_version.rb +16 -6
  46. data/lib/pdk/util/template_uri.rb +72 -43
  47. data/lib/pdk/util/version.rb +1 -1
  48. data/lib/pdk/util/windows.rb +1 -0
  49. data/lib/pdk/util/windows/api_types.rb +0 -7
  50. data/lib/pdk/util/windows/file.rb +1 -1
  51. data/lib/pdk/util/windows/string.rb +1 -1
  52. data/lib/pdk/validate/base_validator.rb +8 -6
  53. data/lib/pdk/validate/puppet/puppet_syntax.rb +1 -1
  54. data/lib/pdk/validate/ruby/rubocop.rb +1 -1
  55. data/lib/pdk/version.rb +1 -1
  56. data/locales/pdk.pot +223 -114
  57. metadata +103 -50
@@ -7,7 +7,7 @@ module PDK
7
7
  module Test
8
8
  class Unit
9
9
  def self.cmd(tests, opts = {})
10
- rake_args = opts.key?(:parallel) ? 'parallel_spec_standalone' : 'spec_standalone'
10
+ rake_args = opts[:parallel] ? 'parallel_spec_standalone' : 'spec_standalone'
11
11
  rake_args += "[#{tests}]" unless tests.nil?
12
12
  rake_args
13
13
  end
@@ -22,7 +22,7 @@ module PDK
22
22
 
23
23
  command = PDK::CLI::Exec::Command.new(*argv).tap do |c|
24
24
  c.context = :module
25
- c.add_spinner(spinner_text)
25
+ c.add_spinner(spinner_text) if spinner_text
26
26
  c.environment = environment
27
27
  end
28
28
 
@@ -72,23 +72,23 @@ module PDK
72
72
 
73
73
  environment = { 'CI_SPEC_OPTIONS' => '--format j' }
74
74
  environment['PUPPET_GEM_VERSION'] = options[:puppet] if options[:puppet]
75
- spinner_msg = options.key?(:parallel) ? _('Running unit tests in parallel.') : _('Running unit tests.')
75
+ spinner_msg = options[:parallel] ? _('Running unit tests in parallel.') : _('Running unit tests.')
76
76
  result = rake(cmd(tests, options), spinner_msg, environment)
77
77
 
78
- json_result = if options.key?(:parallel)
78
+ json_result = if options[:parallel]
79
79
  PDK::Util.find_all_json_in(result[:stdout])
80
80
  else
81
81
  PDK::Util.find_first_json_in(result[:stdout])
82
82
  end
83
83
 
84
- if parallel_with_no_tests?(options.key?(:parallel), json_result, result)
84
+ if parallel_with_no_tests?(options[:parallel], json_result, result)
85
85
  json_result = [{ 'messages' => ['No examples found.'] }]
86
86
  result[:exit_code] = 0
87
87
  end
88
88
 
89
89
  raise PDK::CLI::FatalError, _('Unit test output did not contain a valid JSON result: %{output}') % { output: result[:stdout] } if json_result.nil? || json_result.empty?
90
90
 
91
- json_result = merge_json_results(json_result) if options.key?(:parallel)
91
+ json_result = merge_json_results(json_result) if options[:parallel]
92
92
 
93
93
  parse_output(report, json_result, result[:duration])
94
94
 
@@ -183,15 +183,13 @@ module PDK
183
183
  end
184
184
 
185
185
  # @return array of { :id, :full_description }
186
- def self.list
186
+ def self.list(options = {})
187
187
  PDK::Util::Bundler.ensure_binstubs!('rake')
188
188
 
189
- command_argv = [File.join(PDK::Util.module_root, 'bin', 'rake'), 'spec_list_json']
190
- command_argv.unshift(File.join(PDK::Util::RubyVersion.bin_path, 'ruby.exe')) if Gem.win_platform?
189
+ environment = {}
190
+ environment['PUPPET_GEM_VERSION'] = options[:puppet] if options[:puppet]
191
191
 
192
- list_command = PDK::CLI::Exec::Command.new(*command_argv)
193
- list_command.context = :module
194
- output = list_command.execute!
192
+ output = rake('spec_list_json', _('Finding unit tests.'), environment)
195
193
 
196
194
  rspec_json = PDK::Util.find_first_json_in(output[:stdout])
197
195
  raise PDK::CLI::FatalError, _('Failed to find valid JSON in output from rspec: %{output}' % { output: output[:stdout] }) unless rspec_json
data/lib/pdk/util.rb CHANGED
@@ -106,6 +106,15 @@ module PDK
106
106
  end
107
107
  module_function :cachedir
108
108
 
109
+ def configdir
110
+ if Gem.win_platform?
111
+ File.join(ENV['LOCALAPPDATA'], 'PDK')
112
+ else
113
+ File.join(ENV.fetch('XDG_CONFIG_HOME', File.join(Dir.home, '.config')), 'pdk')
114
+ end
115
+ end
116
+ module_function :configdir
117
+
109
118
  # Returns path to the root of the module being worked on.
110
119
  #
111
120
  # @return [String, nil] Fully qualified base path to module, or nil if
@@ -81,7 +81,7 @@ module PDK
81
81
  end
82
82
 
83
83
  def gemfile_lock
84
- return nil if gemfile.nil?
84
+ return if gemfile.nil?
85
85
  @gemfile_lock ||= File.join(File.dirname(gemfile), 'Gemfile.lock')
86
86
  end
87
87
 
@@ -104,10 +104,6 @@ module PDK
104
104
 
105
105
  result = cmd.execute!
106
106
 
107
- unless result[:exit_code].zero?
108
- PDK.logger.debug(result.values_at(:stdout, :stderr).join("\n"))
109
- end
110
-
111
107
  result[:exit_code].zero?
112
108
  end
113
109
 
@@ -140,7 +136,7 @@ module PDK
140
136
  result = cmd.execute!
141
137
 
142
138
  unless result[:exit_code].zero?
143
- PDK.logger.fatal(result.values_at(:stdout, :stderr).join("\n"))
139
+ PDK.logger.fatal(result.values_at(:stdout, :stderr).join("\n")) unless PDK.logger.debug?
144
140
  raise PDK::CLI::FatalError, _('Unable to resolve default Gemfile dependencies.')
145
141
  end
146
142
 
@@ -181,7 +177,7 @@ module PDK
181
177
  result = cmd.execute!
182
178
 
183
179
  unless result[:exit_code].zero?
184
- PDK.logger.fatal(result.values_at(:stdout, :stderr).join("\n"))
180
+ PDK.logger.fatal(result.values_at(:stdout, :stderr).join("\n")) unless PDK.logger.debug?
185
181
  raise PDK::CLI::FatalError, _('Unable to resolve Gemfile dependencies.')
186
182
  end
187
183
 
@@ -200,7 +196,7 @@ module PDK
200
196
  result = cmd.execute!
201
197
 
202
198
  unless result[:exit_code].zero?
203
- PDK.logger.fatal(result.values_at(:stdout, :stderr).join("\n"))
199
+ PDK.logger.fatal(result.values_at(:stdout, :stderr).join("\n")) unless PDK.logger.debug?
204
200
  raise PDK::CLI::FatalError, _('Unable to install missing Gemfile dependencies.')
205
201
  end
206
202
 
@@ -215,7 +211,7 @@ module PDK
215
211
  result = cmd.execute!
216
212
 
217
213
  unless result[:exit_code].zero?
218
- PDK.logger.fatal(_("Failed to generate binstubs for '%{gems}':\n%{output}") % { gems: gems.join(' '), output: result.values_at(:stdout, :stderr).join("\n") })
214
+ PDK.logger.fatal(_("Failed to generate binstubs for '%{gems}':\n%{output}") % { gems: gems.join(' '), output: result.values_at(:stdout, :stderr).join("\n") }) unless PDK.logger.debug?
219
215
  raise PDK::CLI::FatalError, _('Unable to install requested binstubs.')
220
216
  end
221
217
 
@@ -13,6 +13,43 @@ module PDK
13
13
  File.open(path, 'wb') { |f| f.write(content) }
14
14
  end
15
15
  module_function :write_file
16
+
17
+ def read_file(file, nil_on_error: false)
18
+ File.read(file)
19
+ rescue => e
20
+ raise e unless nil_on_error
21
+ nil
22
+ end
23
+ module_function :read_file
24
+
25
+ #:nocov:
26
+ # These methods just wrap core Ruby functionality and
27
+ # can be ignored for code coverage
28
+ def directory?(*args)
29
+ File.directory?(*args)
30
+ end
31
+ module_function :directory?
32
+
33
+ def file?(*args)
34
+ File.file?(*args)
35
+ end
36
+ module_function :file?
37
+
38
+ def expand_path(*args)
39
+ File.expand_path(*args)
40
+ end
41
+ module_function :expand_path
42
+
43
+ def glob(*args)
44
+ Dir.glob(*args)
45
+ end
46
+ module_function :glob
47
+
48
+ def fnmatch(*args)
49
+ File.fnmatch(*args)
50
+ end
51
+ module_function :fnmatch
52
+ #:nocov:
16
53
  end
17
54
  end
18
55
  end
@@ -138,7 +138,7 @@ module PDK
138
138
 
139
139
  unless metadata_file
140
140
  PDK.logger.warn _('Unable to determine Puppet version for module: no metadata.json present in module.')
141
- return nil
141
+ return
142
142
  end
143
143
 
144
144
  metadata = PDK::Module::Metadata.from_file(metadata_file)
@@ -8,6 +8,8 @@ module PDK
8
8
 
9
9
  def_delegators :instance, :gem_path, :gem_paths_raw, :gem_home, :available_puppet_versions, :bin_path
10
10
 
11
+ # TODO: resolve this
12
+ # rubocop:disable Lint/DuplicateMethods
11
13
  attr_reader :instance
12
14
 
13
15
  def instance(version = nil)
@@ -21,6 +23,7 @@ module PDK
21
23
  end
22
24
  @instance[active_ruby_version]
23
25
  end
26
+ # rubocop:enable Lint/DuplicateMethods
24
27
 
25
28
  def active_ruby_version
26
29
  @active_ruby_version || default_ruby_version
@@ -45,12 +48,19 @@ module PDK
45
48
  end
46
49
 
47
50
  def default_ruby_version
48
- # For now, the packaged versions will be using default of 2.4.5
49
- # FIXME: make this dynamic
50
- return '2.4.5' if PDK::Util.package_install?
51
-
52
- # TODO: may not be a safe assumption that highest available version should be default
53
- latest_ruby_version
51
+ @default_ruby_version ||= if PDK::Util.package_install?
52
+ # Default to the ruby that supports the latest puppet gem. If you wish to default to a
53
+ # specific Puppet Gem version use the following example;
54
+ #
55
+ # PDK::Util::PuppetVersion.find_gem_for('5.5.10')[:ruby_version]
56
+ #
57
+ PDK::Util::PuppetVersion.latest_available[:ruby_version]
58
+ else
59
+ # TODO: may not be a safe assumption that highest available version should be default
60
+ # WARNING Do NOT use PDK::Util::PuppetVersion.*** methods as it can recurse into this
61
+ # method and cause Stack Level Too Deep errors.
62
+ latest_ruby_version
63
+ end
54
64
  end
55
65
 
56
66
  def latest_ruby_version
@@ -5,6 +5,15 @@ module PDK
5
5
  class TemplateURI
6
6
  SCP_PATTERN = %r{\A(?!\w+://)(?:(?<user>.+?)@)?(?<host>[^:/]+):(?<path>.+)\z}
7
7
 
8
+ PACKAGED_TEMPLATE_KEYWORD = 'pdk-default'.freeze
9
+ DEPRECATED_TEMPLATE_URL = 'https://github.com/puppetlabs/pdk-module-template'.freeze
10
+
11
+ LEGACY_PACKAGED_TEMPLATE_PATHS = {
12
+ 'windows' => 'file:///C:/Program Files/Puppet Labs/DevelopmentKit/share/cache/pdk-templates.git',
13
+ 'macos' => 'file:///opt/puppetlabs/pdk/share/cache/pdk-templates.git',
14
+ 'linux' => 'file:///opt/puppetlabs/pdk/share/cache/pdk-templates.git',
15
+ }.freeze
16
+
8
17
  # XXX Previously
9
18
  # - template_uri used to get the string form of the uri when generating the module and written to pdk answers and metadata
10
19
  # - template_path or deuri_path used for humans to see and commands to run
@@ -26,9 +35,16 @@ module PDK
26
35
  #
27
36
  def initialize(opts_or_uri)
28
37
  # If a uri string is passed, skip the valid uri finding code.
29
- @uri = if opts_or_uri.is_a?(String) || opts_or_uri.is_a?(self.class)
38
+ @uri = if opts_or_uri.is_a?(self.class)
39
+ opts_or_uri.uri
40
+ elsif opts_or_uri.is_a?(String)
30
41
  begin
31
- Addressable::URI.parse(opts_or_uri)
42
+ uri, ref = opts_or_uri.split('#', 2)
43
+ if self.class.packaged_template?(uri)
44
+ self.class.default_template_uri(ref).uri
45
+ else
46
+ Addressable::URI.parse(opts_or_uri)
47
+ end
32
48
  rescue Addressable::URI::InvalidURIError
33
49
  raise PDK::CLI::FatalError, _('PDK::Util::TemplateURI attempted initialization with a non-uri string: {string}') % { string: opts_or_uri }
34
50
  end
@@ -48,7 +64,11 @@ module PDK
48
64
  #
49
65
  # @returns String
50
66
  def metadata_format
51
- self.class.human_readable(@uri.to_s)
67
+ if self.class.packaged_template?(git_remote)
68
+ self.class.human_readable("pdk-default##{git_ref}")
69
+ else
70
+ self.class.human_readable(@uri.to_s)
71
+ end
52
72
  end
53
73
  alias to_s metadata_format
54
74
  alias to_str metadata_format
@@ -76,11 +96,7 @@ module PDK
76
96
 
77
97
  # @returns String
78
98
  def git_ref
79
- if @uri.fragment
80
- @uri.fragment
81
- else
82
- self.class.default_template_ref
83
- end
99
+ @uri.fragment || self.class.default_template_ref(self)
84
100
  end
85
101
 
86
102
  def git_ref=(ref)
@@ -88,11 +104,11 @@ module PDK
88
104
  end
89
105
 
90
106
  # @returns PDK::Util::TemplateURI
91
- def self.default_template_uri
107
+ def self.default_template_uri(ref = nil)
92
108
  if PDK::Util.package_install?
93
- PDK::Util::TemplateURI.new(Addressable::URI.new(scheme: 'file', host: '', path: File.join(PDK::Util.package_cachedir, 'pdk-templates.git')))
109
+ PDK::Util::TemplateURI.new(Addressable::URI.new(scheme: 'file', host: '', path: File.join(PDK::Util.package_cachedir, 'pdk-templates.git'), fragment: ref))
94
110
  else
95
- PDK::Util::TemplateURI.new('https://github.com/puppetlabs/pdk-templates')
111
+ PDK::Util::TemplateURI.new(Addressable::URI.new(scheme: 'https', host: 'github.com', path: '/puppetlabs/pdk-templates', fragment: ref))
96
112
  end
97
113
  end
98
114
 
@@ -106,11 +122,12 @@ module PDK
106
122
 
107
123
  # `C:...` urls are not URI-safe. They should be of the form `/C:...` to
108
124
  # be URI-safe. scp-like urls like `user@host:/path` are not URI-safe
109
- # either but are not handled here. Should they be?
125
+ # either and so are subsequently converted to ssh:// URIs.
110
126
  #
111
127
  # @returns String
112
128
  def self.uri_safe(string)
113
- (Gem.win_platform? && string =~ %r{^[a-zA-Z][\|:]}) ? "/#{string}" : string
129
+ url = (Gem.win_platform? && string =~ %r{^[a-zA-Z][\|:]}) ? "/#{string}" : string
130
+ parse_scp_url(url)
114
131
  end
115
132
 
116
133
  # If the passed value is a URI-safe windows path such as `/C:...` then it
@@ -122,6 +139,27 @@ module PDK
122
139
  (Gem.win_platform? && string =~ %r{^\/[a-zA-Z][\|:]}) ? string[1..-1] : string
123
140
  end
124
141
 
142
+ def self.parse_scp_url(url)
143
+ # Valid URIs to avoid catching:
144
+ # - absolute local paths
145
+ # - have :'s in paths when preceeded by a slash
146
+ # - have only digits following the : and preceeding a / or end-of-string that is 0-65535
147
+ # The last item is ambiguous in the case of scp/git paths vs. URI port
148
+ # numbers, but can be made unambiguous by making the form to
149
+ # ssh://git@github.com/1234/repo.git or
150
+ # ssh://git@github.com:1234/user/repo.git
151
+ scp_url = url.match(SCP_PATTERN)
152
+ return url unless Pathname.new(url).relative? && scp_url
153
+
154
+ uri = Addressable::URI.new(scheme: 'ssh', user: scp_url[:user], host: scp_url[:host], path: scp_url[:path])
155
+ PDK.logger.warn _('%{scp_uri} appears to be an SCP style URL; it will be converted to an RFC compliant URI: %{rfc_uri}') % {
156
+ scp_uri: url,
157
+ rfc_uri: uri.to_s,
158
+ }
159
+
160
+ uri.to_s
161
+ end
162
+
125
163
  # @return [Array<Hash{Symbol => Object}>] an array of hashes. Each hash
126
164
  # contains 3 keys: :type contains a String that describes the template
127
165
  # directory, :url contains a String with the URL to the template
@@ -136,42 +174,29 @@ module PDK
136
174
  # 1. Get the CLI, metadata (or answers if no metadata), and default URIs
137
175
  # 2. Construct the hash
138
176
  if explicit_url
139
- # Valid URIs to avoid catching:
140
- # - absolute local paths
141
- # - have :'s in paths when preceeded by a slash
142
- # - have only digits following the : and preceeding a / or end-of-string that is 0-65535
143
- # The last item is ambiguous in the case of scp/git paths vs. URI port
144
- # numbers, but can be made unambiguous by making the form to
145
- # ssh://git@github.com/1234/repo.git or
146
- # ssh://git@github.com:1234/user/repo.git
147
- scp_url = explicit_url.match(SCP_PATTERN)
148
- if Pathname.new(uri_safe(explicit_url)).relative? && scp_url
149
- explicit_uri = Addressable::URI.new(scheme: 'ssh', user: scp_url[:user], host: scp_url[:host], path: scp_url[:path])
150
- PDK.logger.warn _('%{scp_uri} appears to be an SCP style URL; it will be converted to an RFC compliant URI: %{rfc_uri}') % {
151
- scp_uri: explicit_url,
152
- rfc_uri: explicit_uri.to_s,
153
- }
154
- end
155
- explicit_uri ||= Addressable::URI.parse(uri_safe(explicit_url))
156
- explicit_uri.fragment = explicit_ref || default_template_ref
177
+ explicit_uri = Addressable::URI.parse(uri_safe(explicit_url))
178
+ explicit_uri.fragment = explicit_ref || default_template_ref(new(explicit_uri))
157
179
  else
158
180
  explicit_uri = nil
159
181
  end
160
182
  metadata_uri = if PDK::Util.module_root && File.file?(File.join(PDK::Util.module_root, 'metadata.json'))
161
- Addressable::URI.parse(uri_safe(PDK::Util.module_metadata['template-url']))
183
+ if PDK::Util.module_metadata['template-url']
184
+ new(uri_safe(PDK::Util.module_metadata['template-url'])).uri
185
+ else
186
+ nil
187
+ end
162
188
  else
163
189
  nil
164
190
  end
165
- answers_uri = if PDK.answers['template-url'] == 'https://github.com/puppetlabs/pdk-module-template'
166
- # use the new github template-url if it is still the old one.
191
+ answers_uri = if [PACKAGED_TEMPLATE_KEYWORD, DEPRECATED_TEMPLATE_URL].include?(PDK.answers['template-url'])
167
192
  Addressable::URI.parse(default_template_uri)
168
193
  elsif PDK.answers['template-url']
169
- Addressable::URI.parse(uri_safe(PDK.answers['template-url']))
194
+ new(uri_safe(PDK.answers['template-url'])).uri
170
195
  else
171
196
  nil
172
197
  end
173
- default_uri = Addressable::URI.parse(default_template_uri)
174
- default_uri.fragment = default_template_ref
198
+ default_uri = default_template_uri.uri
199
+ default_uri.fragment = default_template_ref(default_template_uri)
175
200
 
176
201
  ary = []
177
202
  ary << { type: _('--template-url'), uri: explicit_uri, allow_fallback: false } if explicit_url
@@ -182,12 +207,12 @@ module PDK
182
207
  end
183
208
 
184
209
  # @returns String
185
- def self.default_template_ref
186
- if PDK::Util.development_mode?
187
- 'master'
188
- else
189
- PDK::TEMPLATE_REF
190
- end
210
+ def self.default_template_ref(uri = nil)
211
+ return 'master' if PDK::Util.development_mode?
212
+ return PDK::TEMPLATE_REF if uri.nil?
213
+
214
+ uri = new(uri) unless uri.is_a?(self)
215
+ uri.default? ? PDK::TEMPLATE_REF : 'master'
191
216
  end
192
217
 
193
218
  # @returns Addressable::URI
@@ -226,6 +251,10 @@ module PDK
226
251
 
227
252
  false
228
253
  end
254
+
255
+ def self.packaged_template?(path)
256
+ path == PACKAGED_TEMPLATE_KEYWORD || LEGACY_PACKAGED_TEMPLATE_PATHS.value?(path)
257
+ end
229
258
  end
230
259
  end
231
260
  end
@@ -27,7 +27,7 @@ module PDK
27
27
  def self.git_ref
28
28
  source_git_dir = File.join(File.expand_path('../../..', File.dirname(__FILE__)), '.git')
29
29
 
30
- return nil unless File.directory?(source_git_dir)
30
+ return unless File.directory?(source_git_dir)
31
31
 
32
32
  PDK::Util::Git.describe(source_git_dir)
33
33
  end
@@ -1,6 +1,7 @@
1
1
  module PDK
2
2
  module Util
3
3
  module Windows
4
+ WIN32_FALSE = 0
4
5
  module File; end
5
6
 
6
7
  if Gem.win_platform?