b53ab53ee2c229335fa231a3d2defd93998e14d5
[mass-build.git] / mass_build.py
1 #!/usr/bin/python
2 import vcs, build_system, imp
3 import argparse, os, sys, subprocess
4 from collections import OrderedDict
5
6 # an entire Project
7 class Project:
8         def __init__(self, folder, projectConfig, globalConfig):
9                 self.folder = folder
10                 self.name = projectConfig['name']
11                 # VCS
12                 vcsName = projectConfig['vcs']
13                 if vcsName == 'git':
14                         self.vcs = vcs.Git(self.sourceFolder(), projectConfig['url'], projectConfig['version'])
15                 elif vcsName == 'svn':
16                         self.vcs = vcs.SVN(self.sourceFolder(), projectConfig['url'], projectConfig.get('versionName'))
17                 else:
18                         raise Exception("Unknown VCS type "+vcsName)
19                 # build system
20                 if globalConfig.get('buildDeb', False):
21                         self.buildSystem = build_system.AutoDebuild(self.sourceFolder(), self.buildFolder(), projectConfig, self.vcs, globalConfig)
22                 else:
23                         buildSystemName = projectConfig['buildSystem']
24                         if buildSystemName == 'cmake':
25                                 self.buildSystem = build_system.CMake(self.sourceFolder(), self.buildFolder(), projectConfig, globalConfig)
26                         else:
27                                 raise Exception("Unknown build system type "+buildSystemName)
28         
29         def sourceFolder(self):
30                 return os.path.join(self.folder, self.name)
31         
32         def buildFolder(self):
33                 return os.path.join(config['buildDir'], self.sourceFolder())
34
35 # read command-line arguments
36 parser = argparse.ArgumentParser(description='Update and build a bunch of stuff')
37 parser.add_argument("-c, --config",
38                     dest="config", default="mass-build.conf",
39                     help="mass-build config file")
40 parser.add_argument("--reconfigure",
41                     action="store_true", dest="reconfigure",
42                     help="Force configuration to be run")
43 parser.add_argument("--reset-source",
44                     action="store_true", dest="reset_source",
45                     help="Reset sourcecode to the given version (removes local changes!)")
46 parser.add_argument("--no-update",
47                     action="store_false", dest="update",
48                     help="Do not update projects before compilation")
49 parser.add_argument("--resume-from", metavar='PROJECT',
50                     dest="resume_from",
51                     help="Resume building from the given project")
52 parser.add_argument("projects",  metavar='PROJECT', nargs='*',
53                     help="Manually specify projects or folders to be built (project names take precedence)")
54 args = parser.parse_args()
55 # sanitize
56 if args.reset_source and not args.update:
57         raise Exception("When no update is performed, no reset to the given version can be done either")
58
59 # load config
60 config = imp.load_source('config', args.config).__dict__
61 os.remove(args.config+'c') # remove compiled python file
62 allProjects = OrderedDict() # all projects
63 allFolders = {} # all folders
64 workProjects = [] # projects we work on
65
66 # return the position of the given item in the list
67 def findInList(list, item):
68         for i in xrange(len(list)):
69                 if list[i] == item:
70                         return i
71         raise Exception("%s not found in list" % str(item))
72
73 # populate list of projects, return list of projects in that folder
74 def loadProjects(projects, folder=''):
75         folderProjects = []
76         for projectConfig in projects:
77                 if 'folder' in projectConfig: # a subpath
78                         folderProjects += loadProjects(projectConfig['projects'], os.path.join(folder, projectConfig['folder']))
79                 else: # a proper project
80                         if projectConfig['name'] in allProjects:
81                                 raise Exception("Duplicate project name "+project['name'])
82                         project = Project(folder, projectConfig, config)
83                         allProjects[projectConfig['name']] = project
84                         folderProjects.append(project)
85         # store projects of this folder
86         if folder in allFolders:
87                 raise Exception("Duplicate folder name "+folder)
88         allFolders[folder] = folderProjects
89         return folderProjects
90
91 # now check what we have to do
92 loadProjects(config['projects'])
93 if args.projects:
94         if args.resume_from is not None:
95                 raise Exception("Can not use --resume-from and manually specify projects")
96         for name in args.projects:
97                 if name in allProjects:
98                         workProjects.append(allProjects[name])
99                 elif name in allFolders:
100                         workProjects += allFolders[name]
101                 else:
102                         raise Exception("Project or folder%s does not exist" % name)
103 elif args.resume_from is None:
104         workProjects = projects.values() # all the projects
105 else:
106         if not args.resume_from in allProjects:
107                 raise Exception("Project %s does not exist" % args.resume_from)
108         startWith = allProjects[args.resume_from]
109         startIndex = findInList(allProjects.values(), startWith)
110         workProjects = allProjects.values()[startIndex:]
111
112 # and do it!
113 for project in workProjects:
114         try:
115                 if args.update:
116                         print "Updating project",project.sourceFolder()
117                         project.vcs.update(forceVersion=args.reset_source)
118                 print "Building project",project.sourceFolder()
119                 project.buildSystem.build(reconfigure=args.reconfigure)
120                 print
121         except (subprocess.CalledProcessError, KeyboardInterrupt) as e: # for some exceptions, a stackrace is usually pointless
122                 print >> sys.stderr
123                 print >> sys.stderr
124                 if isinstance(e, KeyboardInterrupt): # str(e) would be the empty string
125                         print >> sys.stderr, "Interruped by user while processing %s" % (project.name)
126                 else:
127                         print >> sys.stderr, "Error while processing %s: %s" % (project.name, str(e))
128                 print >> sys.stderr
129                 sys.exit(1)