1import os 2import shutil 3import subprocess 4 5import infra 6 7 8class Builder(object): 9 def __init__(self, config, builddir, logtofile, jlevel=None): 10 self.config = '\n'.join([line.lstrip() for line in 11 config.splitlines()]) + '\n' 12 self.builddir = builddir 13 self.logfile = infra.open_log_file(builddir, "build", logtofile) 14 self.jlevel = jlevel 15 16 def is_defconfig_valid(self, configfile, defconfig): 17 """Check if the .config is contains all lines present in the defconfig.""" 18 with open(configfile) as configf: 19 configlines = configf.readlines() 20 21 defconfiglines = defconfig.split("\n") 22 23 # Check that all the defconfig lines are still present 24 for defconfigline in defconfiglines: 25 if defconfigline + "\n" not in configlines: 26 self.logfile.write("WARN: defconfig can't be used\n") 27 self.logfile.write(" Missing: %s\n" % defconfigline.strip()) 28 self.logfile.flush() 29 return False 30 31 return True 32 33 def configure(self, make_extra_opts=[], make_extra_env={}): 34 """Configure the build. 35 36 make_extra_opts: a list of arguments to be passed to the make 37 command. 38 e.g. make_extra_opts=["BR2_EXTERNAL=/path"] 39 40 make_extra_env: a dict of variables to be appended (or replaced) 41 in the environment that calls make. 42 e.g. make_extra_env={"BR2_DL_DIR": "/path"} 43 """ 44 if not os.path.isdir(self.builddir): 45 os.makedirs(self.builddir) 46 47 config_file = os.path.join(self.builddir, ".config") 48 with open(config_file, "w+") as cf: 49 cf.write(self.config) 50 # dump the defconfig to the logfile for easy debugging 51 self.logfile.write("> start defconfig\n" + self.config + 52 "> end defconfig\n") 53 self.logfile.flush() 54 55 env = { 56 "PATH": os.environ["PATH"], 57 "HOME": os.environ["HOME"] 58 } 59 env.update(make_extra_env) 60 61 cmd = ["make", 62 "O={}".format(self.builddir)] 63 cmd += make_extra_opts 64 cmd += ["olddefconfig"] 65 66 ret = subprocess.call(cmd, stdout=self.logfile, stderr=self.logfile, 67 cwd=infra.basepath(), env=env) 68 if ret != 0: 69 raise SystemError("Cannot olddefconfig") 70 71 if not self.is_defconfig_valid(config_file, self.config): 72 raise SystemError("The defconfig is not valid") 73 74 def build(self, make_extra_opts=[], make_extra_env={}): 75 """Perform the build. 76 77 make_extra_opts: a list of arguments to be passed to the make 78 command. It can include a make target. 79 e.g. make_extra_opts=["foo-source"] 80 81 make_extra_env: a dict of variables to be appended (or replaced) 82 in the environment that calls make. 83 e.g. make_extra_env={"BR2_DL_DIR": "/path"} 84 """ 85 env = { 86 "PATH": os.environ["PATH"], 87 "HOME": os.environ["HOME"] 88 } 89 90 if "http_proxy" in os.environ: 91 self.logfile.write("Using system proxy: " + 92 os.environ["http_proxy"] + "\n") 93 env['http_proxy'] = os.environ["http_proxy"] 94 env['https_proxy'] = os.environ["http_proxy"] 95 env.update(make_extra_env) 96 97 cmd = ["make", "-C", self.builddir] 98 if "BR2_PER_PACKAGE_DIRECTORIES=y" in self.config.splitlines() and self.jlevel: 99 cmd.append(f"-j{self.jlevel}") 100 cmd += make_extra_opts 101 102 ret = subprocess.call(cmd, stdout=self.logfile, stderr=self.logfile, 103 env=env) 104 if ret != 0: 105 raise SystemError("Build failed") 106 107 open(self.stamp_path(), 'a').close() 108 109 def stamp_path(self): 110 return os.path.join(self.builddir, "build-done") 111 112 def is_finished(self): 113 return os.path.exists(self.stamp_path()) 114 115 def delete(self): 116 if os.path.exists(self.builddir): 117 shutil.rmtree(self.builddir) 118