generic (7646B)
1 #!/usr/bin/env python3 2 # This file is in the public domain. 3 # Helper script to build the latest DEB packages in the container. 4 5 import os 6 import sys 7 import subprocess 8 import glob 9 import shutil 10 import shlex 11 12 def run_cmd(cmd, shell=False, cwd=None, env=None): 13 """Helper to run commands and exit on failure (mimicking set -e).""" 14 # If specific env vars are passed, update the current env, otherwise use current 15 command_env = os.environ.copy() 16 if env: 17 command_env.update(env) 18 19 # Flush stdout so logs appear in order 20 sys.stdout.flush() 21 22 subprocess.check_call(cmd, shell=shell, cwd=cwd, env=command_env) 23 24 def get_output(cmd, shell=False, cwd=None): 25 """Helper to get command output as string.""" 26 return subprocess.check_output(cmd, shell=shell, cwd=cwd, text=True).strip() 27 28 def make_version(deb_version, build_codename): 29 if "-" in deb_version: 30 # We already have a dash, thus a debian version. 31 # Append out build_codename to that. 32 return f"{deb_version}+{build_codename}" 33 # We don't have a debian version yet, start at zero, because by convention, 34 # real / manually created debian release versions start at 1. 35 return f"{deb_version}-0+{build_codename}" 36 37 38 def main(): 39 if 'LD_LIBRARY_PATH' in os.environ: 40 del os.environ['LD_LIBRARY_PATH'] 41 42 # Arguments 43 if len(sys.argv) < 3: 44 print("Usage: script.py <PACKAGE> <CODENAME> <ARCH>", file=sys.stderr) 45 sys.exit(1) 46 47 PACKAGE = sys.argv[1] 48 CODENAME = sys.argv[2] 49 ARCH = sys.argv[3] 50 51 # Path of the debian/ folder in the repository 52 DEBIANPATH = "" 53 debpath_file = f"/buildconfig/{PACKAGE}.debpath" 54 if os.path.exists(debpath_file): 55 with open(debpath_file, 'r') as f: 56 DEBIANPATH = f.read().strip() 57 58 print(f"Building {PACKAGE} with generic build logic", file=sys.stderr) 59 60 with open(f"/buildconfig/{PACKAGE}.tag", 'r') as f: 61 TAG = f.read().strip() 62 63 # 1. Setup Local Repo 64 os.chdir("/pkgdir") 65 # Using shell=True here to easily handle the pipe logic 66 run_cmd("dpkg-scanpackages . | xz - > /pkgdir/Packages.xz", shell=True) 67 68 with open("/etc/apt/sources.list.d/taler-packaging-local.list", "w") as f: 69 f.write("deb [trusted=yes] file:/pkgdir ./\n") 70 71 run_cmd(["apt-get", "update"]) 72 73 # 2. Prepare Build Directory 74 if not os.path.exists("/build"): 75 os.makedirs("/build") 76 os.chdir("/build") 77 78 with open(f"/buildconfig/{PACKAGE}.giturl", 'r') as f: 79 GITURL = f.read().strip() 80 81 run_cmd(["git", "config", "--global", "advice.detachedHead", "false"]) 82 run_cmd(["git", "clone", "--depth=1", f"--branch={TAG}", GITURL, PACKAGE]) 83 84 build_pkg_path = os.path.join("/build", PACKAGE, DEBIANPATH) 85 os.chdir(build_pkg_path) 86 87 # Get current version 88 DEB_VERSION = get_output(["dpkg-parsechangelog", "-S", "Version"]) 89 print(f"Current version of {PACKAGE}/{DEBIANPATH} is {DEB_VERSION}") 90 91 # 3. Bootstrap and Install Deps 92 os.chdir(os.path.join("/build", PACKAGE)) 93 run_cmd(["./bootstrap"]) 94 95 os.chdir(build_pkg_path) 96 97 # Install build-time dependencies 98 # Note: Passing the tool command as a single string because it contains spaces/options 99 tool_cmd = "apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes" 100 run_cmd(["mk-build-deps", "--install", f"--tool={tool_cmd}", "debian/control"]) 101 102 # Sparse checkout hint 103 with open(".version", "w") as f: 104 f.write(f"{DEB_VERSION}\n") 105 106 # 4. Configure Environment for Build 107 deb_dbg_repo = "debian/.debhelper/" 108 os.environ["DEB_DBG_SYMBOLS_REPO"] = deb_dbg_repo 109 os.makedirs(deb_dbg_repo, exist_ok=True) 110 111 # touch command 112 open(os.path.join(deb_dbg_repo, "debian-symbols-pool"), 'a').close() 113 114 os.environ["DEB_BUILD_MAINT_OPTIONS"] = "debug" 115 116 # Add a codename-specific tag to the package. 117 # We need this tag as reprepro doesn't support multiple packages (even for 118 # different codenames like trixie/bookworm) per repo. 119 # Examples of how it should work: 120 # 1.2.3 => 1.2.3-0~trixie 121 # 1.2.3-2 => 1.2.3-2~trixie 122 # What we must not do: 123 # WRONG: 1.2.3 => 1.2.3~trixie 124 # Because 1.2.3~trixie <= 1.2.3, version requirements in dependencies would 125 # not work. 126 127 out_deb_version = make_version(DEB_VERSION, CODENAME) 128 129 print(f"Building {PACKAGE} as version {out_deb_version}", file=sys.stderr) 130 131 # Add distro/codename specific version trailer 132 if CODENAME: 133 env_vars = { 134 "DEBEMAIL": "deb@taler.net", 135 "NAME": "Taler Packaging Team" 136 } 137 run_cmd([ 138 "dch", "-b", 139 "--distribution", "unstable", 140 "--newversion", out_deb_version, 141 f"Build for {CODENAME}" 142 ], env=env_vars) 143 144 # 5. Build Package 145 run_cmd(["dpkg-buildpackage", "-rfakeroot", "-b", "-uc", "-us"]) 146 147 # Copy artifacts 148 # Globs for ../*.deb and ../*.ddeb relative to current dir 149 deb_files = glob.glob("../*.deb") 150 ddeb_files = glob.glob("../*.ddeb") 151 152 for f in deb_files: 153 shutil.copy(f, "/pkgdir/") 154 155 for f in ddeb_files: 156 shutil.copy(f, "/pkgdir/") 157 158 # Save built filenames 159 built_current_path = f"/pkgdir/{PACKAGE}@{ARCH}.built.current" 160 with open(built_current_path, 'w') as f: 161 for deb in deb_files: 162 f.write(os.path.basename(deb) + "\n") 163 for ddeb in ddeb_files: 164 f.write(os.path.basename(ddeb) + "\n") 165 166 # 6. Testing 167 # Re-scan 168 os.chdir("/pkgdir") 169 run_cmd("dpkg-scanpackages . | xz - > /pkgdir/Packages.xz", shell=True) 170 171 with open("/etc/apt/sources.list.d/taler-packaging-local.list", "w") as f: 172 f.write("deb [trusted=yes] file:/pkgdir ./\n") 173 174 run_cmd(["apt-get", "update"]) 175 176 print(f"Installing built packages from {build_pkg_path}/..", file=sys.stderr) 177 178 # Since we are in /pkgdir, and files were copied there, we glob current dir. 179 pkgdir_debs = glob.glob(f"{build_pkg_path}/../*.deb") 180 if pkgdir_debs: 181 run_cmd(["apt", "install", "-y"] + pkgdir_debs) 182 183 # Check Binaries 184 # Iterate over original source locations (copied from logic `../*.deb`) 185 # But since we are in /pkgdir now, let's use the files we just identified. 186 for deb in pkgdir_debs: 187 # Get contents: dpkg --contents 188 contents = get_output(["dpkg", "--contents", deb]) 189 190 for line in contents.splitlines(): 191 parts = line.split() 192 if len(parts) < 6: 193 print("failed to read package contents", file=sys.stderr) 194 sys.exit(3) 195 196 # dpkg --contents output format looks like: 197 # drwxr-xr-x root/root 0 2023-01-01 12:00 ./usr/bin/ 198 fname = parts[5] 199 200 if "bin" not in fname: 201 continue 202 203 # Convert to absolute path 204 if fname.startswith("./"): 205 fname = fname[2:] 206 if not fname.startswith("/"): 207 fname = "/" + fname 208 209 # Check if ELF executable 210 file_info = get_output(["file", fname]) 211 if "ELF" in file_info and "executable" in file_info: 212 print(f"checking {fname}") 213 try: 214 run_cmd(["ldd", fname]) 215 except subprocess.CalledProcessError: 216 print(f"Installed binary {fname} has linker issue") 217 sys.exit(2) 218 219 # Finalize tag 220 with open(f"/pkgdir/{PACKAGE}@{ARCH}.built.tag", "w") as f: 221 f.write(TAG + "\n") 222 223 if __name__ == "__main__": 224 main()