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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +59 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +14 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +58 -0
  7. data/LICENSE +9 -0
  8. data/README.md +311 -0
  9. data/Rakefile +1 -0
  10. data/bin/comet +30 -0
  11. data/comet-build.gemspec +26 -0
  12. data/lib/comet/dsl/bin_output.rb +19 -0
  13. data/lib/comet/dsl/c/source.rb +57 -0
  14. data/lib/comet/dsl/cpp/source.rb +57 -0
  15. data/lib/comet/dsl/elf_output.rb +19 -0
  16. data/lib/comet/dsl/firmware.rb +65 -0
  17. data/lib/comet/dsl/hardware.rb +56 -0
  18. data/lib/comet/dsl/helpers.rb +9 -0
  19. data/lib/comet/dsl/hex_output.rb +19 -0
  20. data/lib/comet/dsl/include_walker.rb +42 -0
  21. data/lib/comet/dsl/library.rb +21 -0
  22. data/lib/comet/dsl/linker.rb +45 -0
  23. data/lib/comet/dsl/map_output.rb +19 -0
  24. data/lib/comet/dsl/native/source.rb +44 -0
  25. data/lib/comet/dsl/options.rb +44 -0
  26. data/lib/comet/dsl/software.rb +42 -0
  27. data/lib/comet/dsl/source.rb +24 -0
  28. data/lib/comet/dsl/target.rb +64 -0
  29. data/lib/comet/makefile.rb +129 -0
  30. data/lib/comet/parser.rb +114 -0
  31. data/lib/comet/rules/alias.rb +29 -0
  32. data/lib/comet/rules/build.rb +118 -0
  33. data/lib/comet/rules/clean.rb +29 -0
  34. data/lib/comet/rules/codegen.rb +51 -0
  35. data/lib/comet/rules/compile.rb +72 -0
  36. data/lib/comet/rules/error.rb +33 -0
  37. data/lib/comet/rules/link.rb +74 -0
  38. data/lib/comet/rules/merge.rb +37 -0
  39. data/lib/comet/rules/objcopy.rb +41 -0
  40. data/lib/comet/rules/tmpdir.rb +39 -0
  41. data/lib/comet/version.rb +3 -0
  42. data/lib/comet.rb +76 -0
  43. data/spec/comet/complex_c_program/Makefile +57 -0
  44. data/spec/comet/complex_c_program/comet.rb +43 -0
  45. data/spec/comet/complex_c_program/include/public_api.h +2 -0
  46. data/spec/comet/complex_c_program/src/common.h +3 -0
  47. data/spec/comet/complex_c_program/src/library.h +2 -0
  48. data/spec/comet/complex_c_program/src/main.c +6 -0
  49. data/spec/comet/complex_c_program/src/module_a/impl.c +1 -0
  50. data/spec/comet/complex_c_program/src/module_a/impl.h +1 -0
  51. data/spec/comet/complex_c_program/src/module_b/impl.c +1 -0
  52. data/spec/comet/complex_c_program/src/module_b/impl.h +1 -0
  53. data/spec/comet/empty_build_file/Makefile +10 -0
  54. data/spec/comet/empty_build_file/comet.rb +0 -0
  55. data/spec/comet/empty_build_file/folder/.keep +0 -0
  56. data/spec/comet/generator_spec.rb +67 -0
  57. data/spec/comet/nested_circular_reference/Makefile +5 -0
  58. data/spec/comet/nested_circular_reference/comet.rb +8 -0
  59. data/spec/comet/no_build_file/Makefile +5 -0
  60. data/spec/comet/self_referential/Makefile +5 -0
  61. data/spec/comet/self_referential/comet.rb +5 -0
  62. data/spec/comet/simple_c_program/Makefile +30 -0
  63. data/spec/comet/simple_c_program/comet.rb +15 -0
  64. data/spec/comet/simple_c_program/main.c +5 -0
  65. data/spec/comet/simple_c_program/stuff.h +0 -0
  66. metadata +215 -0
