taler-deployment

Deployment scripts and configuration files
Log | Files | Refs | README

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()