SCons and EiffelStudio 5.6
This is a SCons tool for building EiffelStudio 5.6 projects. It's superseded by EiffelStudioTool, which builds current versions of EiffelStudio. It has been tested on Windows XP and 2003, and on Mac OS X 10.4.7.
Put the builder in a script called Eiffel.py, in a directory that you reference with toolpath, as shown in the example below.
It requires Python 2.4 or above, because it uses the subprocess module which was new in Python 2.4.
It does not attempt to scan the Eiffel project's Ace configuration file to figure out dependencies. This means that, if you modify an Eiffel class, this tool will not know that it needs to rebuild the project. It should be fairly easy to writer a Scanner to do this, but I won't bother because EiffelStudio 5.7 has replaced Eiffel's venerable Ace files with a completely new XML-based ECF file format. So, after editing some classes, you need to run SCons with the -c option to clean the targets before doing a rebuild. Alternatively, you can explicitly build the list of dependencies yourself, using classes_in_cluster(), as in the following example.
Example Usage
1 # This example assumes that Eiffel.py is in the same directory as the SConstruct.
2
3 import os
4 from Eiffel import classes_in_cluster
5 env = Environment(ENV = os.environ, tools = ['Eiffel'], toolpath = ['.'])
6
7 # Build a precompiled library for the Gobo clusters.
8 gobo = Alias('gobo', env.Eiffel('gobo/precomp', 'gobo.ace'))
9
10 # Build some applications that depend on the gobo precompiled library.
11 # Each application will be built under the same directory containing its Ace file.
12 # If finalizing, install them into "/InstallDir".
13 for name, ace, classes in [
14 ['app1', 'app1/app1.ace', classes_in_cluster('app1')],
15 ['app2', 'app2/app2.ace', classes_in_cluster('app2')]
16 ]:
17 target = env.Eiffel(name, [ace, gobo] + classes)
18 Default(target)
19 Alias(name, target)
20 if len(target) > 2: Install('/InstallDir', target[2])
Builder
1 # EiffelStudio 5.6 support for SCons
2 # Written by Peter Gummer, July 2006
3 # Amended by Peter Gummer, December 2006
4
5 """
6 Tool-specific initialisation for EiffelStudio 5.6.
7 This probably also works with earlier versions of EiffelStudio.
8 """
9
10 import os, glob, sys, shutil, datetime, subprocess, SCons
11
12 log_file = None
13
14 def log_open(env):
15 global log_file
16
17 if env['ECLOG'] == '':
18 log_file = sys.stdout
19 elif log_file == None:
20 log_file = open(env['ECLOG'], 'w+')
21 elif log_file.closed:
22 log_file = open(env['ECLOG'], 'a+')
23
24 def log(s):
25 log_file.write(str(s) + '\n')
26
27 def log_date():
28 log(datetime.datetime.now())
29
30 def log_process(args, working_directory):
31 commandline = subprocess.list2cmdline(args)
32 if log_file != sys.stdout: print ' ' + commandline
33 log(commandline)
34 log_file.flush()
35 return subprocess.call(args, cwd = working_directory, stdout = log_file, stderr = subprocess.STDOUT)
36
37 def ec_string(target, source, env):
38 return env['ECFLAGS'] + ' ' + os.path.basename(str(target[0]))
39
40 def ec(target, source, env):
41 """
42 Function to be used as the Eiffel Builder's action.
43 Build target[0] (the Eiffel project file) and target[1] (the workbench executable) from source[0] (the Ace file).
44 Also build target[2] (the finalised executable) if specified.
45 All compiler output is logged to a file.
46 Note that ec's return code is unreliable: it returns 0 if C compilation fails.
47 We return 0 (success) if target[1] (the workbench executable) is built.
48 """
49 result = 0
50 epr = str(target[0])
51 exe = str(target[1])
52 ace = os.path.abspath(str(source[0]))
53 project = os.path.abspath(os.path.dirname(epr))
54
55 log_open(env)
56 log('=================== ' + epr + ' ===================')
57 log_date()
58
59 shutil.rmtree(project + '/EIFGEN')
60
61 command = ['ec', '-batch', '-ace', ace, '-project_path', project]
62 if os.path.basename(epr) == 'precomp.epr': command += ['-precompile']
63 log_process(command + env['ECFLAGS'].split() + ['-c_compile'], None)
64
65 if len(target) > 2 and os.path.exists(os.path.dirname(exe)):
66 log('--------------------------')
67 log_date()
68 log_process(['finish_freezing', '-silent'], os.path.dirname(exe))
69
70 if not os.path.exists(exe):
71 if log_file != sys.stdout:
72 if log_file.tell() > 1000:
73 log_file.seek(-1000, 1)
74 else:
75 log_file.seek(0)
76
77 print '... ' + log_file.read()
78 log_file.seek(0, 2)
79
80 subprocess.Popen(['estudio', '-create', project, '-ace', ace])
81 result = 1
82
83 if log_file != sys.stdout: log_file.close()
84 return result
85
86 def ace_to_epr(target, source, env):
87 """
88 Function to be used as the Eiffel Builder's emitter.
89 Parameters:
90 1. target[0]: the name of the executable (application or dll) produced by the Eiffel project.
91 2. target[1]: the Eiffel project directory. This parameter is optional.
92 2. source[0]: the Ace file. If target[1] is not given, this also gives the Eiffel project directory.
93 3. Additional source items optionally specify other dependencies, e.g. a precompiled library.
94 The result specifies the target and source that will be passed to ec():
95 1. Result target[0]: the given project's .epr file, with a directory part.
96 2. Result target[1]: the workbench executable target to be built ("driver" if precompiling).
97 3. Result target[2]: if finalising and not precompiling, the finalised executable target.
98 4. Result source[0]: the given Ace file.
99 5. Additional given source items, if any.
100 Note: the "driver" executable does not exist for .NET precompiled libraries, so it probably doesn't work.
101 """
102 result = None, source
103 exe = str(target[0])
104 epr = os.path.splitext(exe)[0] + '.epr'
105
106 if len(source) == 0:
107 print 'No source .ace file specified: cannot build ' + exe
108 elif not env.Detect('ec'):
109 print 'Please add "ec" to your path: cannot build ' + exe
110 else:
111 is_precompiling = epr == 'precomp.epr'
112 if is_precompiling: exe = env['ISE_C_COMPILER'] + '/driver' + env['PROGSUFFIX']
113
114 project_dir = os.path.dirname(str(source[0]))
115 if len(target) > 1: project_dir = str(target[1])
116
117 epr = project_dir + '/' + epr
118 exe = project_dir + '/EIFGEN/?_code/' + exe
119 result = [epr, exe.replace('?', 'W')]
120
121 if '-finalize' in env['ECFLAGS'] and not is_precompiling:
122 result += [exe.replace('?', 'F')]
123
124 result = result, source
125
126 return result
127
128 def c_compiler(env):
129 """
130 The given Environment's ISE_C_COMPILER variable, if set.
131 The Microsoft compiler is the default because ISE_C_COMPILER is normally set on all platforms but Windows.
132 """
133 if env['ENV'].has_key('ISE_C_COMPILER'):
134 return env['ENV']['ISE_C_COMPILER']
135 else:
136 return 'msc'
137
138 def generate(env):
139 """Add a Builder and options for Eiffel to the given Environment."""
140 opts = SCons.Script.Options()
141 opts.Add('ECFLAGS', '"-freeze" for a workbench build.', '-finalize')
142 opts.Add('ECLOG', 'File to log Eiffel compiler output.', 'SCons.Eiffel.log')
143 opts.Add('ISE_C_COMPILER', 'msc = Microsoft, bcc = Borland, etc.', c_compiler(env))
144 opts.Update(env)
145 SCons.Script.Help(opts.GenerateHelpText(env))
146
147 ec_action = SCons.Script.Action(ec, ec_string)
148 env['BUILDERS']['Eiffel'] = SCons.Script.Builder(action = ec_action, emitter = ace_to_epr, suffix = env['PROGSUFFIX'])
149
150 def exists(env):
151 """Is the Eiffel compiler available?"""
152 return env.Detect('ec')
153
154 # Utility functions.
155
156 def files(pattern):
157 """All files matching a pattern, excluding directories."""
158 return [file for file in glob.glob(pattern) if os.path.isfile(file)]
159
160 def classes_in_cluster(cluster):
161 """All Eiffel class files in the given cluster and its subclusters."""
162 result = []
163
164 for root, dirnames, filenames in os.walk(cluster):
165 if '.svn' in dirnames: dirnames.remove('.svn')
166 result += files(root + '/*.e')
167
168 return result
