Class: Autobuild::Environment

Inherits:
Object
  • Object
show all
Defined in:
lib/autobuild/environment.rb

Overview

Manager class for environment variables

Defined Under Namespace

Classes: ExportedEnvironment

Constant Summary collapse

PKGCONFIG_PATH_RX =
%r{.*/((?:lib|lib64|share)/.*)}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeEnvironment

Returns a new instance of Environment.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/autobuild/environment.rb', line 100

def initialize
    @inherited_environment = Hash.new
    @environment = Hash.new
    @source_before = Set.new
    @source_after = Set.new
    @inherit = true
    @inherited_variables = Set.new
    @path_variables = Set.new

    @system_env = Hash.new
    @original_env = ORIGINAL_ENV.dup

    @default_pkgconfig_search_suffixes = nil
    @arch_names = nil
    @target_arch = nil
    @arch_size = nil
end

Instance Attribute Details

#environmentObject (readonly)

List of the environment that should be set before calling a subcommand

It is a map from environment variable name to the corresponding value. If the value is an array, it is joined using the operating system's path separator (File::PATH_SEPARATOR)



90
91
92
# File 'lib/autobuild/environment.rb', line 90

def environment
  @environment
end

#inherited_environmentObject (readonly)

In generated environment update shell files, indicates whether an environment variable should be overriden by the shell script, or simply updated

If inherited_environment is true, the generated shell script will contain

export VARNAME=new_value:new_value:$VARNAME

otherwise

export VARNAME=new_value:new_value


84
85
86
# File 'lib/autobuild/environment.rb', line 84

def inherited_environment
  @inherited_environment
end

#inherited_variablesObject (readonly)

Returns the value of attribute inherited_variables.



92
93
94
# File 'lib/autobuild/environment.rb', line 92

def inherited_variables
  @inherited_variables
end

#original_envObject (readonly)

Returns the value of attribute original_env.



92
93
94
# File 'lib/autobuild/environment.rb', line 92

def original_env
  @original_env
end

#path_variablesObject (readonly)

The set of environment variables that are known to hold paths on the filesystem



98
99
100
# File 'lib/autobuild/environment.rb', line 98

def path_variables
  @path_variables
end

#system_envObject (readonly)

Returns the value of attribute system_env.



92
93
94
# File 'lib/autobuild/environment.rb', line 92

def system_env
  @system_env
end

#target_archObject

Returns the value of attribute target_arch.



92
93
94
# File 'lib/autobuild/environment.rb', line 92

def target_arch
  @target_arch
end

Class Method Details

.environment_from_export(export, base_env = ENV) ⇒ Object

Build an environment hash from an environment export and some initial state

This is basically the programmatic version of what #export_env_sh instructs the shell to do



528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/autobuild/environment.rb', line 528

def self.environment_from_export(export, base_env = ENV)
    result = Hash.new
    export.set.each do |name, value|
        result[name] = value.join(File::PATH_SEPARATOR)
    end
    base_env.each do |name, value|
        result[name] ||= value
    end
    export.unset.each do |name|
        result.delete(name)
    end
    export.update.each do |name, (with_inheritance, without_inheritance)|
        if result[name]
            variable_expansion = "$#{name}"
            with_inheritance = with_inheritance.map do |value|
                if value == variable_expansion
                    base_env[name]
                else value
                end
            end
            result[name] = with_inheritance.join(File::PATH_SEPARATOR)
        else
            result[name] = without_inheritance.join(File::PATH_SEPARATOR)
        end
    end
    result
end

.find_executable_in_path(file, entries) ⇒ Object



698
699
700
701
702
703
704
705
706
707
708
# File 'lib/autobuild/environment.rb', line 698

def self.find_executable_in_path(file, entries)
    entries.each do |dir|
        full = File.join(dir, file)
        begin
            stat = File.stat(full)
            return full if stat.file? && stat.executable?
        rescue ::Exception # rubocop:disable Lint/SuppressedException
        end
    end
    nil
end

.find_in_path(file, entries) ⇒ Object



714
715
716
717
718
719
720
# File 'lib/autobuild/environment.rb', line 714

