some updates to the Linux build system - obtained a core binary and all required...
[xonotic/netradiant.git] / config.py
1 import sys, traceback, platform, re, commands
2
3 if __name__ != '__main__':
4         from SCons.Script import *
5
6 import utils
7
8 # config = debug release
9 # aliases are going to be very needed here
10 # we have dependency situations too
11 # target = 
12
13 class Config:
14         # not used atm, but useful to keep a list in mind
15         # may use them eventually for the 'all' and other aliases expansions?
16         target_choices = utils.Enum( 'radiant' )
17         config_choices = utils.Enum( 'debug', 'release' )
18
19         # aliases
20         # 'all' -> for each choices
21         # 'gamecode' for the targets, 'game' 'cgame' 'ui'
22         
23         def __init__( self ):
24                 # initialize defaults
25                 self.target_selected = [ 'radiant' ]
26                 self.config_selected = [ 'release' ]
27                 # those are global to each config
28                 self.cc = 'gcc-4.1'
29                 self.cxx = 'g++-4.1'
30
31         def __repr__( self ):
32                 return 'config: target=%s config=%s' % ( self.target_selected, self.config_selected )
33
34         def _processTarget( self, ops ):
35                 self.target_selected = ops
36
37         def _processConfig( self, ops ):
38                 self.config_selected = ops
39
40         def _processCC( self, ops ):
41                 self.cc = ops
42
43         def _processCXX( self, ops ):
44                 self.cxx = ops
45
46         def setupParser( self, operators ):
47                 operators['target'] = self._processTarget
48                 operators['config'] = self._processConfig
49                 operators['cc'] = self._processCC
50                 operators['cxx'] = self._processCXX
51
52         def emit( self ):
53                 settings = self
54                 for config_name in self.config_selected:
55                         config = {}
56                         config['name'] = config_name
57                         config['shared'] = False
58                         Export( 'utils', 'settings', 'config' )
59                         build_dir = os.path.join( 'build', config_name )
60                         BuildDir( build_dir, '.', duplicate = 0 )
61                         # left out jpeg6, splines (FIXME: I think jpeg6 is not used at all, can trash?)
62                         lib_objects = []
63                         for project in [ 'libs/synapse/synapse.vcproj', 'libs/cmdlib/cmdlib.vcproj', 'libs/mathlib/mathlib.vcproj', 'libs/l_net/l_net.vcproj', 'libs/ddslib/ddslib.vcproj', 'libs/picomodel/picomodel.vcproj', 'libs/md5lib/md5lib.vcproj' ]:
64                                 Export( 'project' )
65                                 lib_objects += SConscript( os.path.join( build_dir, 'SConscript.lib' ) )
66                         Export( 'lib_objects' )
67                         radiant = SConscript( os.path.join( build_dir, 'SConscript.radiant' ) )
68                         InstallAs( 'install/radiant.bin', radiant )
69
70                         # PIC versions of the libs for the modules
71                         shlib_objects_extra = {}
72                         for project in [ 'libs/synapse/synapse.vcproj', 'libs/mathlib/mathlib.vcproj', 'libs/cmdlib/cmdlib.vcproj' ]:
73                                 ( libpath, libname ) = os.path.split( project )
74                                 libname = os.path.splitext( libname )[0]
75                                 config['shared'] = True
76                                 Export( 'project', 'config' )
77                                 build_dir = os.path.join( 'build', config_name, 'shobjs' )
78                                 BuildDir( build_dir, '.', duplicate = 0 )
79                                 shlib_objects_extra[libname] = SConscript( os.path.join( build_dir, 'SConscript.lib' ) )
80
81                         for project in [ 'plugins/vfspk3/vfspk3.vcproj',
82                                          'plugins/image/image.vcproj',
83                                          'plugins/entity/entity.vcproj',
84                                          'plugins/map/map.vcproj',
85                                          'plugins/mapxml/mapxml.vcproj',
86                                          'plugins/shaders/shaders.vcproj',
87                                          'plugins/surface/surface.vcproj'
88                                          ]:
89                                 ( libpath, libname ) = os.path.split( project )
90                                 libname = os.path.splitext( libname )[0]
91                                 shlib_objects = shlib_objects_extra['synapse']
92                                 if ( libname == 'entity' ):
93                                         shlib_objects += shlib_objects_extra['mathlib']
94                                 if ( libname == 'map' ):
95                                         shlib_objects += shlib_objects_extra['cmdlib']
96                                 Export( 'project', 'shlib_objects' )
97                                 module = SConscript( os.path.join( build_dir, 'SConscript.module' ) )
98                                 InstallAs( 'install/modules/%s.so' % libname, module )
99
100         def SetupEnvironment( self, env, config, useGtk = False, useGtkGL = False, useJPEG = False ):
101                 env['CC'] = self.cc
102                 env['CXX'] = self.cxx
103                 ( ret, xml2 ) = commands.getstatusoutput( 'xml2-config --cflags' )
104                 if ( ret != 0 ):
105                         print 'xml2-config failed'
106                         assert( False )
107                 xml2libs = commands.getoutput( 'xml2-config --libs' )
108                 env.Append( LINKFLAGS = xml2libs.split( ' ' ) )
109                 baseflags = [ '-pipe', '-Wall', '-fmessage-length=0', '-fvisibility=hidden', xml2.split( ' ' ) ]
110 #               baseflags += [ '-m32' ]
111                 if ( useGtk ):
112                         ( ret, gtk2 ) = commands.getstatusoutput( 'pkg-config gtk+-2.0 --cflags' )
113                         if ( ret != 0 ):
114                                 print 'pkg-config gtk+-2.0 failed'
115                                 assert( False )
116                         baseflags += gtk2.split( ' ' )
117                         gtk2libs = commands.getoutput( 'pkg-config gtk+-2.0 --libs' )
118                         env.Append( LINKFLAGS = gtk2libs.split( ' ' ) )
119                 else:
120                         # always setup at least glib
121                         ( ret, glib ) = commands.getstatusoutput( 'pkg-config glib-2.0 --cflags' )
122                         if ( ret != 0 ):
123                                 print 'pkg-config glib-2.0 failed'
124                                 assert( False )
125                         baseflags += glib.split( ' ' )
126                         gliblibs = commands.getoutput( 'pkg-config glib-2.0 --libs' )
127                         env.Append( LINKFLAGS = gliblibs.split( ' ' ) )
128                 if ( useGtkGL ):
129                         ( ret, gtkgl ) = commands.getstatusoutput( 'pkg-config gtkglext-1.0 --cflags' )
130                         if ( ret != 0 ):
131                                 print 'pkg-config gtkglext-1.0 failed'
132                                 assert( False )
133                         baseflags += gtkgl.split( ' ' )
134                         gtkgllibs = commands.getoutput( 'pkg-config gtkglext-1.0 --libs' )
135                         env.Append( LINKFLAGS = gtkgllibs.split( ' ' ) )
136                 if ( useJPEG ):
137                         env.Append( LIBS = 'jpeg' )
138                         
139                 env.Append( CFLAGS = baseflags )
140                 env.Append( CXXFLAGS = baseflags + [ '-fpermissive', '-fvisibility-inlines-hidden' ] )
141                 env.Append( CPPPATH = [ 'include', 'libs' ] )
142                 env.Append( CPPDEFINES = [ 'Q_NO_STLPORT' ] )
143                 if ( config == 'debug' ):
144                         env.Append( CFLAGS = [ '-g' ] )
145                         env.Append( CPPDEFINES = [ '_DEBUG' ] )                         
146                 else:
147                         env.Append( CFLAGS = [ '-O3', '-Winline', '-ffast-math', '-fno-unsafe-math-optimizations' ] )
148                         #env.Append( CFLAGS = [ '-march=pentium3' ] )
149
150 #               env.Append( LINKFLAGS = [ '-m32' ] )
151
152 # parse the config statement line to produce/update an existing config list
153 # the configs expose a list of keywords and accepted values, which the engine parses out 
154 class ConfigParser:
155         def __init__( self ):
156                 self.operators = {}
157
158         def _processOp( self, ops ):
159                 assert( len( ops ) == 1 )
160                 op = ops.pop()
161                 if ( op == 'clear' ):
162                         self.configs = []
163                         self.current_config = None
164                 elif ( op == 'pop' ):
165                         self.configs.pop()
166                         self.current_config = None
167                 elif ( op == 'push' ):
168                         self.configs.append( self.current_config )
169                         self.current_config = Config()
170                         self._setupParser( self.current_config )
171
172         def _setupParser( self, c ):
173                 self.operators = { 'op' : self._processOp }
174                 c.setupParser( self.operators )
175
176         def _parseStatement( self, s ):
177                 statement_re = re.compile( '(.*)=(.*)' )
178                 value_list_re = re.compile( '([^,]*),?' )
179                 if ( not statement_re.match( s ) ):
180                         print 'syntax error (statement match): %s' % repr( s )
181                         return
182                 statement_split = statement_re.split( s )
183                 if ( len( statement_split ) != 4 ):
184                         print 'syntax error (statement split): %s' % repr( s )
185                         return
186                 ( foo, name, value, bar ) = statement_split
187                 value_split = value_list_re.split( value )
188                 if ( len( value_split ) < 2 or len( value_split ) % 2 != 1 ):
189                         print 'syntax error (value split): %s' % ( repr( value_split ) )
190                         return
191                 try:
192                         value_array = []
193                         value_split.reverse()
194                         value_split.pop()
195                         while ( len( value_split ) != 0 ):
196                                 value_array.append( value_split.pop() )
197                                 value_split.pop()                                       
198                 except:
199                         print traceback.print_exception( sys.exc_type, sys.exc_value, sys.exc_traceback )
200                         print 'syntax error (value to array): %s' % ( repr( value_split ) )
201                         return
202
203                 return ( name, value_array )            
204         
205         def parseStatements( self, _configs, statements ):
206                 self.current_config = None
207                 self.configs = _configs
208                 if ( self.configs is None ):
209                         self.configs = []
210                 for s in statements:
211
212                         if ( self.current_config is None ):
213                                 # use a provided config, or create a default one
214                                 if ( len( self.configs ) > 0 ):
215                                         self.current_config = self.configs.pop()
216                                 else:
217                                         self.current_config = Config()
218                                 # setup the operator table for this config
219                                 # NOTE: have that in self._processOp too
220                                 self._setupParser( self.current_config )
221                         
222                         ret = self._parseStatement( s )
223                         if ( ret is None ):
224                                 print 'stop statement parse at %s' % repr( s )
225                                 break
226                         ( name, value_array ) = ret
227                         try:
228                                 processor = self.operators[name]
229                         except:
230                                 print 'unknown operator %s - stop statement parse at %s' % ( repr( name ), repr( s ) )
231                                 break
232                         processor( value_array )
233
234                 if ( not self.current_config is None ):
235                         self.configs.append( self.current_config )
236                 # make sure there is at least one config
237                 if ( len( self.configs ) == 0 ):
238                         print 'pushing a default config'
239                         self.configs.append( Config() )
240                 return self.configs
241
242 import unittest
243
244 class TestConfigParse( unittest.TestCase ):
245
246         def setUp( self ):
247                 self.parser = ConfigParser()
248
249         def testBasicParse( self ):
250                 # test basic config parsing
251                 # needs to cleanly stop at the first config statement that is not recognized
252                 configs = self.parser.parseStatements( None, [ 'game=missionpack', 'config=qvm', 'foobar' ] )
253                 print repr( configs )
254
255         def testMultiParse( self ):
256                 # multiple configs seperated by commas
257                 configs = self.parser.parseStatements( None, [ 'target=server,game,cgame' ] )
258                 print repr( configs )
259
260         def testOp( self ):
261                 # test the operator for multiple configs
262                 configs = self.parser.parseStatements( None, [ 'target=core', 'config=release', 'op=push', 'target=game,cgame,ui', 'config=debug' ] )
263                 print repr( configs )
264
265 if __name__ == '__main__':
266         unittest.main()