1 ##############################################################################
2 # Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others.
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
18 from apex.common import utils
20 CACHE_JOURNAL = 'cache_journal.yaml'
23 BUILD_LOG_FILE = './apex_build.log'
26 class ApexBuildException(Exception):
30 def create_build_parser():
31 build_parser = argparse.ArgumentParser()
32 build_parser.add_argument('--debug', action='store_true', default=False,
33 help="Turn on debug messages")
34 build_parser.add_argument('-l', '--log-file',
35 default=BUILD_LOG_FILE,
36 dest='log_file', help="Log file to log to")
37 build_parser.add_argument('-c', '--cache-dir',
40 help='Directory to store cache')
41 build_parser.add_argument('--iso', action='store_true',
43 help='Build ISO image')
44 build_parser.add_argument('--rpms', action='store_true',
47 build_parser.add_argument('-r', '--release',
49 help='Version to apply to build '
55 def get_journal(cache_dir):
57 Search for the journal file and returns its contents
58 :param cache_dir: cache storage directory where journal file is
59 :return: content of journal file
61 journal_file = "{}/{}".format(cache_dir, CACHE_JOURNAL)
62 if os.path.isfile(journal_file) is False:
63 logging.info("Journal file not found {}, skipping cache search".format(
66 with open(journal_file, 'r') as fh:
67 cache_journal = yaml.safe_load(fh)
68 assert isinstance(cache_journal, list)
72 def get_cache_file(cache_dir):
74 Searches for a valid cache entry in the cache journal
75 :param cache_dir: directory where cache and journal are located
76 :return: name of valid cache file
78 cache_journal = get_journal(cache_dir)
79 if cache_journal is not None:
80 valid_cache = cache_journal[-1]
81 if os.path.isfile(valid_cache):
85 def unpack_cache(cache_dest, cache_dir=None):
87 logging.info("Cache directory not provided, skipping cache unpack")
89 elif os.path.isdir(cache_dir) is False:
90 logging.info("Cache Directory does not exist, skipping cache unpack")
93 logging.info("Cache Directory Found: {}".format(cache_dir))
94 cache_file = get_cache_file(cache_dir)
95 if cache_file is None:
96 logging.info("No cache file detected, skipping cache unpack")
98 logging.info("Unpacking Cache {}".format(cache_file))
99 if not os.path.exists(cache_dest):
100 os.makedirs(cache_dest)
102 subprocess.check_call(["tar", "xvf", cache_file, "-C", cache_dest])
103 except subprocess.CalledProcessError:
104 logging.warning("Cache unpack failed")
106 logging.info("Cache unpacked, contents are: {}".format(
107 os.listdir(cache_dest)))
110 def build(build_root, version, iso=False, rpms=False):
112 logging.warning("iso is deprecated. Will not build iso and build rpm "
114 make_targets = ['rpm']
116 make_targets = ['rpm']
118 logging.warning("Nothing specified to build, and images are no "
119 "longer supported in Apex. Will only run rpm check")
120 make_targets = ['rpm-check']
121 if version is not None:
122 make_args = ['RELEASE={}'.format(version)]
125 logging.info('Running make clean...')
127 subprocess.check_call(['make', '-C', build_root, 'clean'])
128 except subprocess.CalledProcessError:
129 logging.error('Failure to make clean')
131 logging.info('Building targets: {}'.format(make_targets))
133 output = subprocess.check_output(["make"] + make_args + ["-C",
134 build_root] + make_targets)
136 except subprocess.CalledProcessError as e:
137 logging.error("Failed to build Apex artifacts")
138 logging.error(e.output)
142 def build_cache(cache_source, cache_dir):
144 Tar up new cache with unique name and store it in cache storage
145 directory. Also update journal file with new cache entry.
146 :param cache_source: source files to tar up when building cache file
147 :param cache_dir: cache storage location
150 if cache_dir is None:
151 logging.info("No cache dir specified, will not build cache")
153 cache_name = 'apex-cache-{}.tgz'.format(str(uuid.uuid4()))
154 cache_full_path = os.path.join(cache_dir, cache_name)
155 os.makedirs(cache_dir, exist_ok=True)
157 subprocess.check_call(['tar', '--atime-preserve', '--dereference',
158 '-caf', cache_full_path, '-C', cache_source,
160 except BaseException as e:
161 logging.error("Unable to build new cache tarball")
162 if os.path.isfile(cache_full_path):
163 os.remove(cache_full_path)
165 if os.path.isfile(cache_full_path):
166 logging.info("Cache Build Complete")
168 cache_entries = get_journal(cache_dir)
169 if cache_entries is None:
170 cache_entries = [cache_name]
172 cache_entries.append(cache_name)
173 journal_file = os.path.join(cache_dir, CACHE_JOURNAL)
174 with open(journal_file, 'w') as fh:
175 yaml.safe_dump(cache_entries, fh, default_flow_style=False)
176 logging.info("Journal updated with new entry: {}".format(cache_name))
178 logging.warning("Cache file did not build correctly")
181 def prune_cache(cache_dir):
183 Remove older cache entries if there are more than 2
184 :param cache_dir: Cache storage directory
187 if cache_dir is None:
189 cache_modified_flag = False
190 cache_entries = get_journal(cache_dir)
191 while len(cache_entries) > 2:
192 logging.debug("Will remove older cache entries")
193 cache_to_rm = cache_entries[0]
194 cache_full_path = os.path.join(cache_dir, cache_to_rm)
195 if os.path.isfile(cache_full_path):
197 os.remove(cache_full_path)
199 cache_modified_flag = True
201 logging.warning("Failed to remove cache file: {}".format(
206 logging.debug("No more cache cleanup necessary")
208 if cache_modified_flag:
209 logging.debug("Updating cache journal")
210 journal_file = os.path.join(cache_dir, CACHE_JOURNAL)
211 with open(journal_file, 'w') as fh:
212 yaml.safe_dump(cache_entries, fh, default_flow_style=False)
216 parser = create_build_parser()
217 args = parser.parse_args(sys.argv[1:])
219 log_level = logging.DEBUG
221 log_level = logging.INFO
222 os.makedirs(os.path.dirname(args.log_file), exist_ok=True)
223 formatter = '%(asctime)s %(levelname)s: %(message)s'
224 logging.basicConfig(filename=args.log_file,
226 datefmt='%m/%d/%Y %I:%M:%S %p',
228 console = logging.StreamHandler()
229 console.setLevel(log_level)
230 console.setFormatter(logging.Formatter(formatter))
231 logging.getLogger('').addHandler(console)
232 utils.install_ansible()
233 # Since we only support building inside of git repo this should be fine
235 apex_root = subprocess.check_output(
236 ['git', 'rev-parse', '--show-toplevel']).decode('utf-8').strip()
237 except subprocess.CalledProcessError:
238 logging.error("Must be in an Apex git repo to execute build")
240 apex_build_root = os.path.join(apex_root, BUILD_ROOT)
241 if not os.path.isdir(apex_build_root):
242 logging.error("You must execute this script inside of the Apex "
243 "local code repository")
244 raise ApexBuildException("Invalid path for apex root: {}. Must be "
245 "invoked from within Apex code directory.".
247 dep_playbook = os.path.join(apex_root,
248 'lib/ansible/playbooks/build_dependencies.yml')
249 utils.run_ansible(None, dep_playbook)
250 build(apex_build_root, args.build_version, args.iso, args.rpms)
253 if __name__ == '__main__':