def self.find_in_path(file, entries)
    entries.each do |dir|
        full = File.join(dir, file)
        return full if File.file?(full)
    end
    nil
end

.pathvar(path, varname) ⇒ Object

DEPRECATED: use add_path instead



557
558
559
560
561
562
563
# File 'lib/autobuild/environment.rb', line 557

def self.pathvar(path, varname)
    if File.directory?(path)
        return if block_given? && !yield(path)

        add_path(varname, path)
    end
end

Instance Method Details

#[](name) ⇒ Object



150
151
152
# File 'lib/autobuild/environment.rb', line 150

def [](name)
    resolved_env[name]
end

#add(name, *values) ⇒ Object

Adds new value(s) at the end of an environment variable



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/autobuild/environment.rb', line 292

def add(name, *values)
    values = values.map { |v| expand(v) }

    set = environment[name] if environment.key?(name)
    init_from_env(name) unless inherited_environment.key?(name)

    if !set
        set = Array.new
    elsif !set.respond_to?(:to_ary)
        set = [set]
    end

    values.concat(set)
    @environment[name] = values
end

#add_path(name, *paths) ⇒ Object

Add a path at the end of an environment variable

Unlike “normal” variables, entries of path variables that cannot be found on disk are filtered out at usage points (either #resolve_env or at the time of envirnonment export)

See Also:



389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/autobuild/environment.rb', line 389

def add_path(name, *paths)
    declare_path_variable(name)
    paths = paths.map { |p| expand(p) }

    oldpath = (environment[name] ||= Array.new)
    paths.reverse_each do |path|
        path = path.to_str
        next if oldpath.include?(path)

        add(name, path)
        oldpath << path
        $LOAD_PATH.unshift path if name == 'RUBYLIB'
    end
end

#add_prefix(newprefix, includes = nil) ⇒ Object

Updates the environment when a new prefix has been added



650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
# File 'lib/autobuild/environment.rb', line 650

def add_prefix(newprefix, includes = nil)
    if (!includes || includes.include?('PATH')) &&
       File.directory?("#{newprefix}/bin")
        add_path('PATH', "#{newprefix}/bin")
    end

    if !includes || includes.include?('PKG_CONFIG_PATH')
        each_env_search_path(newprefix,
                             default_pkgconfig_search_suffixes) do |path|
            add_path('PKG_CONFIG_PATH', path)
        end
    end

    if !includes || includes.include?(LIBRARY_PATH)
        ld_library_search = ['lib', 'lib/ARCH', 'libARCHSIZE']
        each_env_search_path(newprefix, ld_library_search) do |path|
            glob_path = File.join(path, "lib*.#{LIBRARY_SUFFIX}")
            has_sofile = Dir.enum_for(:glob, glob_path)
                .find { true }
            add_path(LIBRARY_PATH, path) if has_sofile
        end
    end

    # Validate the new rubylib path
    if !includes || includes.include?('RUBYLIB')
        new_rubylib = "#{newprefix}/lib"

        standalone_ruby_package =
            File.directory?(new_rubylib) &&
            !File.directory?(File.join(new_rubylib, "ruby")) &&
            !Dir["#{new_rubylib}/**/*.rb"].empty?
        add_path('RUBYLIB', new_rubylib) if standalone_ruby_package

        %w[rubylibdir archdir sitelibdir sitearchdir vendorlibdir vendorarchdir].
            map { |key| RbConfig::CONFIG[key] }.
            map { |path| path.gsub(%r{.*lib(?:32|64)?/}, '\\1') }.
            each do |subdir|
                if File.directory?("#{newprefix}/lib/#{subdir}")
                    add_path("RUBYLIB", "#{newprefix}/lib/#{subdir}")
                end
            end
    end
end

#arch_namesObject



617
618
619
620
621
622
623
624
625
626
627
628
629
630
# File 'lib/autobuild/environment.rb', line 617

def arch_names
    return @arch_names if @arch_names

    result = Set.new
    if File.file?('/usr/bin/dpkg-architecture')
        cmdline = ['/usr/bin/dpkg-architecture']
        cmdline << "-T" << target_arch if target_arch
        out = `#{cmdline.join(" ")}`.split
        arch = out.grep(/DEB_TARGET_MULTIARCH/).first ||
               out.grep(/DEB_BUILD_MULTIARCH/).first
        result << arch.chomp.split('=').last if arch
    end
    @arch_names = result
end

#arch_sizeObject



592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
# File 'lib/autobuild/environment.rb', line 592

def arch_size
    return @arch_size if @arch_size

    if File.file?('/usr/bin/dpkg-architecture')
        cmdline = ['/usr/bin/dpkg-architecture']
        cmdline << "-T" << target_arch if target_arch
        out = `#{cmdline.join(" ")}`.split
        arch = out.grep(/DEB_TARGET_ARCH_BITS/).first ||
               out.grep(/DEB_BUILD_ARCH_BITS/).first
        @arch_size = Integer(arch.chomp.split('=').last) if arch
    end

    @arch_size ||=
        if RbConfig::CONFIG['host_cpu'] =~ /64/
            64
        else 32
        end
    @arch_size
end

#clear(name = nil) ⇒ Object

Unsets any value on the environment variable name, including inherited value.

In a bourne shell, this would be equivalent to doing

unset name


176
177
178
179
180
181
182
183
184
185
186
# File 'lib/autobuild/environment.rb', line 176

def clear(name = nil)
    if name
        environment[name] = nil
        inherited_environment[name] = nil
    else
        keys = environment.keys # get keys first to avoid delete-while-iterating
        keys.each do |env_key|
            clear(env_key)
        end
    end
end

#declare_path_variable(name) ⇒ Object

Declares that the given environment variable holds a path

Non-existent paths in these variables are filtered out. It is called automatically if one of the 'path' methods are called (#set_path, #push_path, …)

Parameters:

  • name (String)


125
126
127
# File 'lib/autobuild/environment.rb', line 125

def declare_path_variable(name)
    path_variables << name
end

#default_pkgconfig_search_suffixesObject

Returns the system-wide search path that is embedded in pkg-config



639
640
641
642
643
644
645
646
647
# File 'lib/autobuild/environment.rb', line 639

def default_pkgconfig_search_suffixes
    @default_pkgconfig_search_suffixes ||=
        `LANG=C #{Autobuild.tool("pkg-config")} --variable pc_path pkg-config`
            .strip
            .split(":")
            .grep(PKGCONFIG_PATH_RX)
            .map { |l| l.gsub(PKGCONFIG_PATH_RX, '\1') }
            .to_set
end

#each_env_search_path(prefix, patterns) ⇒ Object



565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
# File 'lib/autobuild/environment.rb', line 565

def each_env_search_path(prefix, patterns)
    arch_names = self.arch_names
    arch_size  = self.arch_size

    seen = Set.new
    patterns.each do |base_path|
        paths = []
        if base_path =~ /ARCHSIZE/
            base_path = base_path.gsub('ARCHSIZE', arch_size.to_s)
        end
        if base_path =~ /ARCH/
            arch_names.each do |arch|
                paths << base_path.gsub('ARCH', arch)
            end
        else
            paths << base_path
        end
        paths.each do |p|
            p = File.join(prefix, *p.split('/'))
            if !seen.include?(p) && File.directory?(p)
                yield(p)
                seen << p
            end
        end
    end
end

#expand(value) ⇒ Object

Method called to filter the environment variables before they are set, for instance to expand variables



735
736
737
# File 'lib/autobuild/environment.rb', line 735

def expand(value)
    value
end

#export_env_sh(io, shell: 'sh') ⇒ Object

Generates a shell script that sets the environment variable listed in Autobuild.environment, following the inheritance setting listed in Autobuild.inherited_environment.

It also sources the files added by source_file



501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'lib/autobuild/environment.rb', line 501

def export_env_sh(io, shell: 'sh')
    export = exported_environment
    source_before(shell: shell).each do |path|
        io.puts format(SHELL_SOURCE_SCRIPT, path)
    end
    export.unset.each do |name|
        io.puts format(SHELL_UNSET_COMMAND, name)
    end
    export.set.each do |name, value|
        io.puts format(SHELL_SET_COMMAND, name, value.join(File::PATH_SEPARATOR))
        io.puts format(SHELL_EXPORT_COMMAND, name)
    end
    export.update.each do |name, (with_inheritance, without_inheritance)|
        io.puts format(SHELL_CONDITIONAL_SET_COMMAND, name,
                       with_inheritance.join(File::PATH_SEPARATOR),
                       without_inheritance.join(File::PATH_SEPARATOR))
        io.puts format(SHELL_EXPORT_COMMAND, name)
    end
    source_after(shell: shell).each do |path|
        io.puts format(SHELL_SOURCE_SCRIPT, path)
    end
end

#exported_environmentObject

Computes the set of environment modification operations that should be applied to load this environment

This is for instance used to generate the env.sh



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/autobuild/environment.rb', line 471

def exported_environment
    export = ExportedEnvironment.new(Hash.new, Array.new, Hash.new)
    environment.each_key do |name|
        value_with_inheritance    = value(name, inheritance_mode: :keep)
        value_without_inheritance = value(name, inheritance_mode: :ignore)
        if path_variable?(name)
            [value_with_inheritance, value_without_inheritance].each do |paths|
                paths.delete_if { |p| p !~ /^\$/ && !File.exist?(p) }
            end
        end

        if !value_with_inheritance
            export.unset << name
        elsif value_with_inheritance == value_without_inheritance # no inheritance
            export.set[name] = value_with_inheritance
        else
            export.update[name] = [
                value_with_inheritance,
                value_without_inheritance
            ]
        end
    end
    export
end

#filter_original_env(_name, parent_env) ⇒ Object



268
269
270
# File 'lib/autobuild/environment.rb', line 268

def filter_original_env(_name, parent_env)
    parent_env.dup
end

#find_executable_in_path(file, path_var = 'PATH') ⇒ Object



694
695
696
# File 'lib/autobuild/environment.rb', line 694

def find_executable_in_path(file, path_var = 'PATH')
    self.class.find_executable_in_path(file, value(path_var) || Array.new)
end

#find_in_path(file, path_var = 'PATH') ⇒ Object



710
711
712
# File 'lib/autobuild/environment.rb', line 710

def find_in_path(file, path_var = 'PATH')
    self.class.find_in_path(file, value(path_var) || Array.new)
end

#include?(name) ⇒ Boolean

Whether this object manages the given environment variable

Returns:

  • (Boolean)


359
360
361
# File 'lib/autobuild/environment.rb', line 359

def include?(name)
    environment.key?(name)
end

#inherit(*names) ⇒ Boolean

Declare that the given environment variable must not be reset by the env.sh script, but that new values should simply be prepended to it.

Returns:

  • (Boolean)

    true if environment inheritance is globally enabled and false otherwise. This is controlled by Autobuild.env_inherit=

See Also:



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/autobuild/environment.rb', line 244

def inherit(*names)
    flag =
        if !names.last.respond_to?(:to_str)
            names.pop
        else true
        end

    if flag
        @inherited_variables |= names
        names.each do |env_name|
            init_from_env(env_name)
        end
    else
        names.each do |n|
            if @inherited_variables.include?(n)
                @inherited_variables.delete(n)
                init_from_env(n)
            end
        end
    end

    @inherit
end

#inherit=(value) ⇒ Object

If true (the default), the environment variables that are marked as inherited will be inherited from the global environment (during the build as well as in the generated env.sh files)

Otherwise, only the environment that is explicitely set in autobuild will be passed on to subcommands, and saved in the environment scripts.

See Also:



228
229
230
231
232
233
234
235
# File 'lib/autobuild/environment.rb', line 228

def inherit=(value)
    @inherit = value
    # get keys first to avoid modify-while-iterating
    keys = inherited_environment.keys
    keys.each do |env_name|
        init_from_env(env_name)
    end
end

#inherit?(name = nil) ⇒ Boolean

Returns true if the given environment variable must not be reset by the env.sh script, but that new values should simply be prepended to it.

Parameters:

  • name (String, nil) (defaults to: nil)

    the environment variable that we want to check for inheritance. If nil, the global setting is returned.

Returns:

  • (Boolean)

See Also:



210
211
212
213
214
215
216
217
# File 'lib/autobuild/environment.rb', line 210

def inherit?(name = nil)
    if @inherit
        if name
            @inherited_variables.include?(name)
        else true
        end
    end
end

#init_from_env(name) ⇒ Object



272
273
274
275
276
277
278
279
# File 'lib/autobuild/environment.rb', line 272

def init_from_env(name)
    inherited_environment[name] =
        if inherit?(name) && (parent_env = original_env[name])
            filter_original_env(name, parent_env.split(File::PATH_SEPARATOR))
        else
            Array.new
        end
end

#initialize_copy(old) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/autobuild/environment.rb', line 134

def initialize_copy(old)
    super
    @inherited_environment = @inherited_environment.
        map_value { |_k, v| v&.dup }
    @environment = @environment.
        map_value { |_k, v| v&.dup }
    @source_before = Marshal.load(Marshal.dump(@source_before)) # deep copy
    @source_after = Marshal.load(Marshal.dump(@source_after)) # deep copy
    @inherited_variables = @inherited_variables.dup

    @system_env = @system_env.
        map_value { |_k, v| v&.dup }
    @original_env = @original_env.
        map_value { |_k, v| v&.dup }
end

#isolateObject



722
723
724
725
# File 'lib/autobuild/environment.rb', line 722

def isolate
    self.inherit = false
    push_path 'PATH', '/usr/local/bin', '/usr/bin', '/bin'
end

#path_variable?(name) ⇒ Boolean

Whether the given environment variable contains path(s)

Returns:

  • (Boolean)


130
131
132
# File 'lib/autobuild/environment.rb', line 130

def path_variable?(name)
    path_variables.include?(name)
end

#prepareObject



727
728
729
730
731
# File 'lib/autobuild/environment.rb', line 727

def prepare
    # Set up some important autobuild parameters
    inherit 'PATH', 'PKG_CONFIG_PATH', 'RUBYLIB', \
            LIBRARY_PATH, 'CMAKE_PREFIX_PATH', 'PYTHONPATH'
end

#push(name, *values) ⇒ Object



281
282
283
284
285
286
287
288
289
# File 'lib/autobuild/environment.rb', line 281

def push(name, *values)
    if (current = environment[name])
        current = current.dup
        set(name, *values)
        add(name, *current)
    else
        add(name, *values)
    end
end

#push_path(name, *values) ⇒ Object

Add a path at the beginning of an environment variable

Unlike “normal” variables, entries of path variables that cannot be found on disk are filtered out at usage points (either #resolve_env or at the time of envirnonment export)

See Also:



418
419
420
421
422
423
424
425
426
427
# File 'lib/autobuild/environment.rb', line 418

def push_path(name, *values)
    declare_path_variable(name)
    if (current = environment.delete(name))
        current = current.dup
        add_path(name, *values)
        add_path(name, *current)
    else
        add_path(name, *values)
    end
end

#remove_path(name, *paths) ⇒ Object



404
405
406
407
408
409
# File 'lib/autobuild/environment.rb', line 404

def remove_path(name, *paths)
    declare_path_variable(name)
    paths.each do |p|
        environment[name].delete(p)
    end
end

#reset(name = nil) ⇒ Object

Resets the value of name to its original value. If it is inherited from the



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/autobuild/environment.rb', line 156

def reset(name = nil)
    if name
        environment.delete(name)
        inherited_environment.delete(name)
        init_from_env(name)
    else
        keys = environment.keys # get keys first to avoid delete-while-iterating
        keys.each do |env_key|
            reset(env_key)
        end
    end
end

#resolved_envObject



363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/autobuild/environment.rb', line 363

def resolved_env
    resolved_env = Hash.new
    environment.each_key do |name|
        if (value = value(name))
            value = value.find_all { |p| File.exist?(p) } if path_variable?(name)
            resolved_env[name] = value.join(File::PATH_SEPARATOR)
        else
            resolved_env[name] = nil
        end
    end
    resolved_env
end

#set(name, *values) ⇒ Object

Set a new environment variable



189
190
191
192
# File 'lib/autobuild/environment.rb', line 189

def set(name, *values)
    environment.delete(name)
    add(name, *values)
end

#set_path(name, *paths) ⇒ Object



376
377
378
379
380
# File 'lib/autobuild/environment.rb', line 376

def set_path(name, *paths)
    declare_path_variable(name)
    clear(name)
    add_path(name, *paths)
end

#source_afterArray<String> #source_after(path) ⇒ Object

Overloads:

  • #source_afterArray<String>

    List of scripts that should be sourced at the end of env.sh

    Returns:

    • (Array<String>)

      a list of paths that should be sourced at the end of the shell script generated by #export_env_sh

  • #source_after(path) ⇒ Object

    Parameters:

    • path (String)

      a path that should be added to source_after



456
457
458
459
460
461
462
463
# File 'lib/autobuild/environment.rb', line 456

def source_after(file = nil, shell: 'sh')
    if file
        @source_after << { file: file, shell: shell }
        source_after(shell: shell) # for backwards compatibility
    else @source_after.select { |pair| pair[:shell] == shell }
                      .map { |item| item[:file] }
    end
end

#source_beforeArray<String> #source_before(path) ⇒ Object

Overloads:

  • #source_beforeArray<String>

    List of scripts that should be sourced at the top of env.sh

    Returns:

    • (Array<String>)

      a list of paths that should be sourced at the beginning of the shell script generated by #export_env_sh

  • #source_before(path) ⇒ Object

    Parameters:

    • path (String)

      a path that should be added to source_before



438
439
440
441
442
443
444
445
# File 'lib/autobuild/environment.rb', line 438

def source_before(file = nil, shell: 'sh')
    if file
        @source_before << { file: file, shell: shell }
        source_before(shell: shell) # for backwards compatibility
    else @source_before.select { |pair| pair[:shell] == shell }
                       .map { |item| item[:file] }
    end
end

#unset(name) ⇒ Object

Unset the given environment variable

It is different from #delete in that it will lead to the environment variable being actively unset, while 'delete' will leave it to its original value



199
200
201
# File 'lib/autobuild/environment.rb', line 199

def unset(name)
    environment[name] = nil
end

#update_environment(newprefix, includes = nil) ⇒ Object



632
633
634
# File 'lib/autobuild/environment.rb', line 632

def update_environment(newprefix, includes = nil)
    add_prefix(newprefix, includes)
end

#value(name, options = Hash.new) ⇒ nil, Array<String>

Returns an environment variable value

Parameters:

  • name (String)

    the environment variable name

  • options (Hash) (defaults to: Hash.new)

    a customizable set of options

Options Hash (options):

  • inheritance_mode (Symbol) — default: :expand

    controls how environment variable inheritance should be done. If :expand, the current envvar value is inserted in the generated value. If :keep, the name of the envvar is inserted (as e.g. $NAME). If :ignore, inheritance is disabled in the generated value. Not that this applies only for the environment variables for which inheritance has been enabled with #inherit, other variables always behave as if :ignore was selected.

Returns:

  • (nil, Array<String>)

    either nil if this environment variable is not set, or an array of values. How the values should be joined to form the actual value is OS-specific, and not handled by this method



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/autobuild/environment.rb', line 321

def value(name, options = Hash.new)
    # For backward compatibility only
    unless options.respond_to?(:to_hash)
        options =
            if options
                Hash[:inheritance_mode => :expand]
            else
                Hash[:inheritance_mode => :keep]
            end
    end
    options = Kernel.validate_options options,
                                      inheritance_mode: :expand
    inheritance_mode = options[:inheritance_mode]

    if !include?(name)
        nil
    elsif !environment[name]
        nil
    else
        inherited =
            if inheritance_mode == :expand
                inherited_environment[name] || []
            elsif inheritance_mode == :keep && inherit?(name)
                ["$#{name}"]
            else []
            end

        value = []
        [environment[name], inherited, system_env[name]].each do |paths|
            (paths || []).each do |p|
                value << p unless value.include?(p)
            end
        end
        value
    end
end