1+ #!/usr/bin/python3
2+
3+ import os
4+ import json
5+ import shutil
6+ import subprocess
7+ import sys
8+
9+ def has_env_flag (name ):
10+ return os .environ .get (name , "0" ) == "1"
11+
12+ def _format_scalar (value ):
13+ # strings must be quoted and escaped, numbers/bools emitted as literals
14+ if isinstance (value , str ):
15+ return json .dumps (value )
16+ if isinstance (value , bool ):
17+ return "true" if value else "false"
18+ if isinstance (value , (int , float )):
19+ return str (value )
20+ # fallback to stringified-quoted
21+ return json .dumps (str (value ))
22+
23+
24+ def _format_list (lst ):
25+ # produce multiline list with each element on its own indented line and a trailing comma
26+ if not lst :
27+ return "[]"
28+ parts = ["[" ]
29+ for item in lst :
30+ parts .append (" " + _format_scalar (item ) + "," )
31+ parts .append ("]" )
32+ return "\n " .join (parts )
33+
34+
35+ def dict_to_gn (obj ):
36+ lines = []
37+ for key , val in obj .items ():
38+ if isinstance (val , (list , tuple )):
39+ formatted = _format_list (val )
40+ lines .append (f"{ key } = { formatted } " )
41+ else :
42+ lines .append (f"{ key } = { _format_scalar (val )} " )
43+ # separate entries with a blank line to match example formatting
44+ return "\n \n " .join (lines )
45+
46+ def write_file (path , content ):
47+ with open (path , "w" ) as f :
48+ f .write (content )
49+
50+ def read_all_lines (path ):
51+ with open (path , "r" ) as f :
52+ return f .readlines ()
53+
54+ def copy_preserving_dir (root : str , file : str , src_dir : str , dest_dir : str ):
55+ src_path = os .path .join (root , file )
56+ rel_path = os .path .relpath (src_path , src_dir )
57+ dest_path = os .path .join (dest_dir , rel_path )
58+ os .makedirs (os .path .dirname (dest_path ), exist_ok = True )
59+ shutil .copy2 (src_path , dest_path )
60+
61+ def copy_extension (src_dir , dest_dir , extension ):
62+ # Copy all files with the given extension from src_dir to dest_dir
63+ for root , dirs , files in os .walk (src_dir ):
64+ for file in files :
65+ if file .endswith (extension ):
66+ copy_preserving_dir (root , file , src_dir , dest_dir )
67+
68+ def copy_includes (src_dir , dest_dir ):
69+ for root , dirs , files in os .walk (src_dir ):
70+ for file in files :
71+ if file .endswith ('.h' ):
72+ copy_preserving_dir (root , file , src_dir , dest_dir )
73+
74+ def copy_headers ():
75+ artifacts_dir = os .path .join (".." , "artifacts" , "headers" )
76+ for skiaDir in ["include" , "modules" , "src" ]:
77+ copy_includes (skiaDir , os .path .join (artifacts_dir , skiaDir ))
78+
79+
80+ def collect_defines (path ):
81+ lines = read_all_lines (path )
82+ defines = []
83+ for line in lines :
84+ line = line .strip ()
85+ if line .startswith ("defines = " ):
86+ parts = line [len ("defines = " ):].strip ().split ()
87+ for part in parts :
88+ if part .startswith ("-D" ):
89+ defines .append (part [2 :])
90+ break
91+ return defines
92+
93+ def generate_skia_h (gn_dir ):
94+ defines = collect_defines (os .path .join (gn_dir , "obj" , "public_headers_warnings_check.ninja" ))
95+ defines = [d for d in defines if d .startswith ("SK_" ) or d .startswith ("SKIA_" )]
96+
97+ define_directives = [f"#define { d .replace ('=' , ' ' )} " for d in defines ]
98+ skia_h = read_all_lines (os .path .join (gn_dir , "gen" , "skia.h" ))
99+
100+ # insert lines from define_directives to skia_h before first line in skia_h that contains #include
101+ output_lines = []
102+ inserted = False
103+ for line in skia_h :
104+ if not inserted and line .strip ().startswith ("#include" ):
105+ output_lines .extend (define_directives )
106+ output_lines .append ("\n " )
107+ inserted = True
108+ output_lines .append (line .strip ())
109+ return "\n " .join (output_lines )
110+
111+ def gen_linux (arch , self_contained , args ):
112+
113+ # normalize to LLVM-style arch names
114+ if arch == "x64" :
115+ llvm_arch = "x86_64"
116+ elif arch == "arm64" :
117+ llvm_arch = "aarch64"
118+ elif arch == "arm" :
119+ llvm_arch = "armv7a"
120+ else :
121+ raise ValueError (f"Unsupported architecture: { arch } " )
122+
123+ llvm_target = llvm_arch + "-linux-gnu"
124+
125+ args .update ({
126+ "skia_use_harfbuzz" : False ,
127+ "skia_use_icu" : False ,
128+ "skia_use_piex" : True ,
129+ # "skia_use_sfntly": False, Not supported anymore
130+ "skia_use_system_expat" : False ,
131+ "skia_use_system_freetype2" : False ,
132+ "skia_use_system_libjpeg_turbo" : False ,
133+ "skia_use_system_libpng" : False ,
134+ "skia_use_system_libwebp" : False ,
135+ "skia_use_system_zlib" : False ,
136+ "skia_use_vulkan" : True ,
137+ "skia_use_x11" : False
138+ })
139+
140+
141+ args ["cc" ] = "clang"
142+ args ["cxx" ] = "clang++"
143+ args ["target_os" ] = "linux"
144+ args ["target_cpu" ] = arch
145+ args ["extra_cflags" ].extend ([
146+ "--target=" + llvm_target ,
147+ "--sysroot=/sysroots/" + llvm_target
148+ ])
149+ args ["extra_cflags_cc" ].extend ([
150+ "-stdlib=libc++"
151+ ])
152+ args ["extra_ldflags" ].extend ([
153+ "--target=" + llvm_target , "--sysroot=/sysroots/" + llvm_target ,
154+ "-rtlib=compiler-rt" ,
155+ "-stdlib=libc++" ,
156+ "-rtlib=compiler-rt" ,
157+ "-fuse-ld=lld" ,
158+ "-lc++" ,
159+ "-lc++abi" ,
160+ "-lunwind" ,
161+ "-lm" ,
162+ "-lc"
163+ ])
164+
165+
166+ def build_target (target_os , arch , self_contained , debug ):
167+ output_name = f"{ target_os } _{ arch } "
168+ if debug :
169+ output_name += "_debug"
170+ gn_dir = os .path .join ("out" , output_name )
171+ artifacts_dir = os .path .join (".." , "artifacts" , output_name )
172+
173+ args = {
174+ "is_debug" : debug ,
175+ "is_official_build" : not debug ,
176+ "extra_asmflags" : [],
177+ "skia_enable_tools" : False ,
178+ "extra_cflags" : ["-ffunction-sections" , "-fdata-sections" , "-fno-rtti" ],
179+ "extra_cflags_c" : [],
180+ "extra_cflags_cc" : [],
181+ "extra_ldflags" : [],
182+ "skia_enable_skottie" : True ,
183+ }
184+ if target_os == "linux" :
185+ gen_linux (arch , self_contained , args )
186+ else :
187+ print (f"Unsupported target OS: { target_os } " , file = sys .stderr )
188+ sys .exit (1 )
189+
190+ os .makedirs (gn_dir , exist_ok = True )
191+
192+ write_file (os .path .join (gn_dir , "args.gn" ), dict_to_gn (args ))
193+
194+ if not has_env_flag ("DEBUG_SKIP_BUILD" ):
195+ subprocess .run (['gn' , 'gen' , gn_dir ], check = True )
196+ subprocess .run (['ninja' , '-C' , gn_dir ], check = True )
197+
198+
199+ # recursively clear artifacts dir
200+ if os .path .exists (artifacts_dir ):
201+ shutil .rmtree (artifacts_dir )
202+
203+ os .makedirs (artifacts_dir , exist_ok = True )
204+
205+ # Copy .a files from gn_dir to artifacts_dir/lib
206+ lib_dir = os .path .join (artifacts_dir , "lib" )
207+ os .makedirs (lib_dir , exist_ok = True )
208+ copy_extension (gn_dir , lib_dir , ".a" )
209+ copy_extension (gn_dir , lib_dir , ".lib" )
210+
211+ write_file (os .path .join (artifacts_dir , "skia.h" ), generate_skia_h (gn_dir ))
212+ if has_env_flag ("SKIA_BUILDER_SAVE_SPACE" ):
213+ shutil .rmtree (gn_dir )
214+
215+ def main ():
216+ argv = sys .argv [1 :]
217+
218+ os .chdir ("skia" )
219+
220+ if argv [0 ] == "headers" :
221+ copy_headers ()
222+ sys .exit (0 )
223+
224+ if len (argv ) < 2 :
225+ print (f"Usage: " , file = sys .stderr )
226+ print (f"{ sys .argv [0 ]} <os> <arch> [--self-contained]" , file = sys .stderr )
227+ print (f"{ sys .argv [0 ]} headers" , file = sys .stderr )
228+ sys .exit (2 )
229+
230+ target_os = argv [0 ]
231+ arch = argv [1 ]
232+ args = argv [2 :]
233+ self_contained = "--self-contained" in args
234+ debug = "--debug" in args
235+
236+ # prepend ./depot_tools to PATH
237+ depot_tools_path = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." , "depot_tools" ))
238+ os .environ ["PATH" ] = depot_tools_path + os .pathsep + os .environ .get ("PATH" , "" )
239+
240+
241+
242+ if arch == "all" :
243+ for a in ["x64" , "arm64" ]:
244+ build_target (target_os , a , self_contained , debug )
245+ else :
246+ build_target (target_os , arch , self_contained , debug )
247+
248+ if __name__ == "__main__" :
249+ main ()
0 commit comments