1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*8975f5c5SAndroid Build Coastguard Worker# 3*8975f5c5SAndroid Build Coastguard Worker# Copyright 2014 The Chromium Authors 4*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 5*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 6*8975f5c5SAndroid Build Coastguard Worker 7*8975f5c5SAndroid Build Coastguard Worker"""Renders one or more template files using the Jinja template engine.""" 8*8975f5c5SAndroid Build Coastguard Worker 9*8975f5c5SAndroid Build Coastguard Workerimport codecs 10*8975f5c5SAndroid Build Coastguard Workerimport argparse 11*8975f5c5SAndroid Build Coastguard Workerimport os 12*8975f5c5SAndroid Build Coastguard Workerimport sys 13*8975f5c5SAndroid Build Coastguard Worker 14*8975f5c5SAndroid Build Coastguard Workerfrom util import build_utils 15*8975f5c5SAndroid Build Coastguard Workerfrom util import resource_utils 16*8975f5c5SAndroid Build Coastguard Workerimport action_helpers # build_utils adds //build to sys.path. 17*8975f5c5SAndroid Build Coastguard Workerimport zip_helpers 18*8975f5c5SAndroid Build Coastguard Worker 19*8975f5c5SAndroid Build Coastguard Workersys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) 20*8975f5c5SAndroid Build Coastguard Workerfrom pylib.constants import host_paths 21*8975f5c5SAndroid Build Coastguard Worker 22*8975f5c5SAndroid Build Coastguard Worker# Import jinja2 from third_party/jinja2 23*8975f5c5SAndroid Build Coastguard Workersys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party')) 24*8975f5c5SAndroid Build Coastguard Workerimport jinja2 # pylint: disable=F0401 25*8975f5c5SAndroid Build Coastguard Worker 26*8975f5c5SAndroid Build Coastguard Worker 27*8975f5c5SAndroid Build Coastguard Workerclass _RecordingFileSystemLoader(jinja2.FileSystemLoader): 28*8975f5c5SAndroid Build Coastguard Worker def __init__(self, searchpath): 29*8975f5c5SAndroid Build Coastguard Worker jinja2.FileSystemLoader.__init__(self, searchpath) 30*8975f5c5SAndroid Build Coastguard Worker self.loaded_templates = set() 31*8975f5c5SAndroid Build Coastguard Worker 32*8975f5c5SAndroid Build Coastguard Worker def get_source(self, environment, template): 33*8975f5c5SAndroid Build Coastguard Worker contents, filename, uptodate = jinja2.FileSystemLoader.get_source( 34*8975f5c5SAndroid Build Coastguard Worker self, environment, template) 35*8975f5c5SAndroid Build Coastguard Worker self.loaded_templates.add(os.path.relpath(filename)) 36*8975f5c5SAndroid Build Coastguard Worker return contents, filename, uptodate 37*8975f5c5SAndroid Build Coastguard Worker 38*8975f5c5SAndroid Build Coastguard Worker 39*8975f5c5SAndroid Build Coastguard Workerclass JinjaProcessor: 40*8975f5c5SAndroid Build Coastguard Worker """Allows easy rendering of jinja templates with input file tracking.""" 41*8975f5c5SAndroid Build Coastguard Worker def __init__(self, loader_base_dir, variables=None): 42*8975f5c5SAndroid Build Coastguard Worker self.loader_base_dir = loader_base_dir 43*8975f5c5SAndroid Build Coastguard Worker self.variables = variables or {} 44*8975f5c5SAndroid Build Coastguard Worker self.loader = _RecordingFileSystemLoader(loader_base_dir) 45*8975f5c5SAndroid Build Coastguard Worker self.env = jinja2.Environment(loader=self.loader) 46*8975f5c5SAndroid Build Coastguard Worker self.env.undefined = jinja2.StrictUndefined 47*8975f5c5SAndroid Build Coastguard Worker self.env.line_comment_prefix = '##' 48*8975f5c5SAndroid Build Coastguard Worker self.env.trim_blocks = True 49*8975f5c5SAndroid Build Coastguard Worker self.env.lstrip_blocks = True 50*8975f5c5SAndroid Build Coastguard Worker self._template_cache = {} # Map of path -> Template 51*8975f5c5SAndroid Build Coastguard Worker 52*8975f5c5SAndroid Build Coastguard Worker def Render(self, input_filename, variables=None): 53*8975f5c5SAndroid Build Coastguard Worker input_rel_path = os.path.relpath(input_filename, self.loader_base_dir) 54*8975f5c5SAndroid Build Coastguard Worker template = self._template_cache.get(input_rel_path) 55*8975f5c5SAndroid Build Coastguard Worker if not template: 56*8975f5c5SAndroid Build Coastguard Worker template = self.env.get_template(input_rel_path) 57*8975f5c5SAndroid Build Coastguard Worker self._template_cache[input_rel_path] = template 58*8975f5c5SAndroid Build Coastguard Worker return template.render(variables or self.variables) 59*8975f5c5SAndroid Build Coastguard Worker 60*8975f5c5SAndroid Build Coastguard Worker def GetLoadedTemplates(self): 61*8975f5c5SAndroid Build Coastguard Worker return list(self.loader.loaded_templates) 62*8975f5c5SAndroid Build Coastguard Worker 63*8975f5c5SAndroid Build Coastguard Worker 64*8975f5c5SAndroid Build Coastguard Workerdef _ProcessFile(processor, input_filename, output_filename): 65*8975f5c5SAndroid Build Coastguard Worker output = processor.Render(input_filename) 66*8975f5c5SAndroid Build Coastguard Worker 67*8975f5c5SAndroid Build Coastguard Worker # If |output| is same with the file content, we skip update and 68*8975f5c5SAndroid Build Coastguard Worker # ninja's restat will avoid rebuilding things that depend on it. 69*8975f5c5SAndroid Build Coastguard Worker if os.path.isfile(output_filename): 70*8975f5c5SAndroid Build Coastguard Worker with codecs.open(output_filename, 'r', 'utf-8') as f: 71*8975f5c5SAndroid Build Coastguard Worker if f.read() == output: 72*8975f5c5SAndroid Build Coastguard Worker return 73*8975f5c5SAndroid Build Coastguard Worker 74*8975f5c5SAndroid Build Coastguard Worker with codecs.open(output_filename, 'w', 'utf-8') as output_file: 75*8975f5c5SAndroid Build Coastguard Worker output_file.write(output) 76*8975f5c5SAndroid Build Coastguard Worker 77*8975f5c5SAndroid Build Coastguard Worker 78*8975f5c5SAndroid Build Coastguard Workerdef _ProcessFiles(processor, input_filenames, inputs_base_dir, outputs_zip): 79*8975f5c5SAndroid Build Coastguard Worker with build_utils.TempDir() as temp_dir: 80*8975f5c5SAndroid Build Coastguard Worker path_info = resource_utils.ResourceInfoFile() 81*8975f5c5SAndroid Build Coastguard Worker for input_filename in input_filenames: 82*8975f5c5SAndroid Build Coastguard Worker relpath = os.path.relpath(os.path.abspath(input_filename), 83*8975f5c5SAndroid Build Coastguard Worker os.path.abspath(inputs_base_dir)) 84*8975f5c5SAndroid Build Coastguard Worker if relpath.startswith(os.pardir): 85*8975f5c5SAndroid Build Coastguard Worker raise Exception('input file %s is not contained in inputs base dir %s' 86*8975f5c5SAndroid Build Coastguard Worker % (input_filename, inputs_base_dir)) 87*8975f5c5SAndroid Build Coastguard Worker 88*8975f5c5SAndroid Build Coastguard Worker output_filename = os.path.join(temp_dir, relpath) 89*8975f5c5SAndroid Build Coastguard Worker parent_dir = os.path.dirname(output_filename) 90*8975f5c5SAndroid Build Coastguard Worker build_utils.MakeDirectory(parent_dir) 91*8975f5c5SAndroid Build Coastguard Worker _ProcessFile(processor, input_filename, output_filename) 92*8975f5c5SAndroid Build Coastguard Worker path_info.AddMapping(relpath, input_filename) 93*8975f5c5SAndroid Build Coastguard Worker 94*8975f5c5SAndroid Build Coastguard Worker path_info.Write(outputs_zip + '.info') 95*8975f5c5SAndroid Build Coastguard Worker with action_helpers.atomic_output(outputs_zip) as f: 96*8975f5c5SAndroid Build Coastguard Worker zip_helpers.zip_directory(f, temp_dir) 97*8975f5c5SAndroid Build Coastguard Worker 98*8975f5c5SAndroid Build Coastguard Worker 99*8975f5c5SAndroid Build Coastguard Workerdef _ParseVariables(variables_arg, error_func): 100*8975f5c5SAndroid Build Coastguard Worker variables = {} 101*8975f5c5SAndroid Build Coastguard Worker for v in action_helpers.parse_gn_list(variables_arg): 102*8975f5c5SAndroid Build Coastguard Worker if '=' not in v: 103*8975f5c5SAndroid Build Coastguard Worker error_func('--variables argument must contain "=": ' + v) 104*8975f5c5SAndroid Build Coastguard Worker name, _, value = v.partition('=') 105*8975f5c5SAndroid Build Coastguard Worker variables[name] = value 106*8975f5c5SAndroid Build Coastguard Worker return variables 107*8975f5c5SAndroid Build Coastguard Worker 108*8975f5c5SAndroid Build Coastguard Worker 109*8975f5c5SAndroid Build Coastguard Workerdef main(): 110*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 111*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--inputs', required=True, 112*8975f5c5SAndroid Build Coastguard Worker help='GN-list of template files to process.') 113*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--includes', default='', 114*8975f5c5SAndroid Build Coastguard Worker help="GN-list of files that get {% include %}'ed.") 115*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--output', help='The output file to generate. Valid ' 116*8975f5c5SAndroid Build Coastguard Worker 'only if there is a single input.') 117*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--outputs-zip', help='A zip file for the processed ' 118*8975f5c5SAndroid Build Coastguard Worker 'templates. Required if there are multiple inputs.') 119*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--inputs-base-dir', help='A common ancestor directory ' 120*8975f5c5SAndroid Build Coastguard Worker 'of the inputs. Each output\'s path in the output zip ' 121*8975f5c5SAndroid Build Coastguard Worker 'will match the relative path from INPUTS_BASE_DIR to ' 122*8975f5c5SAndroid Build Coastguard Worker 'the input. Required if --output-zip is given.') 123*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--loader-base-dir', help='Base path used by the ' 124*8975f5c5SAndroid Build Coastguard Worker 'template loader. Must be a common ancestor directory of ' 125*8975f5c5SAndroid Build Coastguard Worker 'the inputs. Defaults to DIR_SOURCE_ROOT.', 126*8975f5c5SAndroid Build Coastguard Worker default=host_paths.DIR_SOURCE_ROOT) 127*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--variables', help='Variables to be made available in ' 128*8975f5c5SAndroid Build Coastguard Worker 'the template processing environment, as a GYP list ' 129*8975f5c5SAndroid Build Coastguard Worker '(e.g. --variables "channel=beta mstone=39")', default='') 130*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--check-includes', action='store_true', 131*8975f5c5SAndroid Build Coastguard Worker help='Enable inputs and includes checks.') 132*8975f5c5SAndroid Build Coastguard Worker options = parser.parse_args() 133*8975f5c5SAndroid Build Coastguard Worker 134*8975f5c5SAndroid Build Coastguard Worker inputs = action_helpers.parse_gn_list(options.inputs) 135*8975f5c5SAndroid Build Coastguard Worker includes = action_helpers.parse_gn_list(options.includes) 136*8975f5c5SAndroid Build Coastguard Worker 137*8975f5c5SAndroid Build Coastguard Worker if (options.output is None) == (options.outputs_zip is None): 138*8975f5c5SAndroid Build Coastguard Worker parser.error('Exactly one of --output and --output-zip must be given') 139*8975f5c5SAndroid Build Coastguard Worker if options.output and len(inputs) != 1: 140*8975f5c5SAndroid Build Coastguard Worker parser.error('--output cannot be used with multiple inputs') 141*8975f5c5SAndroid Build Coastguard Worker if options.outputs_zip and not options.inputs_base_dir: 142*8975f5c5SAndroid Build Coastguard Worker parser.error('--inputs-base-dir must be given when --output-zip is used') 143*8975f5c5SAndroid Build Coastguard Worker 144*8975f5c5SAndroid Build Coastguard Worker variables = _ParseVariables(options.variables, parser.error) 145*8975f5c5SAndroid Build Coastguard Worker processor = JinjaProcessor(options.loader_base_dir, variables=variables) 146*8975f5c5SAndroid Build Coastguard Worker 147*8975f5c5SAndroid Build Coastguard Worker if options.output: 148*8975f5c5SAndroid Build Coastguard Worker _ProcessFile(processor, inputs[0], options.output) 149*8975f5c5SAndroid Build Coastguard Worker else: 150*8975f5c5SAndroid Build Coastguard Worker _ProcessFiles(processor, inputs, options.inputs_base_dir, 151*8975f5c5SAndroid Build Coastguard Worker options.outputs_zip) 152*8975f5c5SAndroid Build Coastguard Worker 153*8975f5c5SAndroid Build Coastguard Worker if options.check_includes: 154*8975f5c5SAndroid Build Coastguard Worker all_inputs = set(processor.GetLoadedTemplates()) 155*8975f5c5SAndroid Build Coastguard Worker all_inputs.difference_update(inputs) 156*8975f5c5SAndroid Build Coastguard Worker all_inputs.difference_update(includes) 157*8975f5c5SAndroid Build Coastguard Worker if all_inputs: 158*8975f5c5SAndroid Build Coastguard Worker raise Exception('Found files not listed via --includes:\n' + 159*8975f5c5SAndroid Build Coastguard Worker '\n'.join(sorted(all_inputs))) 160*8975f5c5SAndroid Build Coastguard Worker 161*8975f5c5SAndroid Build Coastguard Worker 162*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 163*8975f5c5SAndroid Build Coastguard Worker main() 164