@@ -0,0 +1,114 @@
1
+ module Comet
2
+ class Parser
3
+ include Comet::DSL::Helpers
4
+ alias inject instance_exec
5
+
6
+ def software(name, depends:, &block)
7
+ raise "software `#{name}' redefined" if @software.key? name
8
+ @software[name] = DSL::Software.new(name, depends: depends, &block)
9
+ end
10
+
11
+ def hardware(name, targets:, &block)
12
+ if hardware_defined? name, targets
13
+ raise "hardware `#{name}' redefined for `#{targets}'"
14
+ end
15
+ @hardware[name].push DSL::Hardware.new(name, targets: targets, &block)
16
+ end
17
+
18
+ def firmware(name, imports:, &block)
19
+ raise "firmware `#{name}' redefined" if firmware_defined? name
20
+ @firmware.push DSL::Firmware.new(name, imports: imports, &block)
21
+ end
22
+
23
+ def array_hash
24
+ Hash.new { |h, k| h[k] = [] }
25
+ end
26
+
27
+ def plain_hash
28
+ {}
29
+ end
30
+
31
+ def initialize(filename)
32
+ @software = plain_hash
33
+ @hardware = array_hash
34
+ @firmware = []
35
+ @filename = filename
36
+ end
37
+
38
+ attr_reader :filename
39
+
40
+ def targets
41
+ parse_file! if @targets.nil?
42
+ @targets ||= compute_targets
43
+ end
44
+
45
+ def compute_targets
46
+ @firmware.flat_map do |firmware|
47
+ firmware.validate!
48
+
49
+ firmware.targets.map do |device|
50
+ [firmware, device]
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def resolve_dependencies!(firmware)
58
+ seen = {}
59
+
60
+ firmware.imports = firmware.imports.flat_map do |name|
61
+ resolve! name, seen
62
+ end
63
+ end
64
+
65
+ def resolve!(name, seen)
66
+ if seen[name] == :temporary
67
+ raise "circular dependency involving `#{name}' detected"
68
+ end
69
+
70
+ object = object_by_name name
71
+ return object if seen[name] == :permanent
72
+ resolve_software! name, object, seen if @software.key? name
73
+ seen[name] = :permanent
74
+
75
+ object
76
+ end
77
+
78
+ def resolve_software!(name, software, seen)
79
+ seen[name] = :temporary
80
+ software.depends = software.depends.flat_map do |dependency|
81
+ resolve! dependency, seen
82
+ end
83
+ end
84
+
85
+ def object_by_name(name)
86
+ if @software[name].nil? && @hardware[name].empty?
87
+ raise "software or hardware `#{name}' undefined"
88
+ end
89
+
90
+ @software[name] || @hardware[name]
91
+ end
92
+
93
+ def resolve_all_dependencies!
94
+ @firmware.each do |firmware|
95
+ resolve_dependencies! firmware
96
+ end
97
+ end
98
+
99
+ def parse_file!
100
+ instance_eval File.read(@filename)
101
+ resolve_all_dependencies!
102
+ end
103
+
104
+ def hardware_defined?(name, targets)
105
+ @hardware[name].any? do |hardware|
106
+ hardware.targets == targets
107
+ end
108
+ end
109
+
110
+ def firmware_defined?(name)
111
+ @firmware.map(&:name).include? name
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,29 @@
1
+ module Comet
2
+ module Rules
3
+ class Alias
4
+ def initialize(name, dependencies)
5
+ @name = name
6
+ @dependencies = dependencies
7
+ end
8
+
9
+ def target
10
+ @name
11
+ end
12
+
13
+ def contents
14
+ [
15
+ ".PHONY: #{target}",
16
+ "#{target}: #{@dependencies.map(&:target).uniq.join ' '}"
17
+ ]
18
+ end
19
+
20
+ def rules
21
+ []
22
+ end
23
+
24
+ def commands
25
+ {}
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,118 @@
1
+ module Comet
2
+ module Rules
3
+ class Build
4
+ def initialize(firmware, device)
5
+ @firmware = firmware
6
+ @device = device
7
+ end
8
+
9
+ def target
10
+ @target ||= Comet::Makefile.fingerprint Hash[
11
+ dependencies: Set[rules.map(&:target).uniq]
12
+ ], extension: :phony
13
+ end
14
+
15
+ def contents
16
+ contents = [
17
+ ".PHONY: #{target}",
18
+ "#{target}: #{rules.map(&:target).uniq.join ' '}"
19
+ ]
20
+
21
+ if fw_target.elf?
22
+ contents.push "\t$(COMET_CP) #{link.target} #{fw_target.elf_output.path}"
23
+ end
24
+
25
+ if fw_target.bin?
26
+ contents.push "\t$(COMET_CP) #{bin.target} #{fw_target.bin_output.path}"
27
+ end
28
+
29
+ if fw_target.hex?
30
+ contents.push "\t$(COMET_CP) #{hex.target} #{fw_target.hex_output.path}"
31
+ end
32
+
33
+ if fw_target.map?
34
+ contents.push "\t$(COMET_CP) #{link.map_file} #{fw_target.map_output.path}"
35
+ end
36
+
37
+ contents
38
+ end
39
+
40
+ def rules
41
+ @rules ||= [link, bin, hex].compact
42
+ end
43
+
44
+ def commands
45
+ { COMET_CP: 'cp' }
46
+ end
47
+
48
+ private
49
+
50
+ def fw_target
51
+ @firmware.target_for @device
52
+ end
53
+
54
+ def bin
55
+ ObjCopy.new linker, link, 'binary', 'bin' if fw_target.bin?
56
+ end
57
+
58
+ def hex
59
+ ObjCopy.new linker, link, 'ihex', 'hex' if fw_target.hex?
60
+ end
61
+
62
+ def link
63
+ @link ||= Link.new linker, codegen, libraries, native_sources
64
+ end
65
+
66
+ def linker
67
+ @firmware.hardware_for(@device).detect(&:linker?).linker_
68
+ end
69
+
70
+ def native_sources
71
+ @firmware.hardware_for(@device).flat_map do |hardware|
72
+ hardware.sources.select(&:native?).flat_map(&:files)
73
+ end
74
+ end
75
+
76
+ def libraries
77
+ @firmware.hardware_for(@device).flat_map(&:libraries).uniq(&:name)
78
+ end
79
+
80
+ def codegen
81
+ @codegen ||= Codegen.new linker, merge
82
+ end
83
+
84
+ def merge
85
+ @merge ||= Merge.new compile
86
+ end
87
+
88
+ def compile
89
+ @compile ||= @firmware.imports.flat_map do |software|
90
+ compile_software software
91
+ end
92
+ end
93
+
94
+ def compile_software(software)
95
+ software.depends.flat_map do |depends|
96
+ if depends.is_a? Comet::DSL::Software
97
+ compile_software depends
98
+ else
99
+ compile_hardware depends
100
+ end
101
+ end + software.sources.reject(&:native?).flat_map do |source|
102
+ source.files.map do |file|
103
+ Compile.new source, file, linker
104
+ end
105
+ end
106
+ end
107
+
108
+ def compile_hardware(hardware)
109
+ return [] unless hardware.targets == @device
110
+ hardware.sources.reject(&:native?).flat_map do |source|
111
+ source.files.map do |file|
112
+ Compile.new source, file, linker
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,29 @@
1
+ module Comet
2
+ module Rules
3
+ class Clean
4
+ def initialize(folder)
5
+ @folder = folder
6
+ end
7
+
8
+ def target
9
+ 'clean'
10
+ end
11
+
12
+ def contents
13
+ [
14
+ ".PHONY: #{target}",
15
+ "#{target}:",
16
+ "\t$(COMET_RM) -rf #{@folder}"
17
+ ]
18
+ end
19
+
20
+ def rules
21
+ []
22
+ end
23
+
24
+ def commands
25
+ { COMET_RM: 'rm' }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,51 @@
1
+ module Comet
2
+ module Rules
3
+ class Codegen
4
+ def initialize(linker, bitcode)
5
+ @linker = linker
6
+ @bitcode = bitcode
7
+ end
8
+
9
+ def target
10
+ @target ||= Comet::Makefile.fingerprint Hash[
11
+ command: clang_link_command,
12
+ target: @bitcode.target,
13
+ triple: @linker.triple,
14
+ isa: @linker.isa,
15
+ cpu: @linker.cpu,
16
+ opt: @linker.opt
17
+ ], extension: :s
18
+ end
19
+
20
+ def contents
21
+ [
22
+ "#{target}: #{@bitcode.target} | #{Comet::TMPDIR}",
23
+ "\t#{llvm_codegen} -o $@ $<"
24
+ ]
25
+ end
26
+
27
+ def rules
28
+ @rules ||= [@bitcode]
29
+ end
30
+
31
+ def commands
32
+ { COMET_OPT: 'clang' }
33
+ end
34
+
35
+ private
36
+
37
+ def llvm_codegen
38
+ [
39
+ *clang_link_command,
40
+ "--target=#{@linker.triple}",
41
+ "-march=#{@linker.isa}",
42
+ "-mcpu=#{@linker.cpu}"
43
+ ].join ' '
44
+ end
45
+
46
+ def clang_link_command
47
+ ['$(COMET_OPT)', '-S', "-O#{@linker.opt}"]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,72 @@
1
+ module Comet
2
+ module Rules
3
+ class Compile
4
+ def initialize(source, file, linker)
5
+ @source = source
6
+ @file = file
7
+ @linker = linker
8
+ end
9
+
10
+ def target
11
+ @target ||= Comet::Makefile.fingerprint Hash[
12
+ file: @file,
13
+ triple: @linker.triple,
14
+ isa: @linker.isa,
15
+ cpu: @linker.cpu,
16
+ flags: Set[formatted_flags],
17
+ headers: formatted_headers,
18
+ dependencies: Set[dependencies]
19
+ ], extension: :ll
20
+ end
21
+
22
+ def contents
23
+ [
24
+ "#{target}: #{@file} #{dependencies.join ' '} | #{Comet::TMPDIR}",
25
+ "\t#{clang_compile} #{formatted_flags.join ' '} -o $@ -c $<"
26
+ ]
27
+ end
28
+
29
+ def rules
30
+ []
31
+ end
32
+
33
+ def commands
34
+ { COMET_CC: 'clang' }
35
+ end
36
+
37
+ private
38
+
39
+ def formatted_flags
40
+ @formatted_flags ||= @source.options.format
41
+ end
42
+
43
+ def dependencies
44
+ @dependencies ||= find_dependencies
45
+ end
46
+
47
+ def find_dependencies
48
+ Comet::DSL::IncludeWalker.includes_for(@file, @source.headers)
49
+ end
50
+
51
+ def clang_compile
52
+ [
53
+ '$(COMET_CC)',
54
+ '-x',
55
+ 'c',
56
+ "--target=#{@linker.triple}",
57
+ "-march=#{@linker.isa}",
58
+ "-mcpu=#{@linker.cpu}",
59
+ '-S',
60
+ '-flto',
61
+ *formatted_headers
62
+ ].join ' '
63
+ end
64
+
65
+ def formatted_headers
66
+ @source.headers.map do |header|
67
+ "-I#{header}"
68
+ end.join
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,33 @@
1
+ module Comet
2
+ module Rules
3
+ class Error
4
+ def initialize(message)
5
+ @message = message
6
+ end
7
+
8
+ def target
9
+ nil
10
+ end
11
+
12
+ def contents
13
+ ["$(error #{pretty_message})"]
14
+ end
15
+
16
+ def rules
17
+ []
18
+ end
19
+
20
+ def commands
21
+ {}
22
+ end
23
+
24
+ private
25
+
26
+ def pretty_message
27
+ words = @message.split
28
+ words.first.capitalize!
29
+ words.join ' '
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,74 @@
1
+ module Comet
2
+ module Rules
3
+ class Link
4
+ def initialize(linker, dependency, libraries, native_sources)
5
+ @linker = linker
6
+ @dependency = dependency
7
+ @libraries = libraries
8
+ @native_sources = native_sources
9
+ end
10
+
11
+ def target
12
+ @target ||= Comet::Makefile.fingerprint Hash[
13
+ dependency: @dependency.target,
14
+ triple: @linker.triple,
15
+ isa: @linker.isa,
16
+ cpu: @linker.cpu,
17
+ script: @linker.script_,
18
+ libraries: formatted_libraries,
19
+ native_sources: @native_sources,
20
+ flags: formatted_flags
21
+ ], extension: :elf
22
+ end
23
+
24
+ def map_file
25
+ target.gsub(/.elf$/, '.map')
26
+ end
27
+
28
+ def contents
29
+ [
30
+ "#{target}: #{@dependency.target} #{@native_sources.join ' '} | #{Comet::TMPDIR}",
31
+ "\t#{clang_link} #{formatted_script} #{formatted_flags.join ' '} -Wl,-Map=#{map_file} -o $@ $^ #{formatted_libraries}"
32
+ ]
33
+ end
34
+
35
+ def rules
36
+ [@dependency]
37
+ end
38
+
39
+ def commands
40
+ { COMET_LD: 'clang' }
41
+ end
42
+
43
+ private
44
+
45
+ def formatted_script
46
+ return '' if @linker.script_.nil?
47
+ "-T#{@linker.script_}"
48
+ end
49
+
50
+ def formatted_flags
51
+ @formatted_flags ||= @linker.options.format
52
+ end
53
+
54
+ def clang_link
55
+ [
56
+ '$(COMET_LD)',
57
+ "--target=#{@linker.triple}",
58
+ "-march=#{@linker.isa}",
59
+ "-mcpu=#{@linker.cpu}"
60
+ ].join ' '
61
+ end
62
+
63
+ def formatted_libraries
64
+ @libraries.flat_map do |library|
65
+ if library.path.nil?
66
+ "-l#{library.name}"
67
+ else
68
+ ["-L#{File.join library.path, library.name}"]
69
+ end
70
+ end.join ' '
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,37 @@
1
+ module Comet
2
+ module Rules
3
+ class Merge
4
+ def initialize(dependencies)
5
+ @dependencies = dependencies
6
+ end
7
+
8
+ def target
9
+ @target ||= Comet::Makefile.fingerprint Hash[
10
+ command: llvm_link,
11
+ dependencies: Set[@dependencies.map(&:target).uniq]
12
+ ], extension: :ll
13
+ end
14
+
15
+ def contents
16
+ [
17
+ "#{target}: #{@dependencies.map(&:target).uniq.join ' '} | #{Comet::TMPDIR}",
18
+ "\t#{llvm_link.join ' '} -o $@ $^"
19
+ ]
20
+ end
21
+
22
+ def rules
23
+ @dependencies
24
+ end
25
+
26
+ def commands
27
+ { COMET_LINK: 'llvm-link' }
28
+ end
29
+
30
+ private
31
+
32
+ def llvm_link
33
+ ['$(COMET_LINK)', '-S']
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ module Comet
2
+ module Rules
3
+ class ObjCopy
4
+ def initialize(linker, elf, format, extension)
5
+ @linker = linker
6
+ @elf = elf
7
+ @format = format
8
+ @extension = extension
9
+ end
10
+
11
+ def target
12
+ @target ||= Comet::Makefile.fingerprint Hash[
13
+ triple: @linker.triple,
14
+ format: @format,
15
+ elf: @elf.target
16
+ ], extension: @extension
17
+ end
18
+
19
+ def contents
20
+ [
21
+ "#{target}: #{@elf.target} | #{Comet::TMPDIR}",
22
+ "\t#{objcopy}"
23
+ ]
24
+ end
25
+
26
+ def rules
27
+ [@elf]
28
+ end
29
+
30
+ def commands
31
+ { COMET_OBJCOPY: 'objcopy' }
32
+ end
33
+
34
+ private
35
+
36
+ def objcopy
37
+ Comet::Makefile.select_command "#{@linker.triple}-$(COMET_OBJCOPY)", '$(COMET_OBJCOPY)', ['-O', @format, '$<', '$@']
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,39 @@
1
+ module Comet
2
+ module Rules
3
+ class TmpDir
4
+ def target
5
+ Comet::TMPDIR
6
+ end
7
+
8
+ def contents
9
+ [
10
+ ".PHONY: #{target}",
11
+ "#{target}:",
12
+ "\tif #{exists?} || #{valid?}; then #{create}; fi"
13
+ ]
14
+ end
15
+
16
+ def rules
17
+ []
18
+ end
19
+
20
+ def commands
21
+ { COMET_LN: 'ln' }
22
+ end
23
+
24
+ private
25
+
26
+ def exists?
27
+ '[ ! -d "$@" ]'
28
+ end
29
+
30
+ def valid?
31
+ "[ ! -d \"`readlink #{target}`\" ]"
32
+ end
33
+
34
+ def create
35
+ '$(COMET_LN) -sf `mktemp -d` $@'
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module Comet
2
+ VERSION = '0.1.2'.freeze
3
+ end