Code structure (ots.make).

call to ots.make.generator.MakeFile constructor takes a list of files. Each
file is converted into a file object containing file's directory, name and
extension. The function MakeFile.make() converts these input files according
to a set of rules. File objects with "cpp" extension are converted into file
objects with "obj" extension, "cu" files become "cu.obj" files and so fourth.
Collateral result of such process is forming make file macros and clauses.

The following is the principal part of the code:

class MakeFile :

doDeleteFiles=True

def
__init__(self,

outputName,outputDir,files,

objDir='objs',

version=None,runtime=None,output=None,

askBeforeDelete=True,

\qquad**
kwargs

)
:

import
ots.make.options as opt

if(
version is None ) :

version=opt.Version.Release

if(
runtime is None ) :

runtime=opt.Runtime.Msvc9

if(
output is None ) :

output=opt.Output.Dll

self.version=version

self.runtime=runtime

self.output=output

self.outputName=outputName

self.outputDir=outputDir

self.objDir=objDir

self.files=files

self.askBeforeDelete=askBeforeDelete

od=str(objDir)

if(
od[1]==':' ) :

self.objDir=objDir

else
:

self.objDir=self.outputDir+objDir

for
k in kwargs :

setattr(self,k,kwargs[k])

def
make(self) :

import
ots.make.generator as g

import
ots.io as io

clauses=g.Clauses()

macros=g.Macros()

rules=g.Rules()

files=[]

for
(dr,fls) in self.files :

for
f in fls :

sn=io.ShortFileName(f)

files.append(g.File(dirPath=dr,name=sn._name,ext=sn._ext))

while(True)
:

if(
len(files)==0 ) :

break

f=files.pop(0)

processed=False

for
r in rules :

try
:

ff=r.process(

file=f,

macros=macros,

clauses=clauses,

generator=self

)

if(
not ff is None ) :

files.append(ff)

processed=True

break

except
NotApplicable :

pass

if(
not processed ) :

raise(Exception('Could
not find a rule for the following file:\n'+str(f)))

import
os

objDir=str(self.objDir)

if(
not os.path.exists(objDir) ) :

os.makedirs(objDir)

makeFile=str(self.objDir+'doIt.mak')

f=open(makeFile,'w')

f.write(str(macros))

f.write('\n')

f.write(str(clauses))

f.write('\n')

f.close()

In the constructor all the input data is attached to "self". The calculated
make file information is accumulated in the fields self.macros and
self.clauses created anew at every call to make(). All of these fields are
propagated everywhere in the code.

The lines

if( not ff is None ) :

files.append(ff)

refer to the possibility of not returning any result from the call to
process(). This happens, for example, with "obj" files. Processing of "obj"
file results in incremental construction of link clause and does not require
further processing.

The line "rules=g.Rules()" refers to the function Rules() declared in the file
make/generator/rules.py:

def Rules() :

return
[

_CppRule(),

_ObjRule(),

_CuRule(),

_CuObjRule(),

_IdlRule(),

_TlbRule(),

_ResRule(),

_DefRule(),

_RcRule()

]

The following an example of a rule:

class _ObjRule(IFinalRule)
:

def
_isApplicable(self) :

return
self.file.ext=='obj'

def
_process(self) :

self.macros.objs.add(self.file)

return
None

where the IFinalRule contains implementation of the function "process":

class IRule(object) :

def
process(self,file,macros,clauses,generator) :

self.file=file

import
ots.make.generator as g

if(
not isinstance(self.file,g.File) ) :

raise(Exception('ots.make.generator.File
object expected'))

if(
not self._isApplicable() ) :

raise(NotApplicable())

self.macros=macros

self.clauses=clauses

self.generator=generator

self._preprocessing()

return
self._process()

def
_preprocessing(self) :

pass

def
_process(self) :

raise(Exception('process
is not implemented'))

def
_isApplicable(self) :

raise(Exception('process
is not implemented'))

class IFinalRule(IRule) :

...

One can see that IRule takes care of propagation of data and leaves out hooks
_preprocessing, _process and _isApplicable. The following rule is a more
informative example of how such data is used.

The function "addIfDoesNotExist" makes sure that the argument
"self._makeCompilerOptionsMacro" is called only once and the field
self.macros.compilerOptions is created only once (per call to
MakeFile.make()).

The function "_getOptions" scans entire reference tree started at
self.generator for anything that has "compile" attribute and collect results
of all calls to "compile". This mechanics is responsible for assembling the
input information into a meaningful make file.