Add Packer (Docker/Vagrant) build def for ODL 4.3
[integration/packaging.git] / rpm / build.py
1 #!/usr/bin/env python
2 """Build OpenDaylight's RPMs using YAML build configs and Jinja2 templates."""
3
4 import os
5 import sys
6 import argparse
7 import shutil
8 import subprocess
9 from string import Template
10
11 try:
12     import yaml
13 except ImportError:
14     sys.stderr.write("We recommend using our included Vagrant env.\n")
15     sys.stderr.write("Else, do `pip install -r requirements.txt` in a venv.\n")
16     raise
17
18 import cache.cache as cache
19 import specs.build_specs as build_specs
20
21 # Common paths used in this script
22 # This file is assumed to be in the root of the RPM build logic's dir structure
23 project_root = os.path.dirname(os.path.abspath(__file__))
24 cache_dir = os.path.join(project_root, "cache")
25 specs_dir = os.path.join(project_root, "specs")
26 rpmbuild_dir = os.path.join(os.path.expanduser("~"), "rpmbuild")
27 src_in_dir = os.path.join(rpmbuild_dir, "SOURCES")
28 spec_in_dir = os.path.join(rpmbuild_dir, "SPECS")
29 srpm_out_dir = os.path.join(rpmbuild_dir, "SRPMS")
30 rpm_out_dir = os.path.join(rpmbuild_dir, "RPMS", "noarch")
31
32 # Templates that can be specialized into common artifact names per-build
33 odl_template = Template("opendaylight-$version_major.$version_minor."
34                         "$version_patch-$rpm_release.tar.gz")
35 specfile_template = Template("opendaylight-$version_major.$version_minor."
36                              "$version_patch-$rpm_release.spec")
37 unitfile_tb_template = Template("opendaylight-$sysd_commit.service.tar.gz")
38 rpm_template = Template("opendaylight-$version_major.$version_minor."
39                         "$version_patch-$rpm_release.el7.noarch.rpm")
40 srpm_template = Template("opendaylight-$version_major.$version_minor."
41                          "$version_patch-$rpm_release.el7.src.rpm")
42
43
44 def build_rpm(build):
45     """Build the RPMs described by the given build description.
46
47     :param build: Description of an RPM build, typically from build_vars.yaml
48     :type build: dict
49
50     """
51     # Specialize a series of name templates for the given build
52     odl_tarball = odl_template.substitute(build)
53     odl_rpm = rpm_template.substitute(build)
54     odl_srpm = srpm_template.substitute(build)
55     odl_specfile = specfile_template.substitute(build)
56     unitfile_tarball = unitfile_tb_template.substitute(build)
57
58     # After building strings from the name templates, build their full path
59     odl_tarball_path = os.path.join(cache_dir, odl_tarball)
60     unitfile_tarball_path = os.path.join(cache_dir, unitfile_tarball)
61     specfile_path = os.path.join(specs_dir, odl_specfile)
62     spec_in_path = os.path.join(spec_in_dir, odl_specfile)
63     rpm_out_path = os.path.join(rpm_out_dir, odl_rpm)
64     srpm_out_path = os.path.join(srpm_out_dir, odl_srpm)
65
66     # Call a helper function to cache the artifacts required for each build
67     cache.cache_build(build)
68
69     # Call helper script to build the required RPM .spec files
70     build_specs.build_spec(build)
71
72     # Clean up old rpmbuild dir structure if it exists
73     if os.path.isdir(rpmbuild_dir):
74         shutil.rmtree(rpmbuild_dir)
75
76     # Create rpmbuild dir structure
77     subprocess.call("rpmdev-setuptree")
78
79     # Move unitfile, tarball and specfile to correct rpmbuild dirs
80     shutil.copy(odl_tarball_path, src_in_dir)
81     shutil.copy(unitfile_tarball_path, src_in_dir)
82     shutil.copy(specfile_path, spec_in_dir)
83
84     # Call rpmbuild, build both SRPMs/RPMs
85     subprocess.call(["rpmbuild", "-ba", spec_in_path])
86
87     # Copy the RPMs/SRPMs from their output dir to the cache dir
88     shutil.copy(rpm_out_path, cache_dir)
89     shutil.copy(srpm_out_path, cache_dir)
90
91
92 # When run as a script, accept a set of builds and execute them
93 if __name__ == "__main__":
94     # Load RPM build variables from a YAML config file
95     build_vars_path = os.path.join(project_root, "build_vars.yaml")
96     with open(build_vars_path) as rpm_var_file:
97         build_vars = yaml.load(rpm_var_file)
98
99     # Accept the version(s) of the build(s) to perform as args
100     # TODO: More docs on ArgParser and argument
101     parser = argparse.ArgumentParser()
102     existing_build_group = parser.add_argument_group("Existing build")
103     existing_build_group.add_argument(
104         "-v", "--version", action="append", metavar="major minor patch rpm",
105         nargs="*", help="RPM version(s) to build"
106     )
107     new_build_group = parser.add_argument_group("New build")
108     new_build_group.add_argument("--major", help="Major (element) version to build")
109     new_build_group.add_argument("--minor", help="Minor (SR) version to build")
110     new_build_group.add_argument("--patch", help="Patch version to build")
111     new_build_group.add_argument("--rpm",   help="RPM version to build")
112     new_build_group.add_argument("--sysd_commit", help="Version of ODL unitfile to package")
113     new_build_group.add_argument("--codename", help="Codename for ODL version")
114     new_build_group.add_argument("--download_url", help="Tarball to repackage into RPM")
115     new_build_group.add_argument("--changelog_date", help="Date this RPM was defined")
116     new_build_group.add_argument("--changelog_name", help="Name of person who defined RPM")
117     new_build_group.add_argument("--changelog_email", help="Email of person who defined RPM")
118
119     # Print help if no arguments are given
120     if len(sys.argv) == 1:
121         parser.print_help()
122         sys.exit(1)
123
124     # Parse the given args
125     args = parser.parse_args()
126
127     # Build list of RPM builds to perform
128     builds = []
129     if args.version:
130         # Build a list of requested versions as dicts of version components
131         versions = []
132         version_keys = ["version_major", "version_minor", "version_patch",
133                         "rpm_release"]
134         # For each version arg, match all version components to build_vars name
135         for version in args.version:
136             versions.append(dict(zip(version_keys, version)))
137
138         # Find every RPM build that matches any version argument
139         # A passed version "matches" a build when the provided version
140         # components are a subset of the version components of a build. Any
141         # version components that aren't passed are simply not checked, so
142         # they can't fail the match, effectively wild-carding them.
143         for build in build_vars["builds"]:
144             for version in versions:
145                 # Converts both dicts' key:value pairs to lists of tuples and
146                 # checks that each tuple in the version list is present in the
147                 # build list.
148                 if all(item in build.items() for item in version.items()):
149                     builds.append(build)
150     else:
151         builds.append({"version_major": args.major,
152                        "version_minor": args.minor,
153                        "version_patch": args.patch,
154                        "rpm_release": args.rpm,
155                        "sysd_commit": args.sysd_commit,
156                        "codename": args.codename,
157                        "download_url": args.download_url,
158                        "changelog_date": args.changelog_date,
159                        "changelog_name": args.changelog_name,
160                        "changelog_email": args.changelog_email})
161
162     for build in builds:
163         build_rpm(build)