2017-04-26 21:56:40 +00:00
#!/usr/bin/env python2
2018-07-18 09:40:26 +00:00
# Copyright 2017 The Dawn Authors
2017-04-20 18:38:20 +00:00
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
############################################################
# COMMON
############################################################
from collections import namedtuple
class Name :
def __init__ ( self , name , native = False ) :
self . native = native
if native :
self . chunks = [ name ]
else :
self . chunks = name . split ( ' ' )
def CamelChunk ( self , chunk ) :
return chunk [ 0 ] . upper ( ) + chunk [ 1 : ]
def canonical_case ( self ) :
return ( ' ' . join ( self . chunks ) ) . lower ( )
def concatcase ( self ) :
return ' ' . join ( self . chunks )
def camelCase ( self ) :
return self . chunks [ 0 ] + ' ' . join ( [ self . CamelChunk ( chunk ) for chunk in self . chunks [ 1 : ] ] )
def CamelCase ( self ) :
return ' ' . join ( [ self . CamelChunk ( chunk ) for chunk in self . chunks ] )
def SNAKE_CASE ( self ) :
return ' _ ' . join ( [ chunk . upper ( ) for chunk in self . chunks ] )
def snake_case ( self ) :
return ' _ ' . join ( self . chunks )
class Type :
def __init__ ( self , name , record , native = False ) :
self . record = record
self . dict_name = name
self . name = Name ( name , native = native )
self . category = record [ ' category ' ]
self . is_builder = self . name . canonical_case ( ) . endswith ( " builder " )
EnumValue = namedtuple ( ' EnumValue ' , [ ' name ' , ' value ' ] )
class EnumType ( Type ) :
def __init__ ( self , name , record ) :
Type . __init__ ( self , name , record )
self . values = [ EnumValue ( Name ( m [ ' name ' ] ) , m [ ' value ' ] ) for m in self . record [ ' values ' ] ]
BitmaskValue = namedtuple ( ' BitmaskValue ' , [ ' name ' , ' value ' ] )
class BitmaskType ( Type ) :
def __init__ ( self , name , record ) :
Type . __init__ ( self , name , record )
self . values = [ BitmaskValue ( Name ( m [ ' name ' ] ) , m [ ' value ' ] ) for m in self . record [ ' values ' ] ]
self . full_mask = 0
for value in self . values :
self . full_mask = self . full_mask | value . value
class NativeType ( Type ) :
def __init__ ( self , name , record ) :
Type . __init__ ( self , name , record , native = True )
2017-04-20 18:42:36 +00:00
class NativelyDefined ( Type ) :
def __init__ ( self , name , record ) :
Type . __init__ ( self , name , record )
2017-04-20 18:38:20 +00:00
class MethodArgument :
def __init__ ( self , name , typ , annotation ) :
self . name = name
self . type = typ
self . annotation = annotation
self . length = None
Method = namedtuple ( ' Method ' , [ ' name ' , ' return_type ' , ' arguments ' ] )
class ObjectType ( Type ) :
def __init__ ( self , name , record ) :
Type . __init__ ( self , name , record )
self . methods = [ ]
2017-04-20 18:42:36 +00:00
self . native_methods = [ ]
2017-04-20 18:43:11 +00:00
self . built_type = None
2017-04-20 18:38:20 +00:00
2018-05-17 20:55:53 +00:00
class StructureMember :
def __init__ ( self , name , typ , annotation ) :
self . name = name
self . type = typ
self . annotation = annotation
self . length = None
class StructureType ( Type ) :
def __init__ ( self , name , record ) :
Type . __init__ ( self , name , record )
self . extensible = record . get ( " extensible " , False )
self . members = [ ]
2017-04-20 18:38:20 +00:00
############################################################
# PARSE
############################################################
import json
2017-04-20 18:42:36 +00:00
def is_native_method ( method ) :
return method . return_type . category == " natively defined " or \
any ( [ arg . type . category == " natively defined " for arg in method . arguments ] )
2017-04-20 18:38:20 +00:00
def link_object ( obj , types ) :
def make_method ( record ) :
arguments = [ ]
arguments_by_name = { }
for a in record . get ( ' args ' , [ ] ) :
arg = MethodArgument ( Name ( a [ ' name ' ] ) , types [ a [ ' type ' ] ] , a . get ( ' annotation ' , ' value ' ) )
arguments . append ( arg )
arguments_by_name [ arg . name . canonical_case ( ) ] = arg
for ( arg , a ) in zip ( arguments , record . get ( ' args ' , [ ] ) ) :
if arg . annotation != ' value ' :
2018-05-17 20:55:53 +00:00
if not ' length ' in a :
if arg . type . category == ' structure ' :
2018-06-08 16:30:50 +00:00
arg . length = " constant "
arg . constant_length = 1
2018-05-17 20:55:53 +00:00
else :
2018-09-21 06:55:25 +00:00
assert ( False )
2018-05-17 20:55:53 +00:00
elif a [ ' length ' ] == ' strlen ' :
2017-04-20 18:38:20 +00:00
arg . length = ' strlen '
else :
arg . length = arguments_by_name [ a [ ' length ' ] ]
return Method ( Name ( record [ ' name ' ] ) , types [ record . get ( ' returns ' , ' void ' ) ] , arguments )
2017-04-20 18:42:36 +00:00
methods = [ make_method ( m ) for m in obj . record . get ( ' methods ' , [ ] ) ]
obj . methods = [ method for method in methods if not is_native_method ( method ) ]
obj . native_methods = [ method for method in methods if is_native_method ( method ) ]
2017-04-20 18:38:20 +00:00
2017-04-20 18:43:11 +00:00
# Compute the built object type for builders
if obj . is_builder :
for method in obj . methods :
if method . name . canonical_case ( ) == " get result " :
obj . built_type = method . return_type
break
assert ( obj . built_type != None )
2018-05-17 20:55:53 +00:00
def link_structure ( struct , types ) :
def make_member ( m ) :
return StructureMember ( Name ( m [ ' name ' ] ) , types [ m [ ' type ' ] ] , m . get ( ' annotation ' , ' value ' ) )
2018-06-27 23:21:39 +00:00
members = [ ]
members_by_name = { }
for m in struct . record [ ' members ' ] :
member = make_member ( m )
members . append ( member )
members_by_name [ member . name . canonical_case ( ) ] = member
struct . members = members
for ( member , m ) in zip ( members , struct . record [ ' members ' ] ) :
# TODO(kainino@chromium.org): More robust pointer/length handling?
if ' length ' in m :
2018-08-27 21:12:56 +00:00
if m [ ' length ' ] == ' strlen ' :
member . length = ' strlen '
else :
member . length = members_by_name [ m [ ' length ' ] ]
2018-05-17 20:55:53 +00:00
2018-09-18 12:49:22 +00:00
# Sort structures so that if struct A has struct B as a member, then B is listed before A
# This is a form of topological sort where we try to keep the order reasonably similar to the
# original order (though th sort isn't technically stable).
# It works by computing for each struct type what is the depth of its DAG of dependents, then
# resorting based on that depth using Python's stable sort. This makes a toposort because if
# A depends on B then its depth will be bigger than B's. It is also nice because all nodes
# with the same depth are kept in the input order.
def topo_sort_structure ( structs ) :
for struct in structs :
struct . visited = False
struct . subdag_depth = 0
def compute_depth ( struct ) :
if struct . visited :
return struct . subdag_depth
max_dependent_depth = 0
for member in struct . members :
if member . type . category == ' structure ' and member . annotation == ' value ' :
max_dependent_depth = max ( max_dependent_depth , compute_depth ( member . type ) + 1 )
struct . subdag_depth = max_dependent_depth
struct . visited = True
return struct . subdag_depth
for struct in structs :
compute_depth ( struct )
result = sorted ( structs , key = lambda struct : struct . subdag_depth )
for struct in structs :
del struct . visited
del struct . subdag_depth
return result
2017-04-20 18:38:20 +00:00
def parse_json ( json ) :
category_to_parser = {
' bitmask ' : BitmaskType ,
' enum ' : EnumType ,
' native ' : NativeType ,
2017-04-20 18:42:36 +00:00
' natively defined ' : NativelyDefined ,
2017-04-20 18:38:20 +00:00
' object ' : ObjectType ,
2018-05-17 20:55:53 +00:00
' structure ' : StructureType ,
2017-04-20 18:38:20 +00:00
}
types = { }
by_category = { }
for name in category_to_parser . keys ( ) :
by_category [ name ] = [ ]
for ( name , record ) in json . items ( ) :
if name [ 0 ] == ' _ ' :
continue
category = record [ ' category ' ]
parsed = category_to_parser [ category ] ( name , record )
by_category [ category ] . append ( parsed )
types [ name ] = parsed
for obj in by_category [ ' object ' ] :
link_object ( obj , types )
2018-05-17 20:55:53 +00:00
for struct in by_category [ ' structure ' ] :
link_structure ( struct , types )
2017-04-20 18:38:20 +00:00
for category in by_category . keys ( ) :
by_category [ category ] = sorted ( by_category [ category ] , key = lambda typ : typ . name . canonical_case ( ) )
2018-09-18 12:49:22 +00:00
by_category [ ' structure ' ] = topo_sort_structure ( by_category [ ' structure ' ] )
2017-04-20 18:38:20 +00:00
return {
' types ' : types ,
' by_category ' : by_category
}
#############################################################
# OUTPUT
#############################################################
import re , os , sys
from collections import OrderedDict
2018-08-13 15:47:44 +00:00
kExtraPythonPath = ' --extra-python-path '
# Try using an additional python path from the arguments if present. This
# isn't done through the regular argparse because PreprocessingLoader uses
# jinja2 in the global scope before "main" gets to run.
if kExtraPythonPath in sys . argv :
path = sys . argv [ sys . argv . index ( kExtraPythonPath ) + 1 ]
sys . path . insert ( 1 , path )
import jinja2
2017-04-20 18:38:20 +00:00
# A custom Jinja2 template loader that removes the extra indentation
# of the template blocks so that the output is correctly indented
class PreprocessingLoader ( jinja2 . BaseLoader ) :
def __init__ ( self , path ) :
self . path = path
def get_source ( self , environment , template ) :
path = os . path . join ( self . path , template )
if not os . path . exists ( path ) :
raise jinja2 . TemplateNotFound ( template )
mtime = os . path . getmtime ( path )
with open ( path ) as f :
source = self . preprocess ( f . read ( ) )
return source , path , lambda : mtime == os . path . getmtime ( path )
blockstart = re . compile ( ' { % -? \ s*(if|for|block)[^}]* % } ' )
blockend = re . compile ( ' { % -? \ s*end(if|for|block)[^}]* % } ' )
def preprocess ( self , source ) :
lines = source . split ( ' \n ' )
# Compute the current indentation level of the template blocks and remove their indentation
result = [ ]
indentation_level = 0
for line in lines :
# The capture in the regex adds one element per block start or end so we divide by two
# there is also an extra line chunk corresponding to the line end, so we substract it.
2017-05-29 18:33:33 +00:00
numends = ( len ( self . blockend . split ( line ) ) - 1 ) / / 2
2017-04-20 18:38:20 +00:00
indentation_level - = numends
2017-05-30 22:14:01 +00:00
line = self . remove_indentation ( line , indentation_level )
# Manually perform the lstrip_blocks jinja2 env options as it available starting from 2.7
# and Travis only has Jinja 2.6
if line . lstrip ( ) . startswith ( ' { % ' ) :
line = line . lstrip ( )
result . append ( line )
2017-04-20 18:38:20 +00:00
2017-05-29 18:33:33 +00:00
numstarts = ( len ( self . blockstart . split ( line ) ) - 1 ) / / 2
2017-04-20 18:38:20 +00:00
indentation_level + = numstarts
2017-07-22 00:00:22 +00:00
return ' \n ' . join ( result ) + ' \n '
2017-04-20 18:38:20 +00:00
def remove_indentation ( self , line , n ) :
for _ in range ( n ) :
if line . startswith ( ' ' ) :
line = line [ 4 : ]
elif line . startswith ( ' \t ' ) :
line = line [ 1 : ]
else :
assert ( line . strip ( ) == ' ' )
return line
FileRender = namedtuple ( ' FileRender ' , [ ' template ' , ' output ' , ' params_dicts ' ] )
2018-08-16 13:32:35 +00:00
FileOutput = namedtuple ( ' FileOutput ' , [ ' name ' , ' content ' ] )
def do_renders ( renders , template_dir ) :
2017-05-30 22:14:01 +00:00
env = jinja2 . Environment ( loader = PreprocessingLoader ( template_dir ) , trim_blocks = True , line_comment_prefix = ' //* ' )
2018-08-16 13:32:35 +00:00
outputs = [ ]
2017-04-20 18:38:20 +00:00
for render in renders :
params = { }
for param_dict in render . params_dicts :
params . update ( param_dict )
2018-08-16 13:32:35 +00:00
content = env . get_template ( render . template ) . render ( * * params )
outputs . append ( FileOutput ( render . output , content ) )
2017-04-20 18:38:20 +00:00
2018-08-16 13:32:35 +00:00
return outputs
2017-04-20 18:38:20 +00:00
#############################################################
# MAIN SOMETHING WHATEVER
#############################################################
import argparse , sys
def as_varName ( * names ) :
return names [ 0 ] . camelCase ( ) + ' ' . join ( [ name . CamelCase ( ) for name in names [ 1 : ] ] )
def as_cType ( name ) :
if name . native :
return name . concatcase ( )
else :
2018-07-18 13:12:52 +00:00
return ' dawn ' + name . CamelCase ( )
2017-04-20 18:38:20 +00:00
def as_cppType ( name ) :
if name . native :
return name . concatcase ( )
else :
return name . CamelCase ( )
def decorate ( name , typ , arg ) :
if arg . annotation == ' value ' :
return typ + ' ' + name
elif arg . annotation == ' * ' :
return typ + ' * ' + name
elif arg . annotation == ' const* ' :
return typ + ' const * ' + name
else :
assert ( False )
def annotated ( typ , arg ) :
name = as_varName ( arg . name )
return decorate ( name , typ , arg )
def as_cEnum ( type_name , value_name ) :
assert ( not type_name . native and not value_name . native )
2018-07-18 13:12:52 +00:00
return ' DAWN ' + ' _ ' + type_name . SNAKE_CASE ( ) + ' _ ' + value_name . SNAKE_CASE ( )
2017-04-20 18:38:20 +00:00
def as_cppEnum ( value_name ) :
assert ( not value_name . native )
if value_name . concatcase ( ) [ 0 ] . isdigit ( ) :
return " e " + value_name . CamelCase ( )
return value_name . CamelCase ( )
def as_cMethod ( type_name , method_name ) :
assert ( not type_name . native and not method_name . native )
2018-07-18 13:12:52 +00:00
return ' dawn ' + type_name . CamelCase ( ) + method_name . CamelCase ( )
2017-04-20 18:38:20 +00:00
def as_MethodSuffix ( type_name , method_name ) :
assert ( not type_name . native and not method_name . native )
return type_name . CamelCase ( ) + method_name . CamelCase ( )
def as_cProc ( type_name , method_name ) :
assert ( not type_name . native and not method_name . native )
2018-07-18 13:12:52 +00:00
return ' dawn ' + ' Proc ' + type_name . CamelCase ( ) + method_name . CamelCase ( )
2017-04-20 18:38:20 +00:00
2018-08-01 13:12:10 +00:00
def as_frontendType ( typ ) :
2017-04-20 18:38:20 +00:00
if typ . category == ' object ' :
2018-08-01 13:12:10 +00:00
if typ . is_builder :
return typ . name . CamelCase ( ) + ' * '
else :
return typ . name . CamelCase ( ) + ' Base* '
elif typ . category in [ ' bitmask ' , ' enum ' ] :
return ' dawn:: ' + typ . name . CamelCase ( )
elif typ . category == ' structure ' :
return as_cppType ( typ . name )
2017-04-20 18:38:20 +00:00
else :
return as_cType ( typ . name )
2017-05-08 13:17:44 +00:00
def cpp_native_methods ( types , typ ) :
methods = typ . methods + typ . native_methods
if typ . is_builder :
methods . append ( Method ( Name ( ' set error callback ' ) , types [ ' void ' ] , [
MethodArgument ( Name ( ' callback ' ) , types [ ' builder error callback ' ] , ' value ' ) ,
MethodArgument ( Name ( ' userdata1 ' ) , types [ ' callback userdata ' ] , ' value ' ) ,
MethodArgument ( Name ( ' userdata2 ' ) , types [ ' callback userdata ' ] , ' value ' ) ,
] ) )
return methods
2017-04-20 18:42:36 +00:00
def c_native_methods ( types , typ ) :
2017-05-08 13:17:44 +00:00
return cpp_native_methods ( types , typ ) + [
2017-04-20 18:38:20 +00:00
Method ( Name ( ' reference ' ) , types [ ' void ' ] , [ ] ) ,
Method ( Name ( ' release ' ) , types [ ' void ' ] , [ ] ) ,
2017-04-20 18:42:36 +00:00
]
2017-10-25 19:39:00 +00:00
def js_native_methods ( types , typ ) :
return cpp_native_methods ( types , typ )
2017-04-20 18:38:20 +00:00
def debug ( text ) :
print ( text )
2018-08-16 13:32:35 +00:00
def get_renders_for_targets ( api_params , targets ) :
2017-04-20 18:38:20 +00:00
base_params = {
' enumerate ' : enumerate ,
' format ' : format ,
' len ' : len ,
' debug ' : debug ,
' Name ' : lambda name : Name ( name ) ,
' as_annotated_cType ' : lambda arg : annotated ( as_cType ( arg . type . name ) , arg ) ,
' as_annotated_cppType ' : lambda arg : annotated ( as_cppType ( arg . type . name ) , arg ) ,
' as_cEnum ' : as_cEnum ,
' as_cppEnum ' : as_cppEnum ,
' as_cMethod ' : as_cMethod ,
' as_MethodSuffix ' : as_MethodSuffix ,
' as_cProc ' : as_cProc ,
' as_cType ' : as_cType ,
' as_cppType ' : as_cppType ,
' as_varName ' : as_varName ,
' decorate ' : decorate ,
}
renders = [ ]
2017-04-20 18:42:36 +00:00
c_params = { ' native_methods ' : lambda typ : c_native_methods ( api_params [ ' types ' ] , typ ) }
2018-07-24 14:25:38 +00:00
cpp_params = { ' native_methods ' : lambda typ : cpp_native_methods ( api_params [ ' types ' ] , typ ) }
2017-04-20 18:42:36 +00:00
2018-07-24 14:25:38 +00:00
if ' dawn_headers ' in targets :
2018-07-18 12:28:38 +00:00
renders . append ( FileRender ( ' api.h ' , ' dawn/dawn.h ' , [ base_params , api_params , c_params ] ) )
2018-07-24 14:25:38 +00:00
renders . append ( FileRender ( ' apicpp.h ' , ' dawn/dawncpp.h ' , [ base_params , api_params , cpp_params ] ) )
renders . append ( FileRender ( ' apicpp_traits.h ' , ' dawn/dawncpp_traits.h ' , [ base_params , api_params , cpp_params ] ) )
2017-04-20 18:38:20 +00:00
2018-07-24 14:25:38 +00:00
if ' libdawn ' in targets :
2017-05-08 13:17:44 +00:00
additional_params = { ' native_methods ' : lambda typ : cpp_native_methods ( api_params [ ' types ' ] , typ ) }
2018-07-24 14:25:38 +00:00
renders . append ( FileRender ( ' api.c ' , ' dawn/dawn.c ' , [ base_params , api_params , c_params ] ) )
renders . append ( FileRender ( ' apicpp.cpp ' , ' dawn/dawncpp.cpp ' , [ base_params , api_params , cpp_params ] ) )
2017-04-20 18:38:20 +00:00
2018-07-18 11:57:29 +00:00
if ' mock_dawn ' in targets :
2018-07-18 12:28:38 +00:00
renders . append ( FileRender ( ' mock_api.h ' , ' mock/mock_dawn.h ' , [ base_params , api_params , c_params ] ) )
renders . append ( FileRender ( ' mock_api.cpp ' , ' mock/mock_dawn.cpp ' , [ base_params , api_params , c_params ] ) )
2017-04-20 18:38:20 +00:00
2018-07-24 11:53:51 +00:00
if ' dawn_native_utils ' in targets :
2018-08-01 13:12:10 +00:00
frontend_params = [
base_params ,
api_params ,
c_params ,
{
' as_frontendType ' : lambda typ : as_frontendType ( typ ) , # TODO as_frontendType and friends take a Type and not a Name :(
' as_annotated_frontendType ' : lambda arg : annotated ( as_frontendType ( arg . type ) , arg )
}
]
renders . append ( FileRender ( ' dawn_native/ValidationUtils.h ' , ' dawn_native/ValidationUtils_autogen.h ' , frontend_params ) )
renders . append ( FileRender ( ' dawn_native/ValidationUtils.cpp ' , ' dawn_native/ValidationUtils_autogen.cpp ' , frontend_params ) )
renders . append ( FileRender ( ' dawn_native/api_structs.h ' , ' dawn_native/dawn_structs_autogen.h ' , frontend_params ) )
renders . append ( FileRender ( ' dawn_native/api_structs.cpp ' , ' dawn_native/dawn_structs_autogen.cpp ' , frontend_params ) )
renders . append ( FileRender ( ' dawn_native/ProcTable.cpp ' , ' dawn_native/ProcTable.cpp ' , frontend_params ) )
2017-05-24 14:04:55 +00:00
2018-07-26 13:07:57 +00:00
if ' dawn_wire ' in targets :
2018-08-01 13:12:10 +00:00
wire_params = [
base_params ,
api_params ,
c_params ,
{
' as_wireType ' : lambda typ : typ . name . CamelCase ( ) + ' * ' if typ . category == ' object ' else as_cppType ( typ . name )
}
]
renders . append ( FileRender ( ' dawn_wire/WireCmd.h ' , ' dawn_wire/WireCmd_autogen.h ' , wire_params ) )
renders . append ( FileRender ( ' dawn_wire/WireCmd.cpp ' , ' dawn_wire/WireCmd_autogen.cpp ' , wire_params ) )
renders . append ( FileRender ( ' dawn_wire/WireClient.cpp ' , ' dawn_wire/WireClient.cpp ' , wire_params ) )
renders . append ( FileRender ( ' dawn_wire/WireServer.cpp ' , ' dawn_wire/WireServer.cpp ' , wire_params ) )
2017-04-20 18:38:20 +00:00
2018-08-16 13:32:35 +00:00
return renders
def output_to_files ( outputs , output_dir ) :
for output in outputs :
output_file = output_dir + os . path . sep + output . name
directory = os . path . dirname ( output_file )
if not os . path . exists ( directory ) :
os . makedirs ( directory )
with open ( output_file , ' w ' ) as outfile :
outfile . write ( output . content )
def output_to_json ( outputs , output_json ) :
json_root = { }
for output in outputs :
json_root [ output . name ] = output . content
with open ( output_json , ' w ' ) as f :
f . write ( json . dumps ( json_root ) )
def output_depfile ( depfile , output , dependencies ) :
with open ( depfile , ' w ' ) as f :
f . write ( output + " : " + " " . join ( dependencies ) )
def main ( ) :
allowed_targets = [ ' dawn_headers ' , ' libdawn ' , ' mock_dawn ' , ' dawn_wire ' , " dawn_native_utils " ]
parser = argparse . ArgumentParser (
description = ' Generates code for various target for Dawn. ' ,
formatter_class = argparse . ArgumentDefaultsHelpFormatter
)
parser . add_argument ( ' json ' , metavar = ' DAWN_JSON ' , nargs = 1 , type = str , help = ' The DAWN JSON definition to use. ' )
parser . add_argument ( ' -t ' , ' --template-dir ' , default = ' templates ' , type = str , help = ' Directory with template files. ' )
parser . add_argument ( ' -T ' , ' --targets ' , required = True , type = str , help = ' Comma-separated subset of targets to output. Available targets: ' + ' , ' . join ( allowed_targets ) )
# Arguments used only for the GN build
parser . add_argument ( kExtraPythonPath , default = None , type = str , help = ' Additional python path to set before loading Jinja2 ' )
parser . add_argument ( ' --output-json-tarball ' , default = None , type = str , help = ' Name of the " JSON tarball " to create (tar is too annoying to use in python). ' )
parser . add_argument ( ' --depfile ' , default = None , type = str , help = ' Name of the Ninja depfile to create for the JSON tarball ' )
parser . add_argument ( ' --expected-outputs-file ' , default = None , type = str , help = " File to compare outputs with and fail if it doesn ' t match " )
# Arguments used only for the CMake build
parser . add_argument ( ' -o ' , ' --output-dir ' , default = None , type = str , help = ' Output directory for the generated source files. ' )
parser . add_argument ( ' --print-dependencies ' , action = ' store_true ' , help = ' Prints a space separated list of file dependencies, used for CMake integration ' )
parser . add_argument ( ' --print-outputs ' , action = ' store_true ' , help = ' Prints a space separated list of file outputs, used for CMake integration ' )
args = parser . parse_args ( )
# Load and parse the API json file
with open ( args . json [ 0 ] ) as f :
loaded_json = json . loads ( f . read ( ) )
api_params = parse_json ( loaded_json )
targets = args . targets . split ( ' , ' )
renders = get_renders_for_targets ( api_params , targets )
# Print outputs and dependencies for CMake
2017-04-20 18:38:20 +00:00
if args . print_dependencies :
dependencies = set (
2017-06-05 21:08:55 +00:00
[ os . path . abspath ( args . template_dir + os . path . sep + render . template ) for render in renders ] +
2017-04-20 18:38:20 +00:00
[ os . path . abspath ( args . json [ 0 ] ) ] +
[ os . path . realpath ( __file__ ) ]
)
2017-05-29 18:33:33 +00:00
dependencies = [ dependency . replace ( ' \\ ' , ' / ' ) for dependency in dependencies ]
2018-08-16 13:32:35 +00:00
sys . stdout . write ( ' ; ' . join ( dependencies ) )
2017-04-20 18:38:20 +00:00
return 0
if args . print_outputs :
outputs = set (
[ os . path . abspath ( args . output_dir + os . path . sep + render . output ) for render in renders ]
)
2017-05-29 18:33:33 +00:00
outputs = [ output . replace ( ' \\ ' , ' / ' ) for output in outputs ]
2018-08-16 13:32:35 +00:00
sys . stdout . write ( ' ; ' . join ( outputs ) )
2017-04-20 18:38:20 +00:00
return 0
2018-08-16 13:32:35 +00:00
# The caller wants to assert that the outputs are what it expects.
# Load the file and compare with our renders. GN-only.
if args . expected_outputs_file != None :
with open ( args . expected_outputs_file ) as f :
expected = set ( [ line . strip ( ) for line in f . readlines ( ) ] )
actual = set ( )
actual . update ( [ render . output for render in renders ] )
if actual != expected :
print ( " Wrong expected outputs, caller expected: \n " + repr ( list ( expected ) ) )
print ( " Actual output: \n " + repr ( list ( actual ) ) )
return 1
outputs = do_renders ( renders , args . template_dir )
# CMake only: output all the files directly.
if args . output_dir != None :
output_to_files ( outputs , args . output_dir )
# GN only: output the tarball and its depfile
if args . output_json_tarball != None :
output_to_json ( outputs , args . output_json_tarball )
dependencies = [ args . template_dir + os . path . sep + render . template for render in renders ]
dependencies . append ( args . json [ 0 ] )
output_depfile ( args . depfile , args . output_json_tarball , dependencies )
2017-04-20 18:38:20 +00:00
if __name__ == ' __main__ ' :
sys . exit ( main ( ) )