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