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 CACHE_JOURNAL = 'cache_journal.yaml'
21 BUILD_LOG_FILE = './apex_build.log'
24 class ApexBuildException(Exception):
28 def create_build_parser():
29 build_parser = argparse.ArgumentParser()
30 build_parser.add_argument('--debug', action='store_true', default=False,
31 help="Turn on debug messages")
32 build_parser.add_argument('-l', '--log-file',
33 default=BUILD_LOG_FILE,
34 dest='log_file', help="Log file to log to")
35 build_parser.add_argument('-c', '--cache-dir',
38 help='Directory to store cache')
39 build_parser.add_argument('--iso', action='store_true',
41 help='Build ISO image')
42 build_parser.add_argument('--rpms', action='store_true',
45 build_parser.add_argument('-r', '--release',
47 help='Version to apply to build '
53 def get_journal(cache_dir):
55 Search for the journal file and returns its contents
56 :param cache_dir: cache storage directory where journal file is
57 :return: content of journal file
59 journal_file = "{}/{}".format(cache_dir, CACHE_JOURNAL)
60 if os.path.isfile(journal_file) is False:
61 logging.info("Journal file not found {}, skipping cache search".format(
64 with open(journal_file, 'r') as fh:
65 cache_journal = yaml.safe_load(fh)
66 assert isinstance(cache_journal, list)
70 def get_cache_file(cache_dir):
72 Searches for a valid cache entry in the cache journal
73 :param cache_dir: directory where cache and journal are located
74 :return: name of valid cache file
76 cache_journal = get_journal(cache_dir)
77 if cache_journal is not None:
78 valid_cache = cache_journal[-1]
79 if os.path.isfile(valid_cache):
83 def unpack_cache(cache_dest, cache_dir=None):
85 logging.info("Cache directory not provided, skipping cache unpack")
87 elif os.path.isdir(cache_dir) is False:
88 logging.info("Cache Directory does not exist, skipping cache unpack")
91 logging.info("Cache Directory Found: {}".format(cache_dir))
92 cache_file = get_cache_file(cache_dir)
93 if cache_file is None:
94 logging.info("No cache file detected, skipping cache unpack")
96 logging.info("Unpacking Cache {}".format(cache_file))
97 if not os.path.exists(cache_dest):
98 os.makedirs(cache_dest)
100 subprocess.check_call(["tar", "xvf", cache_file, "-C", cache_dest])
101 except subprocess.CalledProcessError:
102 logging.warning("Cache unpack failed")
104 logging.info("Cache unpacked, contents are: {}",
105 os.listdir(cache_dest))
108 def build(build_root, version, iso=False, rpms=False):
110 make_targets = ['iso']
112 make_targets = ['rpms']
114 make_targets = ['images', 'rpms-check']
115 if version is not None:
116 make_args = ['RELEASE={}'.format(version)]
119 logging.info('Building targets: {}'.format(make_targets))
121 output = subprocess.check_output(["make"] + make_args + ["-C",
122 build_root] + make_targets)
124 except subprocess.CalledProcessError as e:
125 logging.error("Failed to build Apex artifacts")
126 logging.error(e.output)
130 def build_cache(cache_source, cache_dir):
132 Tar up new cache with unique name and store it in cache storage
133 directory. Also update journal file with new cache entry.
134 :param cache_source: source files to tar up when building cache file
135 :param cache_dir: cache storage location
138 if cache_dir is None:
139 logging.info("No cache dir specified, will not build cache")
141 cache_name = 'apex-cache-{}.tgz'.format(str(uuid.uuid4()))
142 cache_full_path = os.path.join(cache_dir, cache_name)
143 os.makedirs(cache_dir, exist_ok=True)
145 subprocess.check_call(['tar', '--atime-preserve', '--dereference',
146 '-caf', cache_full_path, '-C', cache_source,
148 except BaseException as e:
149 logging.error("Unable to build new cache tarball")
150 if os.path.isfile(cache_full_path):
151 os.remove(cache_full_path)
153 if os.path.isfile(cache_full_path):
154 logging.info("Cache Build Complete")
156 cache_entries = get_journal(cache_dir)
157 if cache_entries is None:
158 cache_entries = [cache_name]
160 cache_entries.append(cache_name)
161 journal_file = os.path.join(cache_dir, CACHE_JOURNAL)
162 with open(journal_file, 'w') as fh:
163 yaml.safe_dump(cache_entries, fh, default_flow_style=False)
164 logging.info("Journal updated with new entry: {}".format(cache_name))
166 logging.warning("Cache file did not build correctly")
169 def prune_cache(cache_dir):
171 Remove older cache entries if there are more than 2
172 :param cache_dir: Cache storage directory
175 if cache_dir is None:
177 cache_modified_flag = False
178 cache_entries = get_journal(cache_dir)
179 while len(cache_entries) > 2:
180 logging.debug("Will remove older cache entries")
181 cache_to_rm = cache_entries[0]
182 cache_full_path = os.path.join(cache_dir, cache_to_rm)
183 if os.path.isfile(cache_full_path):
185 os.remove(cache_full_path)
187 cache_modified_flag = True
189 logging.warning("Failed to remove cache file: {}".format(
194 logging.debug("No more cache cleanup necessary")
196 if cache_modified_flag:
197 logging.debug("Updating cache journal")
198 journal_file = os.path.join(cache_dir, CACHE_JOURNAL)
199 with open(journal_file, 'w') as fh:
200 yaml.safe_dump(cache_entries, fh, default_flow_style=False)
202 if __name__ == '__main__':
203 parser = create_build_parser()
204 args = parser.parse_args(sys.argv[1:])
206 log_level = logging.DEBUG
208 log_level = logging.INFO
209 os.makedirs(os.path.dirname(args.log_file), exist_ok=True)
210 formatter = '%(asctime)s %(levelname)s: %(message)s'
211 logging.basicConfig(filename=args.log_file,
213 datefmt='%m/%d/%Y %I:%M:%S %p',
215 console = logging.StreamHandler()
216 console.setLevel(log_level)
217 console.setFormatter(logging.Formatter(formatter))
218 logging.getLogger('').addHandler(console)
219 apex_root = os.path.split(os.getcwd())[0]
220 if 'apex/apex' in apex_root:
221 apex_root = os.path.split(apex_root)[0]
222 for root, dirs, files in os.walk(apex_root):
223 if BUILD_ROOT in dirs and 'apex/apex' not in root:
226 apex_build_root = os.path.join(apex_root, BUILD_ROOT)
227 if os.path.isdir(apex_build_root):
228 cache_tmp_dir = os.path.join(apex_root, TMP_CACHE)
230 logging.error("You must execute this script inside of the Apex "
231 "local code repository")
232 raise ApexBuildException("Invalid path for apex root: {}. Must be "
233 "invoked from within Apex code directory.".
235 unpack_cache(cache_tmp_dir, args.cache_dir)
236 build(apex_build_root, args.build_version, args.iso, args.rpms)
237 build_cache(cache_tmp_dir, args.cache_dir)
238 prune_cache(args.cache_dir)