comet-build 0.1.2

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