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
2019-01-15 20:49:53 +00:00
from common import Name
import common
import wire_cmd
2018-05-17 20:55:53 +00:00
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 ] )
2018-12-05 17:49:04 +00:00
def link_object ( obj , types ) :
def make_method ( json_data ) :
2019-01-15 20:49:53 +00:00
arguments = common . linked_record_members ( json_data . get ( ' args ' , [ ] ) , types )
return common . Method ( Name ( json_data [ ' name ' ] ) , types [ json_data . get ( ' returns ' , ' void ' ) ] , arguments )
2018-12-05 17:49:04 +00:00
methods = [ make_method ( m ) for m in obj . json_data . get ( ' methods ' , [ ] ) ]
2017-04-20 18:42:36 +00:00
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 ) :
2019-01-15 20:49:53 +00:00
struct . members = common . linked_record_members ( struct . json_data [ ' members ' ] , types )
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 :
2018-12-05 17:49:04 +00:00
if member . type . category == ' structure ' :
2018-09-18 12:49:22 +00:00
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 = {
2019-01-15 20:49:53 +00:00
' bitmask ' : common . BitmaskType ,
' enum ' : common . EnumType ,
' native ' : common . NativeType ,
' natively defined ' : common . NativelyDefined ,
' object ' : common . ObjectType ,
' structure ' : common . StructureType ,
2017-04-20 18:38:20 +00:00
}
types = { }
by_category = { }
for name in category_to_parser . keys ( ) :
by_category [ name ] = [ ]
2018-12-05 17:49:04 +00:00
for ( name , json_data ) in json . items ( ) :
2017-04-20 18:38:20 +00:00
if name [ 0 ] == ' _ ' :
continue
2018-12-05 17:49:04 +00:00
category = json_data [ ' category ' ]
parsed = category_to_parser [ category ] ( name , json_data )
2017-04-20 18:38:20 +00:00
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 ' ] )
2019-01-15 20:49:53 +00:00
for struct in by_category [ ' structure ' ] :
struct . update_metadata ( )
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 == ' const* ' :
return typ + ' const * ' + name
2019-01-21 08:29:01 +00:00
elif arg . annotation == ' const*const* ' :
return ' const ' + typ + ' * const * ' + name
2017-04-20 18:38:20 +00:00
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 :
2019-01-15 20:49:53 +00:00
methods . append ( common . Method ( Name ( ' set error callback ' ) , types [ ' void ' ] , [
common . RecordMember ( Name ( ' callback ' ) , types [ ' builder error callback ' ] , ' value ' , False , False ) ,
common . RecordMember ( Name ( ' userdata1 ' ) , types [ ' callback userdata ' ] , ' value ' , False , False ) ,
common . RecordMember ( Name ( ' userdata2 ' ) , types [ ' callback userdata ' ] , ' value ' , False , False ) ,
2017-05-08 13:17:44 +00:00
] ) )
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 ) + [
2019-01-15 20:49:53 +00:00
common . Method ( Name ( ' reference ' ) , types [ ' void ' ] , [ ] ) ,
common . 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 )
2019-01-15 20:49:53 +00:00
def get_renders_for_targets ( api_params , wire_json , 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-12-08 10:35:53 +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 :
2019-01-15 20:49:53 +00:00
additional_params = wire_cmd . compute_wire_params ( api_params , wire_json )
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 )
2019-01-15 20:49:53 +00:00
} ,
additional_params
2018-08-01 13:12:10 +00:00
]
2019-01-16 02:18:06 +00:00
renders . append ( FileRender ( ' dawn_wire/TypeTraits.h ' , ' dawn_wire/TypeTraits_autogen.h ' , wire_params ) )
2018-08-01 13:12:10 +00:00
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/WireServer.cpp ' , ' dawn_wire/WireServer.cpp ' , wire_params ) )
2019-01-16 02:18:06 +00:00
renders . append ( FileRender ( ' dawn_wire/client/ApiObjects.h ' , ' dawn_wire/client/ApiObjects_autogen.h ' , wire_params ) )
renders . append ( FileRender ( ' dawn_wire/client/ApiProcs.cpp ' , ' dawn_wire/client/ApiProcs_autogen.cpp ' , wire_params ) )
renders . append ( FileRender ( ' dawn_wire/client/ApiProcs.h ' , ' dawn_wire/client/ApiProcs_autogen.h ' , wire_params ) )
renders . append ( FileRender ( ' dawn_wire/client/ClientHandlers.cpp ' , ' dawn_wire/client/ClientHandlers_autogen.cpp ' , wire_params ) )
renders . append ( FileRender ( ' dawn_wire/client/ClientPrototypes.inl ' , ' dawn_wire/client/ClientPrototypes_autogen.inl ' , wire_params ) )
renders . append ( FileRender ( ' dawn_wire/client/Device.h ' , ' dawn_wire/client/Device_autogen.h ' , wire_params ) )
2017-04-20 18:38:20 +00:00
2018-08-16 13:32:35 +00:00
return renders
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. ' )
2019-01-15 20:49:53 +00:00
parser . add_argument ( ' --wire-json ' , default = None , type = str , help = ' The DAWN WIRE JSON definition to use. ' )
2018-08-16 13:32:35 +00:00
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 ) )
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 " )
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 ( ' , ' )
2019-01-15 20:49:53 +00:00
dependencies = [
os . path . join ( os . path . abspath ( os . path . dirname ( __file__ ) ) , " common.py " )
]
loaded_wire_json = None
if args . wire_json :
with open ( args . wire_json ) as f :
loaded_wire_json = json . loads ( f . read ( ) )
dependencies . append ( args . wire_json )
renders = get_renders_for_targets ( api_params , loaded_wire_json , targets )
2018-08-16 13:32:35 +00:00
# The caller wants to assert that the outputs are what it expects.
2018-10-31 10:53:11 +00:00
# Load the file and compare with our renders.
2018-08-16 13:32:35 +00:00
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 )
2018-10-31 10:53:11 +00:00
# Output the tarball and its depfile
2018-08-16 13:32:35 +00:00
if args . output_json_tarball != None :
output_to_json ( outputs , args . output_json_tarball )
2019-01-15 20:49:53 +00:00
dependencies + = [ args . template_dir + os . path . sep + render . template for render in renders ]
2018-08-16 13:32:35 +00:00
dependencies . append ( args . json [ 0 ] )
2019-01-15 20:49:53 +00:00
dependencies . append ( os . path . join ( os . path . abspath ( os . path . dirname ( __file__ ) ) , " wire_cmd.py " ) )
2018-08-16 13:32:35 +00:00
output_depfile ( args . depfile , args . output_json_tarball , dependencies )
2017-04-20 18:38:20 +00:00
if __name__ == ' __main__ ' :
sys . exit ( main ( ) )