SCons and Eiffel
This is a SCons tool for building EiffelStudio projects. It works with EiffelStudio 5.7 and above (although the latest EiffelStudio is recommended, currently 7.1). It has been tested on Windows (XP, 2003 and 7), Mac OS X (10.4, 10.5, 10.6 and 10.7) and Linux (Ubuntu 7.10 and 10.04).
Put the builder in a script called Eiffel.py, in a directory that you reference with toolpath, as shown in the example below; or put it in a directory called site_scons/site_tools as mentioned in the SCons man page.
It requires Python 2.4 or above, because it uses the subprocess module which was new in Python 2.4.
Sources and Targets
This Eiffel builder knows the nuts and bolts of running EiffelStudio's command-line compiler. Each EiffelStudio project has a .ecf file (Eiffel configuration file) and builds an executable or a DLL. You feed the builder the path to the .ecf configuration file; this is the builder's source. You can specify additional source dependencies, if you wish (resource files, icons, etc.).
The .ecf file contains one or more targets. A .ecf target specifies which executable you want to build and how to build it. You specify one of these to the builder.
The target passed to the builder is not the same as the target that the builder emits. The target passed in is a string (e.g., 'foo'), from which the builder computes the emitted full target path (e.g., 'C:\projects\app\EIFGENs\foo\F_code\foo.exe'). The builder's emitter computes the full target path by reading various settings inside the .ecf file. (For example, the executable_name setting gives the base name of the target file; sometimes this setting is missing, in which case the base name is the same as the .ecf document element's name attribute. The target file extension is also computed; for example, it is .dll if the msil_generation_type setting is dll.)
When calling the builder, you may omit the target if you wish. By default, the Eiffel builder builds the target with the same base name as the .ecf file. (There will be an error, of course, if the .ecf file has no such target.)
The builder usually emits one target file path (the executable or dll). In some cases, however, it emits more.
When building a .NET assembly (say, Foo.exe or Foo.dll), 'ec' also generates an unmanaged DLL (libFoo.dll) in the same directory. The emitter adds it as a second target.
When building a shared library on Windows (say, foo.dll), 'ec' generates a second file foo.lib in the same directory. The emitter adds it as a second target.
When building a precompiled library in classic mode (i.e., non-.NET), it emits a .melted file in the W_code directory, plus the actual precompiled library files in a compiler-specific subdirectory.
When C compilation is not performed, no executable or DLL will be built. Instead, the emitted target is the Makefile.SH file that controls C compilation. This is handy if the calling script needs to manipulate Makefile.SH prior to launching C compilation as a separate step. The emitter also adds project.epr as a target, because it is created at the end of the Eiffel compilation and is therefore a good indicator that compilation succeeded.
The Scanner
Apart from the .ecf file, the most important input to the Eiffel compiler is the universe of .e Eiffel class files that it has to compile. These class files are dependencies that the builder figures out for itself, by scanning the .ecf file. The Scanner looks for all cluster and override declarations inside the .ecf, and finds all .e files in each such directory. Therefore, thanks to the Scanner, if you modify an Eiffel class in one of the clusters named in the .ecf, or if you modify the .ecf itself, then the builder will know that it needs to rebuild the project.
If the cluster or override is recursive, then the Scanner scans the subdirectories too. If it's not recursive but has explicitly-named sub-clusters, the Scanner also understands how to scan these sub-clusters.
As well as .e class files, the scanner detects the following dependencies mentioned in the .ecf file:
.ecf libraries. (Such libraries are not recursively scanned, however. Although it's useful to detect that the library file itself has changed, a library is a stable thing, so scanning its contents would be a waste of time.)
.NET assemblies.
- External object files.
.h or .hpp files inside external include directories.
Environment variable substitution is performed on the file and directory paths. The Scanner prints warnings if the .ecf file uses undefined environment variables. Construction variables are deliberately ignored, because it would be incorrect to substitute them given that the Eiffel compiler does not know about SCons construction variables. The following EiffelStudio environment variables are commonly used in .ecf files, so if they are undefined the builder tries to define them with sensible platform-specific assumptions:
ISE_C_COMPILER
ISE_EIFFEL
ISE_LIBRARY
ISE_PLATFORM
ECF_CONFIG_PATH
Choosing an Eiffel Compiler
The builder has a construction variable EC, specifying the Eiffel command-line compiler. It defaults to the path to "ec" found in the PATH environment variable. Failing that, it uses the ISE_EIFFEL environment variable to construct the path to "ec". If this too fails, then it falls back simply to "ec". (Using ISE_EIFFEL is helpful, for example, when running SCons on Linux or Mac via sudo, which restricts the PATH variable, or if the user has not added "ec" to the path.)
EiffelStudio 6.2 introduced an alternative compiler called "ecb". Its output is identical to the output of "ec", but it reportedly runs about 20% faster. Beware, however, that "ecb" uses its own set of precompiled libraries, rather than the ones installed with EiffelStudio, so if you are using precompiled libraries you will have to build them specially for "ecb".
I haven't tested the builder with the Gobo Eiffel compiler gec, nor with Helmut Brandl's Eiffel interpreter and compiler tecomp.
Eiffel Compiler Flags
The ECFLAGS construction variable controls what kind of build is done by the Eiffel compiler. Possible values for ECFLAGS are given by typing ec -help at the command line. Here are some common examples:
scons ECFLAGS="-finalize -clean -c_compile": build a finalized (i.e. optimized) executable, from scratch, without assertions. This is the default.
scons ECFLAGS="-finalize -clean": build the C files for a finalized build, from scratch, without assertions, but do not build the executable.
scons ECFLAGS="-finalize -keep -c_compile": do an incremental build of a finalized executable, retaining assertions.
scons ECFLAGS="-precompile -clean" -c_compile: build a precompiled library.
scons ECFLAGS="-freeze -c_compile": do an incremental freeze (i.e., make a debug executable).
scons ECFLAGS="-freeze -project_path 62 -c_compile": do an incremental freeze, in a subdirectory below the SConscript called "62", rather than the default location below the .ecf file.
scons ECFLAGS="-freeze -target app_no_precompile -c_compile": do an incremental freeze, using the the .ecf target "app_no_precompile".
Note, in the last example, that specifying the -target flag overrides any other way of specifying the target.
ECFLAGS can be set by several means:
- On the SCons command line (as shown in the examples above);
- As a named parameter to your Eiffel builder call (as shown in the example below, which is probably the most convenient way to do it); or,
By setting env['ECFLAGS'] explicitly, prior to calling the Eiffel Builder.
Logging
The Eiffel compiler 'ec' produces a lot of progress output. Generally you don't want to see this, so the Eiffel builder redirects it to a file, "SCons.Eiffel.log", in the same directory as the SConstruct. You can specify a different file by setting the ECLOG construction variable. If you want to let it to go to standard output, set ECLOG to nothing (e.g., on the command line, scons ECLOG=).
But you do want to see this output if the build fails! You can open the log file to inspect the reason for failure, which would normally be at the end of the log file. For convenience, however, the Eiffel builder echoes the last thousand characters from the log file onto standard output, which almost invariably proves to be just the right amount of detail to allow you to see what went wrong.
Example Usage
The following SConstruct assumes that you have created "app.ecf" with 3 targets:
gobo to precompile the Gobo library.
app to build an executable named "app" without any precompiled libraries.
app_using_precompile to build "app" with the Gobo precompiled library.
1 # Create an Environment that imports "Eiffel.py" from the same directory as the SConstruct.
2
3 import os
4 env = Environment(ENV = os.environ, tools = ['Eiffel'], toolpath = ['.'])
5
6 # Build "app" (or, on Windows, "app.exe").
7 # The build depends on "app.ecf" and "app.rc".
8 # It also depends on all of the "*.e" files, etc., found by the Scanner.
9 # (Note how the target does not need to be specified, because by default it builds the target with the same base name "app".)
10
11 env.Eiffel(['app.ecf', 'app.rc'])
12
13 # Build the precompiled Gobo library.
14 # (Note how we specify the "gobo" target explicitly.)
15
16 gobo = env.Eiffel('gobo', 'app.ecf', ECFLAGS = '-precompile -clean -c_compile')
17
18 # Here's how to build "app" with the Gobo precompiled library.
19 # (Note how it depends on "gobo", and the target is explicitly given.)
20
21 env.Eiffel('app_using_precompile', ['app.ecf', 'app.rc', gobo])
Builder
1 # EiffelStudio support for SCons
2 # Written by Peter Gummer, February 2007
3 # Scanner added - Peter Gummer, May 2007
4 # Scanner uses xml.dom - Peter Gummer, September 2007
5 # Fix scanning non-recursive clusters - Peter Gummer, March 2008
6 # Don't call finish_freezing - Peter Gummer, April 2008
7 # Support -target and -project_path compiler options and incremental builds - Peter Gummer, May 2008
8 # Scanner detects more dependencies - Peter Gummer, June 2008
9 # Emitter computes target by reading .ecf file - Peter Gummer, June 2008
10 # Support -c_compile compiler option rather than hard-coding it - Peter Gummer, December 2008
11 # Fix scanning on non-Windows platforms - Peter Gummer, August 2010
12 # Emitter computes target compatibly with EiffelStudio 6.5 and higher - Peter Gummer, August 2010
13 # Scanner detects "tests" dependencies to be compatible with EiffelStudio 6.7 - Peter Gummer, December 2010
14 # The EC variable defaults from the the PATH or ISE_EIFFEL environment variable - Peter Gummer, December 2010
15 # Scanner expands $ECF_CONFIG_PATH in dependency locations - Peter Gummer, May 2011
16 # Adjust manifest file path in Windows resource file - Peter Gummer, July 2012
17 # Remove the Files() method; use Glob() instead - Peter Gummer, January 2013
18
19 """
20 Tool-specific initialisation for EiffelStudio.
21 This does not work with EiffelStudio 5.6 or earlier.
22 """
23
24 import os, glob, sys, shutil, datetime, subprocess, re, xml.dom.minidom
25 from SCons.Script import *
26
27 log_file = None
28
29 def log_open(env):
30 global log_file
31
32 if env['ECLOG'] == '':
33 log_file = sys.stdout
34 elif log_file == None:
35 log_file = open(env['ECLOG'], 'w+')
36 elif log_file.closed:
37 log_file = open(env['ECLOG'], 'a+')
38
39 def log(s):
40 log_file.write(str(s) + '\n')
41
42 def log_date():
43 log(datetime.datetime.now())
44
45 def log_process(args):
46 commandline = subprocess.list2cmdline(args)
47 if log_file != sys.stdout: print ' ' + commandline
48 log(commandline)
49 log_file.flush()
50 subprocess.call(args, stdout = log_file, stderr = subprocess.STDOUT)
51
52 def log_file_tail():
53 """The last thousand characters of the log file."""
54 result = ''
55
56 if log_file != sys.stdout:
57 if log_file.tell() > 1000:
58 log_file.seek(-1000, 1)
59 else:
60 log_file.seek(0)
61
62 result = '... ' + log_file.read()
63 log_file.seek(0, 2)
64
65 return result
66
67 def ec_action(target, source, env):
68 """
69 The Eiffel Builder's action function, running the Eiffel compiler.
70 Parameters are as returned by ec_emitter():
71 * target: the paths to the files to be built, as generated by ec_emitter().
72 * source[0]: the ECF file.
73 * source[1], source[2], etc.: any additional dependencies.
74 * env['ECLOG']: name of file to which all compiler output is logged (stdout if empty).
75 * env['ECFLAGS']: Eiffel compiler flags: -finalize, -freeze, -clean, -project_path, -target, etc.
76 Result is 0 (success) if all targets are built; else 1.
77 (Note that the Eiffel compiler's return code is unreliable: it returns 0 if C compilation fails.)
78 """
79 result = 0
80
81 log_open(env)
82 log('=================== ' + ecf_target(target) + ' ===================')
83 log_date()
84
85 rc_copied_to_target = None
86
87 if env['PLATFORM'] == 'win32':
88 rc = os.path.splitext(str(source[0]))[0] + '.rc'
89
90 if os.path.exists(rc):
91 project_path = dirname(str(target[0]), 4)
92 rc_copied_to_target = os.path.join(project_path, os.path.basename(rc))
93
94 if rc == rc_copied_to_target:
95 rc_copied_to_target = None
96 else:
97 f = open(rc, 'r')
98 try: s = f.read()
99 finally: f.close()
100
101 if s:
102 icon_pattern = r'(\w+[ \t]+ICON[ \t]+[^"]*")([^"]+")'
103 manifest_pattern = r'(CREATEPROCESS_MANIFEST_RESOURCE_ID[ \t]+RT_MANIFEST[ \t]+")([^"]+")'
104 substitution = r'\g<1>' + os.path.dirname(rc).replace('\\', '/') + r'/\g<2>'
105 s = re.sub(icon_pattern, substitution, s)
106 s = re.sub(manifest_pattern, substitution, s)
107 f = open(rc_copied_to_target, 'w')
108 try: f.write(s)
109 finally: f.close()
110
111 flags = env['ECFLAGS'].split()
112 if not '-target' in flags: flags += ['-target', ecf_target(target)]
113 log_process([env['EC'], '-batch', '-config', str(source[0])] + flags)
114
115 for t in target:
116 if result == 0 and not os.path.exists(str(t)):
117 print log_file_tail()
118 result = 1
119
120 if rc_copied_to_target: os.remove(rc_copied_to_target)
121 if log_file != sys.stdout: log_file.close()
122 return result
123
124 def ec_emitter(target, source, env):
125 """
126 The Eiffel Builder's emitter function.
127 Parameters:
128 * target[0]: the ECF target to be built; if empty then defaults to the first target in the ECF file.
129 * source[0]: the ECF file. The paths to the files to be built are computed by reading this file.
130 * source[1], source[2], etc.: additional optional dependencies (precompiled libraries, ".rc" files, etc.).
131 * env['ECFLAGS']: Eiffel compiler flags. The paths to the files to be built are affected by these.
132 Result emits the target and source parameters passed to ec_action().
133 * The source emitted is exactly the same as the source parameter passed in.
134 * The target emitted contains one or more calculated file paths.
135 Each target file is in the directory {-project_path}/EIFGENs/{-target}/{-finalize}, where:
136 -project_path if omitted defaults to the current working directory;
137 -target if omitted defaults to the base name of target[0] (or else to the first target in the ECF file);
138 -finalize evaluates to "F_code", else if omitted defaults to "W_code".
139 The number of target file paths emitted, and the actual file names, depend on several factors:
140 * Options specified inside the ECF file;
141 * The -precompile flag;
142 * The -c_compile flag.
143 """
144 result = None
145
146 if len(target) > 0:
147 ec_target = os.path.basename(str(target[0]))
148 else:
149 ec_target = ""
150
151 if len(source) == 0:
152 print '****** ERROR! No source .ecf file specified: cannot build ' + ec_target
153 elif not env.Detect(env['EC']):
154 print '****** ERROR! The Eiffel compiler ' + env['EC'] + ' is missing from your path: cannot build ' + ec_target
155 else:
156 ecf = str(source[0])
157 ec_path = os.getcwd()
158 ec_code = 'W_code'
159 exe_name = dotnet_type = is_dotnet = is_precompiling = is_c_compiling = is_shared_library = None
160
161 flags = env['ECFLAGS'].split()
162
163 for i, flag in enumerate(flags):
164 if flag == '-project_path':
165 ec_path = flags[i + 1]
166 elif flag == '-target':
167 ec_target = flags[i + 1]
168 elif flag == '-finalize':
169 ec_code = 'F_code'
170 elif flag == '-precompile':
171 is_precompiling = True
172 elif flag == '-c_compile':
173 is_c_compiling = True
174
175 ecf_as_xml = xml.dom.minidom.parse(ecf)
176 ec_target_next = ec_target
177
178 while ec_target_next <> None:
179 t = ec_target_next
180 ec_target_next = None
181
182 for element in ecf_as_xml.getElementsByTagName('target'):
183 name = element.attributes['name'].value
184 if ec_target == "": t = ec_target = name
185
186 if t == name:
187 if element.hasAttribute('extends'):
188 ec_target_next = element.attributes['extends'].value
189
190 for setting in element.getElementsByTagName('setting'):
191 name = setting.attributes['name'].value
192
193 if name == 'msil_generation':
194 if is_dotnet == None:
195 is_dotnet = setting.attributes['value'].value == 'true'
196 elif name == 'msil_generation_type':
197 if dotnet_type == None:
198 dotnet_type = '.' + setting.attributes['value'].value
199 elif name == 'executable_name':
200 if exe_name == None:
201 exe_name = setting.attributes['value'].value
202 elif name == 'shared_library_definition':
203 if is_shared_library == None:
204 is_shared_library = True
205
206 if exe_name == None:
207 exe_name = str(ecf_as_xml.documentElement.attributes['name'].value)
208
209 if dotnet_type:
210 ext = dotnet_type
211 elif is_precompiling:
212 ext = '.melted'
213 elif is_shared_library:
214 exe_name = env['SHLIBPREFIX'] + exe_name
215 ext = env['SHLIBSUFFIX']
216 else:
217 exe_name = env['PROGPREFIX'] + exe_name
218 ext = env['PROGSUFFIX']
219
220 ec_path += '/EIFGENs/' + ec_target + '/'
221
222 if is_c_compiling:
223 ec_path += ec_code + '/'
224 result = [ec_path + exe_name + ext]
225
226 if is_dotnet:
227 result += [ec_path + 'lib' + exe_name + '.dll']
228 elif is_precompiling:
229 result += [ec_path + environment_variable(env, 'ISE_C_COMPILER') + '/' + env['PROGPREFIX'] + 'driver' + env['PROGSUFFIX']]
230 elif is_shared_library and env['PLATFORM'] == 'win32':
231 result += [ec_path + 'dll_' + exe_name + '.lib']
232 else:
233 result = [ec_path + ec_code + '/Makefile.SH', ec_path + 'project.epr']
234
235 return result, source
236
237 ecf_environment_variable_regex = re.compile(r'(\$\||\$\(?\w*\)?|[^$]+)', re.M)
238
239 def ecf_scanner(node, env, path):
240 """
241 All dependencies mentioned in 'node', which is expected to be an ECF file.
242 The dependencies consist of:
243 * All Eiffel class files found in all clusters (including override clusters) mentioned in the ECF file.
244 * All .ecf library files mentioned in the ECF file. (Such libraries are not themselves scanned).
245 * All .NET assemblies mentioned in the ECF file.
246 * All external object files mentioned in the ECF file.
247 * All .h and .hpp files found in external include directories mentioned in the ECF file.
248 Because this ignores targets and conditionals in the ECF file, it may cause unnecessary builds.
249 """
250
251 def element_location(element):
252 """
253 The 'location' attribute of 'element', processed to take care of:
254 * Expansion of environment variables.
255 * If 'location' is relative, prefixing with the directory name of 'node'.
256 * If 'location' is a nested cluster, prefixing with the location of the parent element (recursively).
257 """
258 result = ''
259 ecf_config_path = os.path.dirname(os.path.abspath(str(node)))
260
261 for token in ecf_environment_variable_regex.findall(element.attributes['location'].value):
262 if token[0] <> r'$':
263 result += token
264 elif token == r'$|':
265 result += element_location(element.parentNode) + '/'
266 elif token == r'$ECF_CONFIG_PATH' or token == r'$(ECF_CONFIG_PATH)':
267 result += ecf_config_path
268 else:
269 s = environment_variable(env, token)
270
271 if s:
272 result += s
273 else:
274 print '****** WARNING!', str(node), 'uses undefined environment variable', token
275
276 result = result.replace('\\', '/')
277
278 if not os.path.isabs(result):
279 result = os.path.join(ecf_config_path, result)
280
281 return result
282
283 result = []
284 ecf_as_xml = xml.dom.minidom.parse(str(node))
285
286 for tag in ['cluster', 'override', 'tests', 'library', 'assembly', 'external_include', 'external_object']:
287 for element in ecf_as_xml.getElementsByTagName(tag):
288 location = element_location(element)
289
290 if os.path.isfile(location):
291 result += [location]
292 elif tag == 'external_include':
293 result += env.Glob(location + '/*.h') + env.Glob(location + '/*.hpp')
294 elif element.attributes.get('recursive', None):
295 result += classes_in_cluster(env, location)
296 else:
297 result += env.Glob(location + '/*.e')
298
299 return result
300
301 def ecf_target(target, source = None, env = None):
302 """The ECF target corresponding to the given build target."""
303 return os.path.basename(dirname(str(target[0]), 2))
304
305 def generate(env):
306 """Add a Builder and construction variables for Eiffel to the given Environment."""
307 default_ec_path = env.WhereIs('ec')
308
309 if not default_ec_path:
310 default_ec_path = spec_path(env, 'studio', 'bin/ec')
311 if default_ec_path == '': default_ec_path = 'ec'
312
313 vars = Variables()
314 vars.Add('EC', "The Eiffel command-line compiler.", default_ec_path)
315 vars.Add('ECFLAGS', "Use ec -help to see possible options.", '-finalize -clean -c_compile')
316 vars.Add('ECLOG', "File to log Eiffel compiler output.", 'SCons.Eiffel.log')
317 vars.Update(env)
318 Help(vars.GenerateHelpText(env))
319
320 env['BUILDERS']['Eiffel'] = Builder(action = Action(ec_action, ecf_target), emitter = ec_emitter, target_factory = Entry)
321 env.Append(SCANNERS = Scanner(ecf_scanner, skeys = ['.ecf']))
322 env.AddMethod(environment_variable, "EiffelEnvironmentVariable")
323 env.AddMethod(classes_in_cluster, "EiffelClassesInCluster")
324 env.AddMethod(spec_path, "EiffelSpecPath")
325
326 for v in ['ISE_EIFFEL', 'ISE_PLATFORM', 'ISE_C_COMPILER']:
327 if not environment_variable(env, v):
328 print '****** WARNING! Undefined Eiffel environment variable ' + v + '.'
329
330 def exists(env):
331 """Is the Eiffel compiler available?"""
332 return env.Detect(env['EC'])
333
334 # Utility functions.
335
336 def environment_variable(env, var):
337 """
338 The value of the environment variable 'var' within 'env'.
339 If undefined and it is one of the standard EiffelStudio variables, a sensible platform-specific assumption is used; else None.
340 """
341 result = None
342 var = var.lstrip('$').lstrip('(').rstrip(')')
343
344 if env['ENV'].has_key(var):
345 result = env['ENV'][var]
346 elif var == 'ISE_PLATFORM':
347 if env['PLATFORM'] == 'win32':
348 result = 'windows'
349 elif env['PLATFORM'] == 'darwin':
350 result = 'macosx-x86'
351 else:
352 result = 'linux-x86'
353 elif var == 'ISE_C_COMPILER':
354 if env['PLATFORM'] == 'win32':
355 result = 'msc'
356 else:
357 result = 'gcc'
358 elif var == 'ISE_EIFFEL':
359 if env.has_key('EC'): result = env.WhereIs(env['EC'])
360 if result: result = os.path.abspath(dirname(result, 5))
361 elif var == 'ISE_LIBRARY':
362 result = environment_variable(env, 'ISE_EIFFEL')
363
364 return result
365
366 def classes_in_cluster(env, cluster):
367 """All Eiffel class files in the given cluster and its subclusters."""
368 result = []
369
370 for root, dirnames, filenames in os.walk(cluster):
371 if '.svn' in dirnames: dirnames.remove('.svn')
372 result += env.Glob(root + '/*.e')
373
374 return result
375
376 def spec_path(env, mid_part, tail):
377 """
378 A platform-dependent path in the EiffelStudio installation directory of the form:
379 $ISE_EIFFEL + mid_part + '/spec/' + $ISE_PLATFORM + '/' + tail
380 If either of these environment variables is undefined, then the result is an empty string.
381 """
382 result = ''
383 ise_eiffel = environment_variable(env, 'ISE_EIFFEL')
384 ise_platform = environment_variable(env, 'ISE_PLATFORM')
385
386 if ise_eiffel and ise_platform:
387 result = os.path.join(ise_eiffel, mid_part)
388 result = os.path.join(result, 'spec')
389 result = os.path.join(result, ise_platform)
390 result = os.path.join(result, tail)
391 result = os.path.abspath(result)
392
393 return result
394
395 def dirname(path, n):
396 """The directory name of 'path', called recursively 'n' times."""
397 result = path
398 if n > 0: result = dirname(os.path.dirname(path), n - 1)
399 return result
Possible Enhancements
The builder should define ECCOM and ECCOMSTR to support that common idiom.
On Windows, if one of the ISE_* environment variables is not defined then EiffelStudio looks for it in the registry. The Scanner could simulate this better by likewise looking at the registry. The trouble with this approach (apart from the added complexity) is that it might easily break if EiffelStudio's use of the registry changes in future versions.
When doing a .NET build, the Scanner prints a warning that $ISE_DOTNET_FRAMEWORK is undefined. This is the directory containing .NET framework assemblies. We would not normally expect the environment variable to be defined, so it would be good to suppress the warning, unless a reliable way can be found to guess at its value.
The Scanner scans all of the dependencies that the .ecf mentions, blindly ignoring any targets or conditions specified in the .ecf. This can cause unnecessary nodes in the dependency list.
Conditional scanning would require handling the various cases that EiffelStudio itself handles. This would be complicated and error-prone, so it may be best not even to try this one!
- A dependency may be declared only for a particular target in the .ecf. There a couple of ways that target-specific scanning might be achieved:
- One option would be passing the target to the Scanner. A few ideas for doing this:
Perhaps the Builder() keyword argument target_scanner could be used.
Perhaps the Scanner() keyword argument path_function could be abused.
Perhaps the emitter might add the target to Node.attributes, for the Scanner to retrieve.
Probably a cleaner option would be to remove the Scanner altogether, instead adding the dependencies to the sources list in the emitter. This would also be more efficient, because the .ecf file would be parsed only once, not twice.
- One option would be passing the target to the Scanner. A few ideas for doing this:
