ee813639022b7b59e2adaf073177889b90235ac3
[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, config):
9                 self.folder = folder
10                 self.name = config['name']
11                 # VCS
12                 vcsName = config['vcs']
13                 if vcsName == 'git':
14                         self.vcs = vcs.Git(self.sourceFolder(), config['url'], config['version'])
15                 elif vcsName == 'svn':
16                         self.vcs = vcs.SVN(self.sourceFolder(), config['url'])
17                 else:
18                         raise Exception("Unknown VCS type "+vcsName)
19                 # build system
20                 if config.get('buildDeb', False):
21                         self.buildSystem = build_system.AutoDebuild(self.sourceFolder(), self.buildFolder(), config, self.vcs)
22                 else:
23                         buildSystemName = config['buildSystem']
24                         if buildSystemName == 'cmake':
25                                 self.buildSystem = build_system.CMake(self.sourceFolder(), self.buildFolder(), config)
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("--wait-after-config",
44                     action="store_true", dest="wait_after_config",
45                     help="Wait for user confirmation after configuration is finished")
46 parser.add_argument("--reset-source",
47                     action="store_true", dest="reset_source",
48                     help="Reset sourcecode to the given version (removes local changes!)")
49 parser.add_argument("--no-update",
50                     action="store_false", dest="update",
51                     help="Do not update projects before compilation")
52 parser.add_argument("--resume-from", metavar='PROJECT',
53                     dest="resume_from",
54                     help="From the projects specified, continue building with this one (i.e., remove all projects before this one from the list - this never adds new projects)")
55 parser.add_argument("projects",  metavar='PROJECT', nargs='*',
56                     help="Manually specify projects or folders to be built (project names take precedence)")
57 args = parser.parse_args()
58 # sanitize
59 if args.reset_source and not args.update:
60         raise Exception("When no update is performed, no reset to the given version can be done either")
61
62 # load config
63 config = imp.load_source('config', args.config).__dict__
64 os.remove(args.config+'c') # remove compiled python file
65 allProjects = OrderedDict() # all projects
66 allFolders = {} # all folders
67 workProjects = [] # projects we work on
68
69 # copy all items which don't exist below, except for those in the exclude list
70 def inherit(subConfig, superConfig, exclude = ('name', 'projects')):
71         for name in superConfig.keys():
72                 if (not name in subConfig) and (not name in exclude):
73                         subConfig[name] = superConfig[name]
74
75 # populate list of projects, return list of projects in that folder
76 def loadProjects(config, folder=''):
77         folderProjects = []
78         for projectConfig in config['projects']:
79                 assert 'name' in projectConfig # everything must have a name
80                 inherit(projectConfig, config)
81                 if 'projects' in projectConfig: # a subpath
82                         folderProjects += loadProjects(projectConfig, os.path.join(folder, projectConfig['name']))
83                 else: # a proper project
84                         if projectConfig['name'] in allProjects:
85                                 raise Exception("Duplicate project name "+projectConfig['name'])
86                         project = Project(folder, projectConfig)
87                         allProjects[projectConfig['name']] = project
88                         folderProjects.append(project)
89         # store projects of this folder
90         if folder in allFolders:
91                 raise Exception("Duplicate folder name "+folder)
92         allFolders[folder] = folderProjects
93         return folderProjects
94
95 # load available projects
96 loadProjects(config)
97 # get base set og projects to process
98 if args.projects:
99         for name in args.projects:
100                 if name in allProjects:
101                         workProjects.append(allProjects[name])
102                 elif name in allFolders:
103                         workProjects += allFolders[name]
104                 else:
105                         raise Exception("Project or folder %s does not exist" % name)
106 else:
107         workProjects = allProjects.values() # all the projects
108 # apply the "resume from"
109 if args.resume_from is not None:
110         # find project index
111         startIndex = 0
112         while startIndex < len(workProjects):
113                 if workProjects[startIndex].name == args.resume_from:
114                         break # we found it
115                 else:
116                         startIndex += 1
117         if startIndex >= len(workProjects): # project not found
118                 raise Exception("%s not found in list of projects to work on" % args.resume_from)
119         # start here
120         workProjects = workProjects[startIndex:]
121
122 # and do it!
123 for project in workProjects:
124         try:
125                 if args.update:
126                         print "Updating project",project.sourceFolder()
127                         project.vcs.update(forceVersion=args.reset_source)
128                 print "Building project",project.sourceFolder()
129                 project.buildSystem.build(reconfigure=args.reconfigure, waitAfterConfig=args.wait_after_config)
130                 print
131         except (subprocess.CalledProcessError, KeyboardInterrupt) as e: # for some exceptions, a stackrace is usually pointless
132                 print >> sys.stderr
133                 print >> sys.stderr
134                 if isinstance(e, KeyboardInterrupt): # str(e) would be the empty string
135                         print >> sys.stderr, "Interruped by user while processing %s" % (project.name)
136                 else:
137                         print >> sys.stderr, "Error while processing %s: %s" % (project.name, str(e))
138                 print >> sys.stderr
139                 sys.exit(1)
140 print "All operations successfully completed"