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.
- checksums.yaml +7 -0
- data/delphi-compiler.gemspec +36 -0
- data/lib/delphi.rb +9 -0
- data/lib/delphi/compiler.rb +92 -0
- data/lib/delphi/dsl.rb +63 -0
- data/lib/delphi/environment.rb +66 -0
- data/lib/delphi/groupproj.rb +87 -0
- data/lib/delphi/project.rb +128 -0
- data/lib/delphi/resource.rb +29 -0
- data/lib/delphi/versions.rb +84 -0
- data/rakefile.rb +55 -0
- data/test/MyClass.pas +20 -0
- data/test/Test/TestMyClass.pas +53 -0
- data/test/Test/testTests.dpr +27 -0
- data/test/Test/testTests.dproj +485 -0
- data/test/Test/testTests.res +0 -0
- data/test/TestGroup.groupproj +48 -0
- data/test/test.dpr +18 -0
- data/test/test.dproj +511 -0
- data/test/test.res +0 -0
- data/test/test_project.rb +45 -0
- data/version.rb +3 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -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
|
data/lib/delphi.rb
ADDED
@@ -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
|
data/lib/delphi/dsl.rb
ADDED
@@ -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
|