comet-build 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|