CMTInWaf: cmt.py

File cmt.py, 18.5 KB (added by /C=FR/O=CNRS/OU=UMR8607/CN=Christian Arnault/emailAddress=arnault@…, 12 years ago)
Line 
1
2import os, sys, re, shutil
3
4#-------------------------
5# Data model
6#
7class CMTDtb:
8    #---------------------------------------------------------
9    def __init__ (self):
10        self.paths = []
11        self.packages = {}
12        self.libraries = {}
13        self.programs = {}
14        self.macros = {}
15
16    #---------------------------------------------------------
17    def scan_packages (self):
18        for name in self.packages:
19            p = self.packages[name]
20            p.done = False
21
22    #---------------------------------------------------------
23    def add_package (self, name, package):
24        if not name in self.packages:
25            self.packages[name] = package
26
27    #---------------------------------------------------------
28    def add_library (self, ctx, use, name, sources):
29        topsrc = os.path.join (use, 'src')
30        print 'add_library> use=%s name=%s topsrc=%s' % (use, name, topsrc)
31        fsources = []
32        for s in sources:
33            lst = ctx.path.ant_glob (os.path.join (topsrc, s))
34            for f in lst:
35                p = f.path_from (ctx.path)
36                fsources.append (p)
37
38        self.libraries[name] = fsources
39
40    #---------------------------------------------------------
41    def add_program (self, ctx, use, name, sources, uses = ''):
42        topsrc = os.path.join (use, 'src')
43        fsources = []
44        for s in sources:
45            lst = ctx.path.ant_glob (os.path.join (topsrc, s))
46            for f in lst:
47                p = f.path_from (ctx.path)
48                fsources.append (p)
49
50        self.programs[name] = [fsources, uses]
51
52    #---------------------------------------------------------
53    def add_macro (self, ctx, use, name, value):
54        self.macros[name] = value
55
56    #---------------------------------------------------------
57    def add_cmt_path (self, path):
58        self.paths.append (path)
59
60    #---------------------------------------------------------
61    def show_macros (self):
62        for macro, value in self.macros.iteritems():
63            print '> macro: ' + macro + ' ' + str(value)
64
65    #---------------------------------------------------------
66    def show_libraries (self):
67        for lib, sources in self.libraries.iteritems():
68            print '> library: ' + lib
69
70    #---------------------------------------------------------
71    def show_programs (self):
72        for lib, sources in self.programs.iteritems():
73            print '> programs: ' + lib
74
75
76
77dtb = CMTDtb ()
78
79#-------------------------
80# Package configuration
81#
82class CMTMacro:
83    #---------------------------------------------------------
84    def __init__ (self, name = ''):
85        if name == '':
86            return
87       
88        self.name = name
89
90#-------------------------
91# Package configuration
92#
93class CMTPackage:
94    #---------------------------------------------------------
95    def __init__ (self, ctx, name = '', path = ''):
96        if name == '':
97            return
98       
99        self.name = name
100        self.path = path
101        self.uses = []
102
103        dtb.add_package (name, self)
104
105        self.read_requirements (ctx)
106        self.done = False
107
108    #---------------------------------------------------------
109    def add_use (self, ctx, name):
110        found = False
111
112        self.uses.append (name)
113
114        if not name in dtb.packages:
115            for path in dtb.paths:
116                #print 'CMTDtb.add_use> path=' + path
117                p = os.path.join (path, name)
118                if os.path.exists (p):
119                    p = os.path.join (p, 'cmt')
120                    #print 'CMTPackage.add_use> p=' + p
121                    if os.path.exists (p):
122                        package = CMTPackage (ctx, name, path)
123                        found = True
124        else:
125            found = True
126       
127        if not found:
128            print 'package ' + name + 'not found in cmtpaths'
129
130    #---------------------------------------------------------
131    def show_uses (self, pre = ''):
132        print pre + self.name + ' ' + self.path
133
134        if self.done:
135            return
136        self.done = True
137
138        #print pre + 'uses=' + ','.join(self.uses)
139       
140        for name in self.uses:
141            package = dtb.packages[name]
142            package.show_uses (pre + '  ')
143
144    #---------------------------------------------------------
145    def read_requirements (self, ctx):
146        path = os.path.join (self.path, self.name, 'cmt', 'requirements')
147        #print 'CMTPackage.read_requirements> path=' + path
148        try:
149            f = open (path, 'r')
150            for line in f:
151                words = re.split ('\s+', line)
152                if len(words[-1]) == 0:
153                    words = words[:-1]
154                #print 'ws=[' + str(words) + '] last word=[' + str(len(words[-1])) + ']'
155                verb = words[0]
156                if verb == 'use':
157                    #print 'from package ' + self.name + '>use ' + words[1]
158                    self.add_use (ctx=ctx, name=words[1])
159                elif verb == 'library':
160                    #print self.name + '>library ' + words[1] + ' ' + str(words[2:])
161                    dtb.add_library (ctx=ctx, use=self.name, name=words[1], sources=words[2:])
162                elif verb == 'program':
163                    #print self.name + '>program ' + words[1] + ' ' + str(words[2:])
164                    dtb.add_program (ctx=ctx, use=self.name, name=words[1], sources=words[2:])
165                elif verb == 'macro':
166                    #print self.name + '>macro ' + words[1] + ' ' + str(words[2:])
167                    dtb.add_macro (ctx=ctx, use=self.name, name=words[1], value=words[2:])
168        finally:
169            f.close ()
170
171    #---------------------------------------------------------
172    def build (self, ctx):
173        src = os.path.join (self.path, self.name, 'src')
174
175        print 'CMTPackage.build> srcnode=' + str(ctx.srcnode) + ' here=' + os.getcwd ()
176
177        includes = src
178        libraries = ''
179
180        # we collect include dirs from used packages
181        for use in self.uses:
182            inc = os.path.join (use, 'src')
183            includes += ' ' + inc
184
185        # we declare the build for all libraries
186        for lib, sources in dtb.libraries.iteritems():
187
188            print self.name + '> build library ' + lib + ' sources=' + str(sources) + ' includes=' + str(includes)
189           
190            linkopts = ''
191            if lib + '_linkopts' in dtb.macros:
192                linkopts = dtb.macros[lib + '_linkopts']
193
194            link = ' '.join (linkopts)
195
196            ctx.shlib (
197                source=sources, 
198                includes=includes,
199                use=link,
200                target=lib)
201
202        # we declare the build for all programs
203        for pgm, attrs in dtb.programs.iteritems():
204            sources = attrs[0]
205
206            linkopts = ''
207            if pgm + '_linkopts' in dtb.macros:
208                linkopts = dtb.macros[pgm + '_linkopts']
209
210            link = ' '.join (linkopts)
211
212            print self.name + '> build program ' + pgm + ' sources=' + str(sources) + ' link=' + str(link)
213            uses = attrs[1]
214            ctx.program (
215                source=sources, 
216                includes=includes,
217                use=link,
218                target=pgm)
219
220"""
221ProjectGenerator
222
223Construct a CMT project with a hierarchy of packages each containing
224 - sources of a library (with one C++ class)
225 - sources of a test program (which instantiates one object of the class)
226 - a cmt requirements file
227
228Packages may use other packages.
229For each used package, the package's class includes the used classes, and instantiates one object for each the used classe
230
231Two modes:
232  - 'cmt' (the default)
233      only one generic wscript is generated. This script makes use of the CMT module which interprets
234      the requirements files to construct the WAF build actions.
235  - 'waf'
236      one complete WAF script is generated. This script is a pure waf script independant of CMT.
237     
238"""
239class CMTProjectGenerator:
240    #---------------------------------------------------------
241    def __init__ (self, mode = 'cmt'):
242        self.mode = mode
243        self.used = {}
244
245    def write_text (self, name, text):
246        try:
247            f = open(os.path.join (self.project, name), 'w')
248            f.write (text)
249        finally:
250            f.close ()
251           
252    def package_name (self, i):
253        return 'Pack%03d' % i
254       
255    #---------------------------------------------------------
256    def cleanup_project (self):
257        print '> rmdir (%s)' % self.project
258        if os.path.exists(self.project):
259            shutil.rmtree(self.project)
260
261    #---------------------------------------------------------
262    def set_project_requirements (self):
263        print '> mkdir (%s)' % self.project
264        os.mkdir (self.project)
265       
266        print '> mkdir (%s/cmt)' % self.project
267        os.mkdir (os.path.join (self.project, 'cmt'))
268
269        print '> create %s/cmt/requirements' % self.project
270
271        text = ''
272        if sys.platform == 'win32':
273            text += '''#-----------------\n
274macro CXXFLAGS /EHsc\n
275'''
276
277        for p in range(self.packages):
278            text += 'use %s\n' % self.package_name (p)
279            text += '#-----------------\n'
280
281        self.write_text (os.path.join ('cmt', 'requirements'), text)
282
283    #---------------------------------------------------------
284    def get_used (self, p):
285        used = set ()
286        if p in self.used:
287            used = set (self.used[p])
288        return used
289
290    #---------------------------------------------------------
291    def get_all_used (self, p):
292
293        def recurse (p, all = []):
294            u = self.get_used (p)
295            if not p in range (self.packages): return []
296            t = [p]
297            for sub in u:
298                if not sub in t + all:
299                    t += recurse (sub, t + all)
300            return t
301
302        all = recurse (p, [])
303        return all
304
305       
306    #---------------------------------------------------------
307    def set_package_headers (self, name, i):
308
309        used = self.get_used (i)
310   
311        print '> create %s/%s/src/Lib%03d.hxx used=%s all=%s' % (self.project, name, i, used, all)
312
313        text = '''
314#ifndef __Lib%(p)03d_hxx__
315#define __Lib%(p)03d_hxx__
316// --------------------------------------
317''' % {"p":i}
318           
319        if len(used) > 0:
320            for u in used:
321                text += '#include <Lib%03d.hxx>\n' % u
322
323        text += '''
324
325#ifdef _MSC_VER
326#define DllExport __declspec( dllexport )
327#else
328#define DllExport
329#endif
330
331
332class DllExport C%(p)i
333{
334public:
335    C%(p)i ();
336    ~C%(p)i ();
337    void f();
338private:\n'''  % {"p":i}
339
340        if len(used) > 0:
341            for u in used:
342                text += '    C%(p)d o%(p)d;\n' % {"p":u}
343
344        text += '''};
345// --------------------------------------
346#endif
347
348'''
349
350        self.write_text (os.path.join (name, 'src', 'Lib%03d.hxx' % i), text)
351
352
353    #---------------------------------------------------------
354    def set_package_sources (self, name, i):
355
356        used = self.get_used (i)
357           
358        print '> create %s/%s/src/Lib%03d.cxx' % (self.project, name, i)
359
360        text = '''// --------------------------------------
361#include <iostream>
362#include <Lib%(p)03d.hxx>
363
364C%(p)d::C%(p)d ()
365{
366    std::cout << "Constructor C%(p)d" << std::endl;
367}
368
369C%(p)d::~C%(p)d ()
370{
371    std::cout << "Destructor C%(p)d" << std::endl;
372}
373
374void C%(p)d::f ()
375{
376std::cout << "C%(p)d.f" << std::endl;\n''' % {"p":i}
377
378        if len(used) > 0:
379            for k in used:
380                text += '    o%d.f();\n' % (k)
381
382        text += '''}
383// --------------------------------------
384'''
385
386        self.write_text (os.path.join (name, 'src', 'Lib%03d.cxx' % i), text)
387
388    #---------------------------------------------------------
389    def set_package_test (self, name, i):
390
391        used = self.get_used (i)
392           
393        print '> create %s/%s/src/test%03d.cxx' % (self.project, name, i)
394
395        text = '''// --------------------------------------
396#include <iostream>
397#include <Lib%(p)03d.hxx>
398
399int main ()
400{
401    C%(p)d o;
402
403    o.f ();
404}
405// --------------------------------------
406
407''' % {"p":i}
408
409        self.write_text (os.path.join (name, 'src', 'test%03d.cxx' % i), text)
410
411    #---------------------------------------------------------
412    def set_package_requirements (self, name, i):
413
414        used = self.get_used (i)
415           
416        print '> mkdir %s/%s/cmt' % (self.project, name)
417        os.mkdir (os.path.join (self.project, name, 'cmt'))
418
419        print '> create %s/%s/cmt/requirements' % (self.project, name)
420
421        text = '#-----------------\n'
422        if len(used) > 0:
423            for u in used:
424                text += 'use %s\n' % self.package_name (u)
425
426        all_used = self.get_all_used (i)
427
428        text += 'macro Lib%(p)03d_linkopts %(libs)s\n' % {"p":i,
429                                                          "libs":' '.join (['Lib%03d' % u for u in all_used[1:]])}
430
431        text += 'library Lib%(p)03d Lib%(p)03d.cxx\n' % {"p":i}
432
433        text += 'macro test%(p)03d_linkopts %(libs)s\n' % {"p":i,
434                                                           "libs":' '.join (['Lib%03d' % u for u in all_used])}
435        text += '''program test%(p)03d test%(p)03d.cxx
436#-----------------''' % {"p":i}
437           
438        print '> create %s/%s/cmt/requirements' % (self.project, name)
439
440        self.write_text (os.path.join (name, 'cmt', 'requirements'), text)
441
442    #---------------------------------------------------------
443    def set_package (self, i):
444        import random
445        #
446        # we select some used packages from [0 .. i-1]
447        #
448        used = []
449        if i > 1:
450            k = (i-1)/3
451            if k > 1:
452                used = random.sample (range (i-1), k)
453
454        self.used[i] = used
455
456        print 'used = %s' % used
457           
458        name = self.package_name (i)
459
460        print '> mkdir %s/%s' % (self.project, name)
461        os.mkdir (os.path.join (self.project, name))
462           
463        print '> mkdir %s/%s/src' % (self.project, name)
464        os.mkdir (os.path.join (self.project, name, 'src'))
465
466        self.set_package_headers (name, i)
467        self.set_package_sources (name, i)
468        self.set_package_test (name, i)
469        self.set_package_requirements (name, i)
470           
471
472    #---------------------------------------------------------
473    def set_packages (self):
474        for i in range(self.packages):
475            self.set_package (i)
476
477    #---------------------------------------------------------
478    def set_cmt_script (self):
479        text = '''
480import cmt
481
482top, out = cmt.init ()
483
484#---------------------------------------------------------
485def options (ctx):
486  cmt.options (ctx)
487
488#---------------------------------------------------------
489def configure (ctx):
490  cmt.configure (ctx)
491
492#---------------------------------------------------------
493def build (ctx):
494  cmt.build (ctx)
495
496'''
497
498        self.write_text ('wscript', text)
499
500   
501    #---------------------------------------------------------
502    def set_waf_script (self):
503        text = """
504import os, sys
505
506top = ''
507out = sys.platform
508
509#---------------------------------------------------------
510def options (ctx):
511    ctx.load ('compiler_cxx')
512    here = os.getcwd ()
513    default_prefix = os.path.join (here, 'installarea')
514    try:
515        print 'options'
516        ctx.add_option('--prefix',
517                        help    = "installation prefix (configuration only) [Default: '%s']" % default_prefix,
518                        default = default_prefix,
519                        dest    = 'prefix')
520    except:
521        print 'options error'
522        pass
523
524#---------------------------------------------------------
525def configure (ctx):
526    ctx.load ('compiler_cxx')
527
528#---------------------------------------------------------
529def package_name (i):
530        return 'Pack%03d' % i
531
532#---------------------------------------------------------
533def build (ctx):
534    if sys.platform == 'win32':
535        ctx.env.append_value ('CXXFLAGS','/EHsc')
536"""
537
538        for i in range(self.packages):
539            used = self.get_all_used (i)
540            shused = used[1:]
541
542            includes = "includes=[os.path.join (package_name(u), 'src') for u in %s]" % used
543
544            text += """
545
546    ctx.shlib(features = ['cxx','cxxshlib'],
547              source=os.path.join(package_name(%(p)d), 'src', 'Lib%(p)03d.cxx'),
548              %(includes)s,
549              use='%(shuse)s',
550              target='Lib%(p)03d')
551    ctx.program(source=os.path.join(package_name(%(p)d), 'src', 'test%(p)03d.cxx'),
552                %(includes)s,
553                use='%(use)s',
554                target='test%(p)03d')
555""" % {"p":i,
556       "shuse":' '.join (['Lib%03d' % u for u in shused]),
557       "use":' '.join (['Lib%03d' % u for u in used]),
558       "includes":includes}
559
560        self.write_text ('wscript', text)
561   
562    #---------------------------------------------------------
563    def set_wscript (self):
564        print '> create %s/wscript' % (self.project)
565
566        if self.mode == 'cmt':
567            self.set_cmt_script ()
568        elif self.mode == 'waf':
569            self.set_waf_script ()
570
571
572    #---------------------------------------------------------
573    def generate (self, project, packages):
574        self.project = project
575        self.packages = packages
576        self.cleanup_project ()
577        self.set_project_requirements ()
578        self.set_packages ()
579        self.set_wscript ()
580
581
582#-------------------------
583# Main class holding command interface
584#
585class CMTInterface:
586    #---------------------------------------------------------
587    def show_uses (self):
588        dtb.scan_packages ()
589        dtb.project.show_uses ()
590
591    #---------------------------------------------------------
592    def show_libraries (self):
593        dtb.show_libraries ()
594
595    #---------------------------------------------------------
596    def show_programs (self):
597        dtb.show_programs ()
598
599    #---------------------------------------------------------
600    def show_macros (self):
601        dtb.show_macros ()
602
603    #---------------------------------------------------------
604    def generate_project (self, project, packages, mode = 'cmt'):
605        here = os.getcwd ()
606       
607        # we may select mode='waf' or mode='cmt' (default is 'cmt')
608        generator = CMTProjectGenerator (mode) 
609        generator.generate (project, packages)
610
611       
612   
613Interface = CMTInterface ()
614
615
616#----------------------------------------------------------------------------------------------------------------------
617# Interface to waflib
618#----------------------------------------------------------------------------------------------------------------------
619
620
621#-------------------------
622# Generic init action
623#
624def init ():
625    #
626    # discovering the local contexte
627    #
628    here = os.getcwd ()
629    print 'here=' + here
630
631    #
632    # who am I (in terms of Project)
633    #
634    me = os.path.basename (here)
635    print 'me=' + me
636    p = os.path.dirname (here)
637    print 'p=' + p
638
639    #
640    # the current package produces its own entry in the CMTPATH
641    # ... as well its own use entry 
642    #
643    dtb.add_cmt_path (here)
644
645    build = 'any'
646    if sys.platform == 'win32':
647        build = 'win32'
648       
649    top = here
650    out = os.path.join (top, build)
651
652    return (top, out)
653
654
655#-------------------------
656# Generic options action
657#
658def options (ctx):
659    ctx.load ('compiler_cxx')
660
661    here = os.getcwd ()
662    default_prefix = os.path.join (here, 'installarea')
663    try:
664        print 'options'
665        ctx.add_option('--prefix',
666                        help    = "installation prefix (configuration only) [Default: '%s']" % default_prefix,
667                        default = default_prefix,
668                        dest    = 'prefix')
669    except:
670        print 'options error'
671        pass
672
673   
674#-------------------------
675# Generic configure action
676#
677def configure (ctx):
678    here = os.getcwd ()
679    me = os.path.basename (here)
680    p = os.path.dirname (here)
681    dtb.project = CMTPackage (ctx, me, p)
682
683    print 'cmt> configure in ' + ctx.path.abspath ()
684    ctx.load ('compiler_cxx')
685   
686    for path in dtb.paths:
687        print 'path: ' + path
688
689    print '------------ show:'
690    Interface.show_uses ()
691    Interface.show_libraries ()
692    Interface.show_programs ()
693    Interface.show_macros ()
694    print '------------'
695
696#-------------------------
697# Construct all build actions from all declared constituents from all used package
698#
699def build (ctx):
700    if 'CXXFLAGS' in dtb.macros:
701        ctx.env.append_value ('CXXFLAGS', dtb.macros['CXXFLAGS'])
702       
703    print 'cmt> project build in ' + ctx.path.abspath ()
704    u = dtb.project
705    u.build (ctx)
706
707
708if __name__ == "__main__":
709    print '----------init--------------'
710    top, out = init ()
711    print '----------------------------'
712
713    if len(sys.argv) > 1:
714        if re.match ('gencmt=(\d+)', sys.argv[1]):
715            m = re.match ('gencmt=(\d+)', sys.argv[1])
716            Interface.generate_project ('A', int(m.group(1)), 'cmt')
717        elif re.match ('genwaf=(\d+)', sys.argv[1]):
718            m = re.match ('genwaf=(\d+)', sys.argv[1])
719            Interface.generate_project ('A', int(m.group(1)), 'waf')
720        elif re.match ('os', sys.argv[1]):
721            print 'os=%s' % (os.name)
722        elif re.match ('platform', sys.argv[1]):
723            print 'platform=%s' % (sys.platform)
724    else:
725        print """
726cmt.py [gencmt|genwaf|os|platform]
727  gencmt=<n>
728  genwaf=<n>
729  os
730  platform
731"""