comet-build 0.1.2
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 +59 -0
- data/.rspec +1 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +58 -0
- data/LICENSE +9 -0
- data/README.md +311 -0
- data/Rakefile +1 -0
- data/bin/comet +30 -0
- data/comet-build.gemspec +26 -0
- data/lib/comet/dsl/bin_output.rb +19 -0
- data/lib/comet/dsl/c/source.rb +57 -0
- data/lib/comet/dsl/cpp/source.rb +57 -0
- data/lib/comet/dsl/elf_output.rb +19 -0
- data/lib/comet/dsl/firmware.rb +65 -0
- data/lib/comet/dsl/hardware.rb +56 -0
- data/lib/comet/dsl/helpers.rb +9 -0
- data/lib/comet/dsl/hex_output.rb +19 -0
- data/lib/comet/dsl/include_walker.rb +42 -0
- data/lib/comet/dsl/library.rb +21 -0
- data/lib/comet/dsl/linker.rb +45 -0
- data/lib/comet/dsl/map_output.rb +19 -0
- data/lib/comet/dsl/native/source.rb +44 -0
- data/lib/comet/dsl/options.rb +44 -0
- data/lib/comet/dsl/software.rb +42 -0
- data/lib/comet/dsl/source.rb +24 -0
- data/lib/comet/dsl/target.rb +64 -0
- data/lib/comet/makefile.rb +129 -0
- data/lib/comet/parser.rb +114 -0
- data/lib/comet/rules/alias.rb +29 -0
- data/lib/comet/rules/build.rb +118 -0
- data/lib/comet/rules/clean.rb +29 -0
- data/lib/comet/rules/codegen.rb +51 -0
- data/lib/comet/rules/compile.rb +72 -0
- data/lib/comet/rules/error.rb +33 -0
- data/lib/comet/rules/link.rb +74 -0
- data/lib/comet/rules/merge.rb +37 -0
- data/lib/comet/rules/objcopy.rb +41 -0
- data/lib/comet/rules/tmpdir.rb +39 -0
- data/lib/comet/version.rb +3 -0
- data/lib/comet.rb +76 -0
- data/spec/comet/complex_c_program/Makefile +57 -0
- data/spec/comet/complex_c_program/comet.rb +43 -0
- data/spec/comet/complex_c_program/include/public_api.h +2 -0
- data/spec/comet/complex_c_program/src/common.h +3 -0
- data/spec/comet/complex_c_program/src/library.h +2 -0
- data/spec/comet/complex_c_program/src/main.c +6 -0
- data/spec/comet/complex_c_program/src/module_a/impl.c +1 -0
- data/spec/comet/complex_c_program/src/module_a/impl.h +1 -0
- data/spec/comet/complex_c_program/src/module_b/impl.c +1 -0
- data/spec/comet/complex_c_program/src/module_b/impl.h +1 -0
- data/spec/comet/empty_build_file/Makefile +10 -0
- data/spec/comet/empty_build_file/comet.rb +0 -0
- data/spec/comet/empty_build_file/folder/.keep +0 -0
- data/spec/comet/generator_spec.rb +67 -0
- data/spec/comet/nested_circular_reference/Makefile +5 -0
- data/spec/comet/nested_circular_reference/comet.rb +8 -0
- data/spec/comet/no_build_file/Makefile +5 -0
- data/spec/comet/self_referential/Makefile +5 -0
- data/spec/comet/self_referential/comet.rb +5 -0
- data/spec/comet/simple_c_program/Makefile +30 -0
- data/spec/comet/simple_c_program/comet.rb +15 -0
- data/spec/comet/simple_c_program/main.c +5 -0
- data/spec/comet/simple_c_program/stuff.h +0 -0
- metadata +215 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class Firmware
|
4
|
+
include Comet::DSL::Helpers
|
5
|
+
alias inject instance_exec
|
6
|
+
|
7
|
+
def target(device, &block)
|
8
|
+
raise "target #{device} redefined" if @targets.key? device
|
9
|
+
@targets[device] = Target.new(device, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(name, imports:, &block)
|
13
|
+
@name = name
|
14
|
+
@imports = imports
|
15
|
+
@targets = {}
|
16
|
+
|
17
|
+
instance_exec(&block) if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"firmware #{@name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :name
|
25
|
+
attr_accessor :imports
|
26
|
+
|
27
|
+
def targets
|
28
|
+
@targets.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
def target_for(device)
|
32
|
+
@targets[device]
|
33
|
+
end
|
34
|
+
|
35
|
+
def hardware_for(device)
|
36
|
+
@imports.flat_map do |imported|
|
37
|
+
imported.hardware_for device
|
38
|
+
end.uniq
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate!
|
42
|
+
raise "firmware `#{@name}' has no targets" if @targets.empty?
|
43
|
+
|
44
|
+
@targets.values.each(&:validate!)
|
45
|
+
imports.each(&:validate!)
|
46
|
+
|
47
|
+
targets.each do |device|
|
48
|
+
validate_for! device, hardware_for(device)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def validate_for!(device, hardware)
|
55
|
+
if hardware.empty?
|
56
|
+
raise "firmware `#{@name}' does not support target `#{device}'"
|
57
|
+
end
|
58
|
+
|
59
|
+
linkers = hardware.select(&:linker?)
|
60
|
+
raise "no linker section for firmware `#{@name}'" if linkers.empty?
|
61
|
+
raise "multiple linkers for firmware `#{@name}'" if linkers.length > 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class Hardware
|
4
|
+
include Comet::DSL::Helpers
|
5
|
+
alias inject instance_exec
|
6
|
+
|
7
|
+
def source(language:, **kwargs, &block)
|
8
|
+
@sources.push Source.create(language: language, **kwargs, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def linker(*args, **kwargs, &block)
|
12
|
+
raise 'linker section redefined' unless @linker_.nil?
|
13
|
+
@linker_ = Linker.new(*args, **kwargs, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def import(name, path: nil)
|
17
|
+
@libraries.add Library.new(name, path: path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(name, targets:, &block)
|
21
|
+
@name = name
|
22
|
+
@targets = targets
|
23
|
+
@sources = []
|
24
|
+
@linker_ = nil
|
25
|
+
@libraries = Set[]
|
26
|
+
|
27
|
+
instance_exec(&block) if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"hardware #{@name} for #{@targets}"
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :targets
|
35
|
+
attr_reader :sources
|
36
|
+
attr_reader :linker_
|
37
|
+
|
38
|
+
def libraries
|
39
|
+
@libraries.to_a
|
40
|
+
end
|
41
|
+
|
42
|
+
def hardware_for(device)
|
43
|
+
device == targets ? self : []
|
44
|
+
end
|
45
|
+
|
46
|
+
def linker?
|
47
|
+
!@linker_.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate!
|
51
|
+
sources.each(&:validate!)
|
52
|
+
@linker.validate! unless @linker.nil?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class HexOutput
|
4
|
+
def initialize(path)
|
5
|
+
@path = path
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
'hex output'
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :path
|
13
|
+
|
14
|
+
def validate!
|
15
|
+
raise "#{self} path is invalid" if @path.strip.empty?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
module IncludeWalker
|
4
|
+
class << self
|
5
|
+
def includes_for(file, include_paths)
|
6
|
+
@@cache[[file, include_paths]]
|
7
|
+
end
|
8
|
+
|
9
|
+
def search(file, include_paths)
|
10
|
+
includes = []
|
11
|
+
|
12
|
+
File.read(file).scan(PATTERN) do
|
13
|
+
header = Regexp.last_match[1] || Regexp.last_match[2]
|
14
|
+
includes << find_included_file(header, include_paths)
|
15
|
+
end
|
16
|
+
|
17
|
+
includes.compact.uniq.dup.each do |included|
|
18
|
+
includes.concat search(included, include_paths)
|
19
|
+
end
|
20
|
+
|
21
|
+
includes.compact.uniq
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
@@cache = Hash.new do |h, k|
|
27
|
+
h[k] = IncludeWalker.search(*k)
|
28
|
+
end
|
29
|
+
|
30
|
+
PATTERN = /^\s*#\s*include\s*(?:(?:<([^>]+)>)|(?:"([^"]+)"))\s*$/m
|
31
|
+
|
32
|
+
def find_included_file(file, include_paths)
|
33
|
+
match = include_paths.detect do |include_path|
|
34
|
+
File.exist? File.join(include_path, file)
|
35
|
+
end
|
36
|
+
|
37
|
+
File.join(match, file) unless match.nil?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class Library
|
4
|
+
def initialize(name, path: nil)
|
5
|
+
@name = name
|
6
|
+
@path = path
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
"library #{@name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
attr_reader :path
|
15
|
+
|
16
|
+
def validate!
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class Linker
|
4
|
+
include Comet::DSL::Helpers
|
5
|
+
alias inject instance_exec
|
6
|
+
|
7
|
+
def script(script)
|
8
|
+
@script = script
|
9
|
+
end
|
10
|
+
|
11
|
+
def option(*args, **kwargs)
|
12
|
+
@options.add('', *args, **kwargs)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(triple, isa:, cpu:, opt:, &block)
|
16
|
+
@triple = triple
|
17
|
+
@isa = isa
|
18
|
+
@cpu = cpu
|
19
|
+
@options = Options.new
|
20
|
+
@opt = opt
|
21
|
+
@script = nil
|
22
|
+
|
23
|
+
instance_exec(&block) if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
"linker #{@triple}"
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :triple
|
31
|
+
attr_reader :isa
|
32
|
+
attr_reader :cpu
|
33
|
+
attr_reader :options
|
34
|
+
attr_reader :opt
|
35
|
+
|
36
|
+
def script_
|
37
|
+
@script
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate!
|
41
|
+
raise "#{self} triple is invalid" if @triple.strip.empty?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class MapOutput
|
4
|
+
def initialize(path)
|
5
|
+
@path = path
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
'map output'
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :path
|
13
|
+
|
14
|
+
def validate!
|
15
|
+
raise "#{self} path is invalid" if @path.strip.empty?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
module Native
|
4
|
+
class Source
|
5
|
+
include Comet::DSL::Helpers
|
6
|
+
alias inject instance_exec
|
7
|
+
|
8
|
+
def import(pattern)
|
9
|
+
@imports.push pattern
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(**_, &block)
|
13
|
+
@imports = []
|
14
|
+
|
15
|
+
instance_exec(&block) if block_given?
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
'native source'
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :imports
|
23
|
+
|
24
|
+
def language
|
25
|
+
:native
|
26
|
+
end
|
27
|
+
|
28
|
+
def native?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def files
|
33
|
+
@imports.flat_map do |pattern|
|
34
|
+
Dir.glob pattern
|
35
|
+
end.uniq
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate!
|
39
|
+
raise "#{self} references no source files" if @imports.empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class Options
|
4
|
+
def initialize
|
5
|
+
@options = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
'options'
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(prefix, *args, **kwargs)
|
13
|
+
args.each do |key|
|
14
|
+
set_option "#{prefix}#{key}"
|
15
|
+
end
|
16
|
+
|
17
|
+
kwargs.each do |key, value|
|
18
|
+
set_option "#{prefix}#{key}", value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def format(prefix: '-', separator: '=')
|
23
|
+
@options.map do |key, value|
|
24
|
+
next "#{prefix}#{key}" if value.nil?
|
25
|
+
"#{prefix}#{key}#{separator}#{value}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate!
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def set_option(key, value = nil)
|
36
|
+
if value == :remove
|
37
|
+
@options.delete key
|
38
|
+
else
|
39
|
+
@options[key] = value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class Software
|
4
|
+
include Comet::DSL::Helpers
|
5
|
+
alias inject instance_exec
|
6
|
+
|
7
|
+
def source(language:, **kwargs, &block)
|
8
|
+
source = Source.create(language: language, **kwargs, &block)
|
9
|
+
raise 'native source only allowed in hardware block' if source.native?
|
10
|
+
@sources.push source
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(name, depends:, &block)
|
14
|
+
@name = name
|
15
|
+
@depends = depends
|
16
|
+
@sources = []
|
17
|
+
|
18
|
+
instance_exec(&block) if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"software #{@name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :depends
|
26
|
+
attr_reader :sources
|
27
|
+
|
28
|
+
def hardware_for(device)
|
29
|
+
@depends.flat_map do |dependent|
|
30
|
+
dependent.hardware_for device
|
31
|
+
end.uniq
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate!
|
35
|
+
raise "#{@name} has no source directives" if sources.empty?
|
36
|
+
|
37
|
+
depends.each(&:validate!)
|
38
|
+
sources.each(&:validate!)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class Source
|
4
|
+
class << self
|
5
|
+
def create(language:, **kwargs, &block)
|
6
|
+
raise "unknown language #{language}" unless valid? language
|
7
|
+
LANGUAGES[language].new(**kwargs, &block) # pass extra args
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
LANGUAGES = {
|
13
|
+
c: C::Source,
|
14
|
+
cpp: CPP::Source,
|
15
|
+
native: Native::Source
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def valid?(language)
|
19
|
+
LANGUAGES.keys.include? language
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Comet
|
2
|
+
module DSL
|
3
|
+
class Target
|
4
|
+
include Comet::DSL::Helpers
|
5
|
+
alias inject instance_exec
|
6
|
+
|
7
|
+
def elf(path)
|
8
|
+
@elf_output = ElfOutput.new(path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def bin(path)
|
12
|
+
@bin_output = BinOutput.new(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def hex(path)
|
16
|
+
@hex_output = HexOutput.new(path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def map(path)
|
20
|
+
@map_output = MapOutput.new(path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(device, &block)
|
24
|
+
@elf_output = nil
|
25
|
+
@bin_output = nil
|
26
|
+
@hex_output = nil
|
27
|
+
@map_output = nil
|
28
|
+
@device = device
|
29
|
+
|
30
|
+
instance_exec(&block) if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"target for #{@device}"
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :elf_output
|
38
|
+
attr_reader :bin_output
|
39
|
+
attr_reader :hex_output
|
40
|
+
attr_reader :map_output
|
41
|
+
|
42
|
+
def elf?
|
43
|
+
!@elf_output.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
def bin?
|
47
|
+
!@bin_output.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def hex?
|
51
|
+
!@hex_output.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def map?
|
55
|
+
!@map_output.nil?
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate!
|
59
|
+
has_outputs = [elf?, bin?, hex?, map?].any?
|
60
|
+
raise "#{self} describes no outputs" unless has_outputs
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Comet
|
5
|
+
class Makefile
|
6
|
+
def initialize
|
7
|
+
@rules = [tmpdir_rule]
|
8
|
+
end
|
9
|
+
|
10
|
+
def contents
|
11
|
+
(commands + @rules.map(&:contents)).flatten.join "\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(*args)
|
15
|
+
process_spawn make_command(*args) do |stdin|
|
16
|
+
stdin.write contents
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def alias(name, rules)
|
21
|
+
insert Rules::Alias.new(name, rules)
|
22
|
+
end
|
23
|
+
|
24
|
+
def build(firmware, device)
|
25
|
+
append Rules::Build.new(firmware, device)
|
26
|
+
end
|
27
|
+
|
28
|
+
def error(message)
|
29
|
+
insert Rules::Error.new(message)
|
30
|
+
end
|
31
|
+
|
32
|
+
def clean
|
33
|
+
append Rules::Clean.new(Comet::TMPDIR)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def tmpdir_rule
|
39
|
+
@tmpdir_rule ||= Rules::TmpDir.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def insert(rule)
|
43
|
+
@rules.unshift rule unless seen? rule
|
44
|
+
rule.rules.each(&method(:append))
|
45
|
+
rule
|
46
|
+
end
|
47
|
+
|
48
|
+
def append(rule)
|
49
|
+
@rules.push rule unless seen? rule
|
50
|
+
rule.rules.each(&method(:append))
|
51
|
+
rule
|
52
|
+
end
|
53
|
+
|
54
|
+
def seen?(rule)
|
55
|
+
@rules.map(&:target).include? rule.target
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_spawn(arguments)
|
59
|
+
IO.pipe do |read, write|
|
60
|
+
process = spawn(*arguments, in: read)
|
61
|
+
yield write # pass in process stdin
|
62
|
+
write.close # make sure to close
|
63
|
+
Process.wait process
|
64
|
+
end
|
65
|
+
|
66
|
+
$?.exitstatus
|
67
|
+
end
|
68
|
+
|
69
|
+
def commands
|
70
|
+
commands = @rules.map(&:commands).reduce(&:merge)
|
71
|
+
commands.flat_map do |name, default|
|
72
|
+
["#{name} ?= #{default}"]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def make_command(arguments, verbose: false)
|
77
|
+
[
|
78
|
+
ENV.fetch('MAKE', 'make'),
|
79
|
+
verbose ? nil : '-s',
|
80
|
+
'-f', '-', *arguments
|
81
|
+
].compact
|
82
|
+
end
|
83
|
+
|
84
|
+
# Make utilities (TODO: move this elsewhere?)
|
85
|
+
|
86
|
+
class << self
|
87
|
+
def select_command(preferred, alternate, args)
|
88
|
+
return command(alternate, args) if preferred == alternate
|
89
|
+
|
90
|
+
check_command = "(command -v #{preferred} > /dev/null)"
|
91
|
+
preferred_cmd = command preferred, args
|
92
|
+
alternate_cmd = command alternate, args
|
93
|
+
|
94
|
+
"#{check_command} && #{preferred_cmd} || #{alternate_cmd}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def command(command, args)
|
98
|
+
[command, *args].join ' '
|
99
|
+
end
|
100
|
+
|
101
|
+
def fingerprint(dependencies, extension:)
|
102
|
+
File.join Comet::TMPDIR, "#{hash dependencies}.#{extension}"
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def hash(object)
|
108
|
+
digest = Digest::SHA256.new
|
109
|
+
|
110
|
+
if [Array, Set, Hash].any? { |t| object.is_a? t }
|
111
|
+
hash_container digest, container: object
|
112
|
+
else
|
113
|
+
digest.update Digest::SHA256.digest(object.class.name)
|
114
|
+
digest.update Digest::SHA256.digest(object.to_s)
|
115
|
+
end
|
116
|
+
|
117
|
+
digest.hexdigest[0..19]
|
118
|
+
end
|
119
|
+
|
120
|
+
def hash_container(digest, container:)
|
121
|
+
if container.is_a? Array
|
122
|
+
container.each { |element| digest.update hash(element) }
|
123
|
+
else # this will work for both hashes and sets
|
124
|
+
digest.update hash(container.to_a.sort)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|