2 # mass-build - Easily Build Software Involving a Large Amount of Source Repositories
3 # Copyright (C) 2012-2013 Ralf Jung <post@ralfj.de>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 import vcs, build_system
20 import argparse, os, sys, subprocess
21 from collections import OrderedDict
24 def load_module(name, path):
25 import importlib.machinery
26 old_val = sys.dont_write_bytecode
27 sys.dont_write_bytecode = True
28 module = importlib.machinery.SourceFileLoader(name, path).load_module()
29 sys.dont_write_bytecode = old_val
34 def __init__(self, folder, config):
36 self.name = config['name']
38 vcsName = config['vcs']
40 self.vcs = vcs.Git(self.sourceFolder(), config)
41 elif vcsName == 'svn':
42 self.vcs = vcs.SVN(self.sourceFolder(), config['url'])
44 raise Exception("Unknown VCS type "+vcsName)
46 if config.get('buildDeb', False):
47 self.buildSystem = build_system.AutoDebuild(self.sourceFolder(), self.buildFolder(), config, self.vcs)
49 buildSystemName = config['buildSystem']
50 if buildSystemName == 'cmake':
51 self.buildSystem = build_system.CMake(self.sourceFolder(), self.buildFolder(), config)
53 raise Exception("Unknown build system type "+buildSystemName)
55 def sourceFolder(self):
56 return os.path.join(self.folder, self.name)
58 def buildFolder(self):
59 return os.path.join(config['buildDir'], self.sourceFolder())
61 # read command-line arguments
62 parser = argparse.ArgumentParser(description='Update and build a bunch of stuff')
63 parser.add_argument("-c, --config",
64 dest="config", default="mass-build.conf",
65 help="mass-build config file")
66 parser.add_argument("--reconfigure",
67 action="store_true", dest="reconfigure",
68 help="Force configuration to be run")
69 parser.add_argument("-w", "--wait-after-config",
70 action="store_true", dest="wait_after_config",
71 help="Wait for user confirmation after configuration is finished")
72 parser.add_argument("--reset-source",
73 action="store_true", dest="reset_source",
74 help="Reset sourcecode to the given version (removes local changes!)")
75 parser.add_argument("--no-update",
76 action="store_false", dest="update",
77 help="Do not update projects before compilation")
78 parser.add_argument("--resume-from", metavar='PROJECT',
80 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)")
81 parser.add_argument("--check-versions",
82 action="store_true", dest="version_check",
83 help="Check the repositories for newer tags, if possible (does not perform any building steps)")
84 parser.add_argument("projects", metavar='PROJECT', nargs='*',
85 help="Manually specify projects or folders to be built (project names take precedence)")
86 args = parser.parse_args()
87 if args.reset_source and not args.update:
88 raise Exception("Can not reset sources without doing an update")
90 # load config as dictionary
91 config = load_module('config', args.config).__dict__
93 # initialise variables holding the configuration
94 allProjects = OrderedDict() # all projects
95 allFolders = {} # all folders
96 workProjects = [] # projects we work on
98 # copy all items which don't exist below, except for those in the exclude list
99 def inherit(subConfig, superConfig, exclude = ('name', 'projects')):
100 for name in superConfig.keys():
101 if (not name in subConfig) and (not name in exclude):
102 subConfig[name] = superConfig[name]
104 # populate list of projects, return list of projects in that folder
105 def loadProjects(config, folder=''):
107 for projectConfig in config['projects']:
108 assert 'name' in projectConfig # everything must have a name
109 inherit(projectConfig, config)
110 if 'projects' in projectConfig: # a subpath
111 folderProjects += loadProjects(projectConfig, os.path.join(folder, projectConfig['name']))
112 else: # a proper project
113 if projectConfig['name'] in allProjects:
114 raise Exception("Duplicate project name "+projectConfig['name'])
115 project = Project(folder, projectConfig)
116 allProjects[projectConfig['name']] = project
117 folderProjects.append(project)
118 # store projects of this folder
119 if folder in allFolders:
120 raise Exception("Duplicate folder name "+folder)
121 allFolders[folder] = folderProjects
122 return folderProjects
124 # load available projects
126 # get base set og projects to process
128 for name in args.projects:
129 if name in allProjects:
130 workProjects.append(allProjects[name])
131 elif name in allFolders:
132 workProjects += allFolders[name]
134 raise Exception("Project or folder %s does not exist" % name)
136 workProjects = list(allProjects.values()) # all the projects
137 # apply the "resume from"
138 if args.resume_from is not None:
141 while startIndex < len(workProjects):
142 if workProjects[startIndex].name == args.resume_from:
146 if startIndex >= len(workProjects): # project not found
147 raise Exception("%s not found in list of projects to work on" % args.resume_from)
149 workProjects = workProjects[startIndex:]
152 for project in workProjects:
154 if args.version_check:
155 print("Checking project",project.sourceFolder())
156 project.vcs.checkVersions()
159 print("Updating project",project.sourceFolder())
160 project.vcs.update(mode = vcs.MODE_RESET if args.reset_source else vcs.MODE_REBASE)
161 print("Building project",project.sourceFolder())
162 project.buildSystem.build(reconfigure=args.reconfigure, waitAfterConfig=args.wait_after_config)
164 except (subprocess.CalledProcessError, KeyboardInterrupt) as e: # for some exceptions, a stackrace is usually pointless
165 print(file=sys.stderr)
166 print(file=sys.stderr)
167 if isinstance(e, KeyboardInterrupt): # str(e) would be the empty string
168 print("Interruped by user while processing %s" % (project.name), file=sys.stderr)
170 print("Error while processing %s: %s" % (project.name, str(e)), file=sys.stderr)
171 print(file=sys.stderr)
173 print("All operations successfully completed")