delphi-compiler 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9556a4463bec6d1ee6c5839ead76790799b128e2
4
+ data.tar.gz: e49d2ee30f20f5f81f589f543308ae446b2adbb6
5
+ SHA512:
6
+ metadata.gz: 61284f6d39e3399e67fdc78e8b30640f4ef2bc48dbf6dd05393d90776c55b8426dbedb59c1aaa9dfb776f8228b707ab0668218e41166560ff9414df346c1290c
7
+ data.tar.gz: af4562c2008043379b0fa144a01454cb1feeb74f4309597a564949a4522518164079583795a6fbd7ffff52f1871a656111f7c24dfc5947ec85950011726e4b7e
@@ -0,0 +1,36 @@
1
+ require './version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'delphi-compiler'
5
+ s.version = VERSION
6
+ s.date = '2013-12-04'
7
+ s.summary = 'Delphi Compiler'
8
+ s.description = 'A framework to compile Delphi applications from Rake'
9
+ s.authors = ['Scott Sedgwick']
10
+ s.email = 'scott.sedgwick@gmail.com'
11
+ s.files = [
12
+ 'delphi-compiler.gemspec',
13
+ 'rakefile.rb',
14
+ 'version.rb',
15
+ 'lib/delphi.rb',
16
+ 'lib/delphi/compiler.rb',
17
+ 'lib/delphi/dsl.rb',
18
+ 'lib/delphi/environment.rb',
19
+ 'lib/delphi/groupproj.rb',
20
+ 'lib/delphi/project.rb',
21
+ 'lib/delphi/resource.rb',
22
+ 'lib/delphi/versions.rb',
23
+ 'test/MyClass.pas',
24
+ 'test/test.dproj',
25
+ 'test/test.dpr',
26
+ 'test/test.res',
27
+ 'test/test_project.rb',
28
+ 'test/TestGroup.groupproj',
29
+ 'test/Test/TestMyClass.pas',
30
+ 'test/Test/testTests.dpr',
31
+ 'test/Test/testTests.dproj',
32
+ 'test/Test/testTests.res'
33
+ ]
34
+ s.homepage = 'https://github.com/ScottSedgwick/delphi-compiler'
35
+ s.license = 'MIT'
36
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'delphi/compiler'
4
+ require 'delphi/environment'
5
+ require 'delphi/groupproj'
6
+ require 'delphi/resource'
7
+ require 'delphi/project'
8
+ require 'delphi/versions'
9
+ require 'delphi/dsl'
@@ -0,0 +1,92 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'delphi/environment'
4
+
5
+ module Delphi
6
+ # Class to execute Delphi compiler
7
+ #
8
+ # You should not need to directly interact with this class.
9
+ # It is accessed via the compile methods of the Project,
10
+ # GroupProj and Resource classes
11
+ class Compiler
12
+ attr_accessor :fail_on_hints_and_warnings, :fail_on_errors
13
+
14
+ # You need to pass the Delphi target version to this class.
15
+ # If you use the Project or GroupProj classes, they will figure
16
+ # this out for you from the information in the dproj file.
17
+ def initialize(project_version, fail_on_hints_and_warnings = true)
18
+ Delphi::Environment.instance.configure(project_version)
19
+ @fail_on_hints_and_warnings = fail_on_hints_and_warnings
20
+ @fail_on_errors = true
21
+ end
22
+
23
+ # Compile a Project (dproj file)
24
+ def compile(dproj, target, config, platform)
25
+ puts "Compiling #{dproj}"
26
+ cmd = format(FMT_COMPILE, target, config, platform, dproj)
27
+ compile_output = `#{cmd}`
28
+ check_for_failure(compile_output)
29
+ end
30
+
31
+ # Compile a Project Group (groupproj file)
32
+ def compile_group(groupproj, target)
33
+ puts "Compiling #{groupproj}"
34
+ Dir.chdir(File.dirname(groupproj)) do
35
+ cmd = format(FMT_COMPILE_GROUP, target, File.basename(groupproj))
36
+ compile_output = `#{cmd}`
37
+ check_for_failure(compile_output)
38
+ end
39
+ end
40
+
41
+ # Compile a Resource (rc file)
42
+ def compile_rc(rcfile)
43
+ puts "Compiling #{rcfile}"
44
+ Dir.chdir(File.dirname(rcfile)) do
45
+ cmd = format(FMT_COMPILE_RC, File.basename(rcfile))
46
+ `#{cmd}`
47
+ end
48
+ end
49
+
50
+ def bds
51
+ File.join(Delphi::Environment.instance.bin_dir, 'bds.exe')
52
+ end
53
+
54
+ private
55
+
56
+ FMT_COMPILE = 'msbuild.exe /nologo /t:%s /p:Config=%s;Platform=%s "%s"'
57
+ FMT_COMPILE_GROUP = 'msbuild.exe /v:q /nologo /t:%s "%s"'
58
+ FMT_COMPILE_RC = 'brcc32.exe "%s"'
59
+
60
+ def check_for_failure(output)
61
+ if @fail_on_errors && found_errors?(output)
62
+ puts output
63
+ fail 'Compilation failed. Errors found.'
64
+ end
65
+ if fail_due_to_hints_or_warnings?(output)
66
+ puts output
67
+ fail 'Compilation failed. Hints and Warnings found.'
68
+ end
69
+ output
70
+ end
71
+
72
+ def fail_due_to_hints_or_warnings?(output)
73
+ @fail_on_hints_and_warnings && found_hints_or_warnings?(output)
74
+ end
75
+
76
+ def found_hints?(output)
77
+ output =~ / Hint:/
78
+ end
79
+
80
+ def found_hints_or_warnings?(output)
81
+ found_hints?(output) || found_warnings?(output)
82
+ end
83
+
84
+ def found_warnings?(output)
85
+ !(output =~ / 0 Warning\(s\)/)
86
+ end
87
+
88
+ def found_errors?(output)
89
+ !(output =~ / 0 Error\(s\)/)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rake/clean'
4
+ require 'delphi/project'
5
+
6
+ def _parse_args(args)
7
+ args = (args.take(args.length - 1) + args.last.to_a).flatten if args.last.instance_of?(Hash)
8
+ sym, dproj, platform, config, prereqs = args[0], args[1], nil, nil, []
9
+ case args.length
10
+ when 5
11
+ platform = args[2]
12
+ config = args[3]
13
+ prereqs = args[4]
14
+ when 4
15
+ platform = args[2]
16
+ config = args[3]
17
+ when 3
18
+ prereqs = args[2]
19
+ end
20
+ [sym, dproj, platform, config, prereqs]
21
+ end
22
+
23
+ def delphi(*args)
24
+ fail '<Symbol> and <Dproj file> are required arguments to the delphi compilation task' if args.length < 2
25
+ sym, dproj, platform, config, prereqs = _parse_args(args)
26
+
27
+ proj = Delphi::Project.new(dproj)
28
+ proj.platform = platform if platform
29
+ proj.config = config if config
30
+ task sym => prereqs do
31
+ Rake::Task[proj.output].invoke
32
+ end
33
+ file proj.output do
34
+ proj.compile
35
+ yield proj if block_given?
36
+ end
37
+ Rake::Task[sym].sources = proj
38
+
39
+ CLOBBER.include(proj.output)
40
+ end
41
+
42
+ def delphi_exe(sym)
43
+ Rake::Task[sym].sources.output
44
+ end
45
+
46
+ def dunit(sym, prereqs)
47
+ if prereqs.instance_of?(Hash)
48
+ compile_sym = prereqs.keys[0]
49
+ prereqs = prereqs[compile_sym]
50
+ else
51
+ compile_sym = prereqs
52
+ prereqs = []
53
+ end
54
+ prereqs = [prereqs, compile_sym].flatten
55
+ task sym => prereqs do
56
+ exe = delphi_exe(compile_sym)
57
+ Dir.chdir(File.dirname(exe)) do
58
+ system(File.basename(exe))
59
+ fail 'Unit tests failed' unless $CHILD_STATUS.to_i == 0
60
+ yield if block_given?
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,66 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'singleton'
4
+ require 'win32/registry'
5
+ require 'delphi/versions'
6
+
7
+ module Delphi
8
+ # Environment is a Singleton.
9
+ #
10
+ # It is responsible for initializing the console environment
11
+ # for the verison of Delphi that is requested.
12
+ class Environment
13
+ include Singleton
14
+
15
+ # bin_dir is the location of the Delphi executables
16
+ attr_reader :bin_dir
17
+
18
+ def initialize
19
+ super
20
+ @configured_version = 0
21
+ end
22
+
23
+ # Configure needs to be called on the Environment instance before the
24
+ # Compiler can be used. Normally it will be done by the Project, which
25
+ # will read the required version of Delphi from the dproj file.
26
+ def configure(project_version)
27
+ return @configured_version if @configured_version == project_version
28
+ @bin_dir = delphi_directory(project_version)
29
+ rsvars = File.join(@bin_dir, 'rsvars.bat')
30
+ load_env(rsvars)
31
+ @configured_version = project_version
32
+ end
33
+
34
+ private
35
+
36
+ def delphi_directory(delphi_version)
37
+ key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Embarcadero RAD Studio ' + VERSIONS[delphi_version][:key]
38
+ begin
39
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(key) { |reg| "#{reg['InstallLocation']}\\bin" }
40
+ rescue Win32::Registry::Error
41
+ puts "Delphi Compiler version #{delphi_version} not installed on this computer"
42
+ end
43
+ end
44
+
45
+ def load_env(rsvars)
46
+ File.open(rsvars).each { |line| load_line(line[5..-1]) if line =~ /^@SET / }
47
+ end
48
+
49
+ def load_line(line)
50
+ key, value = line.split(/\=/)
51
+ ENV[key] = process_env_var(value)
52
+ end
53
+
54
+ def map_path(path)
55
+ path.split(';').map { |p| yield(p).chomp }.join(';')
56
+ end
57
+
58
+ def process_env_var(value)
59
+ map_path(value) { |v| substitute(v.chomp) }
60
+ end
61
+
62
+ def substitute(value)
63
+ value =~ /^%(.*)%$/ ? ENV[value[1..-2]] : value
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rexml/document'
4
+ require 'delphi/compiler'
5
+
6
+ module Delphi
7
+ # Class to load and compile dproj files
8
+ #
9
+ # The RW properties of this class can be changed to modify
10
+ # the compilation behaviour of a project, but they will not
11
+ # be saved back to the dproj file - it is read only.
12
+ class GroupProj
13
+ attr_accessor :fail_on_hints_and_warnings
14
+
15
+ def initialize(filename)
16
+ @filename = filename
17
+ fail "#{filename} not found" unless File.exist?(filename)
18
+
19
+ load
20
+ fail "#{filename} is empty" if projects == []
21
+
22
+ # Load one project to initialize the environment
23
+ dproj0 = File.join(File.dirname(filename), projects[0])
24
+ @p = Delphi::Project.new(dproj0)
25
+ @fail_on_hints_and_warnings = true
26
+ end
27
+
28
+ # Compile all projects in this group.
29
+ def compile
30
+ puts 'Compiling groupproj'
31
+ compiler.compile_group(@filename, target) if compiler
32
+ end
33
+
34
+ # Reader for the compiler setting
35
+ def fail_on_errors
36
+ compiler.fail_on_errors
37
+ end
38
+
39
+ # Setter for the compiler setting
40
+ def fail_on_errors=(value)
41
+ compiler.fail_on_errors = value
42
+ end
43
+
44
+ # Reader for the compiler setting
45
+ def fail_on_hints_and_warnings
46
+ compiler.fail_on_hints_and_warnings
47
+ end
48
+
49
+ # Setter for the compiler setting
50
+ def fail_on_hints_and_warnings=(value)
51
+ compiler.fail_on_hints_and_warnings = value
52
+ end
53
+
54
+ # Returns an array of dproj filenames in this group.
55
+ def projects
56
+ path = '/Project/ItemGroup/Projects'
57
+ result = []
58
+ @doc.elements.each(path) { |element| result << element.attributes['Include'] }
59
+ result
60
+ end
61
+
62
+ # Read the current compilation target for this group
63
+ def target
64
+ @p.target
65
+ end
66
+
67
+ # Set the compilation target for this group
68
+ def target=(value)
69
+ @p.target = value
70
+ end
71
+
72
+ # Returns an array of valid compile targets (Clean, Compile, Build)
73
+ def targets
74
+ @p.targets
75
+ end
76
+
77
+ private
78
+
79
+ def compiler
80
+ @p.compiler
81
+ end
82
+
83
+ def load
84
+ File.open(@filename) { |f| @doc = REXML::Document.new f }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,128 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rexml/document'
4
+ require 'delphi/compiler'
5
+
6
+ module Delphi
7
+ # Class to load and compile dproj files
8
+ #
9
+ # The RW properties of this class can be changed to modify
10
+ # the compilation behaviour of a project, but they will not
11
+ # be saved back to the dproj file - it is read only.
12
+ class Project
13
+ # The configuration to build - e.g. Debug, Release.
14
+ attr_accessor :config
15
+ # The platform to build for - e.g. Win32, Win64.
16
+ attr_accessor :platform
17
+ # The msbuild target - e.g. Build, Compile, Clean.
18
+ attr_accessor :target
19
+
20
+ attr_reader :dproj
21
+
22
+ def initialize(dproj)
23
+ @dproj = dproj
24
+ fail "#{dproj} not found" unless File.exist?(dproj)
25
+
26
+ load
27
+ @compiler = Delphi::Compiler.new(project_version)
28
+ @config = default_config
29
+ @target = targets[0]
30
+ @platform = platforms[0]
31
+ @outputpath = get('//DCC_ExeOutput').text
32
+ end
33
+
34
+ def compile
35
+ @compiler.compile(@dproj, @target, @config, @platform)
36
+ end
37
+
38
+ # Set the config. Restricts it to valid values.
39
+ def config=(value)
40
+ check_value(configs, value, 'config')
41
+ @config = value
42
+ end
43
+
44
+ def fail_on_hints_and_warnings
45
+ @compiler.fail_on_hints_and_warnings
46
+ end
47
+
48
+ def fail_on_hints_and_warnings=(value)
49
+ @compiler.fail_on_hints_and_warnings = value
50
+ end
51
+
52
+ # Returns the set of valid configurations as read from the dproj file
53
+ def configs
54
+ result = []
55
+ @doc.elements.each('/Project/ItemGroup/BuildConfiguration') do |element|
56
+ config = element.attributes['Include']
57
+ result << config unless config == 'Base'
58
+ end
59
+ result
60
+ end
61
+
62
+ # The output file without the path.
63
+ def out_file
64
+ File.basename(@output)
65
+ end
66
+
67
+ def output
68
+ File.expand_path(File.join(subst_path(@outputpath), File.basename(@dproj, '.dproj') + '.exe'))
69
+ end
70
+
71
+ # Set the platform. Restricts it to valid values.
72
+ def platform=(value)
73
+ check_value(platforms, value, 'platform')
74
+ @platform = value
75
+ end
76
+
77
+ # Returns the set of valid platforms as read from the dproj file
78
+ def platforms
79
+ path = '/Project/PropertyGroup/Platform'
80
+ result = []
81
+ @doc.elements.each(path) { |element| result << element.text }
82
+ result
83
+ end
84
+
85
+ def project_version
86
+ path = 'Project/PropertyGroup/ProjectVersion'
87
+ @project_version = get(path).text.to_f unless @project_version
88
+ @project_version
89
+ end
90
+
91
+ # Set the target. Restricts it to valid values.
92
+ def target=(value)
93
+ check_value(targets, value, 'target')
94
+ @target = value
95
+ end
96
+
97
+ # Returns the set of valid targets
98
+ def targets
99
+ %w(Build Compile Clean)
100
+ end
101
+
102
+ private
103
+
104
+ def check_value(collection, value, name)
105
+ fail "#{value} is not a valid #{name} for #{@dproj}" unless
106
+ collection.include?(value)
107
+ end
108
+
109
+ def default_config
110
+ dc = get('Project/PropertyGroup/Configuration')
111
+ dc ? dc.text : 'Debug'
112
+ end
113
+
114
+ def get(path)
115
+ REXML::XPath.first(@doc, path)
116
+ end
117
+
118
+ def load
119
+ File.open(@dproj) { |f| @doc = REXML::Document.new f }
120
+ end
121
+
122
+ def subst_path(path)
123
+ result = path.sub('$(Platform)', platform).sub('$(Config)', config)
124
+ result = File.join(File.dirname(dproj), result) if result.start_with?('.')
125
+ File.expand_path(result)
126
+ end
127
+ end
128
+ end