1# Copyright (C) 2010, 2011, 2012, 2013, 2014, 2018, 2020 Olga Yakovleva <yakovleva.o.v@gmail.com> 2 3# This program is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 3 of the License, or 6# (at your option) any later version. 7 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12 13# You should have received a copy of the GNU General Public License 14# along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16import sys 17import os 18import os.path 19import subprocess 20import platform 21import datetime 22import re 23if sys.platform=="win32": 24 if sys.version_info[0]>=3: 25 import winreg 26 else: 27 import _winreg as winreg 28 29def get_version(is_release): 30 next_version="1.2.3" 31 return next_version 32 33def CheckPKGConfig(context): 34 context.Message("Checking for pkg-config... ") 35 result=context.TryAction("pkg-config --version")[0] 36 context.Result(result) 37 return result 38 39def CheckPKG(context,name): 40 context.Message("Checking for {}... ".format(name)) 41 result=context.TryAction("pkg-config --exists '{}'".format(name))[0] 42 context.Result(result) 43 return result 44 45def CheckMSVC(context): 46 context.Message("Checking for Visual C++ ... ") 47 result=0 48 version=context.env.get("MSVC_VERSION",None) 49 if version is not None: 50 result=1 51 context.Result(result) 52 return result 53 54def CheckXPCompat(context): 55 context.Message("Checking for Windows XP compatibility ... ") 56 result=0 57 if context.env.get("xp_compat_enabled",False): 58 result=1 59 context.Result(result) 60 return result 61 62def CheckNSIS(context): 63 result=0 64 context.Message("Checking for NSIS") 65 if "NSISDIR" in os.environ: 66 context.env["makensis"]=File(os.path.join(os.environ["NSISDIR"],"makensis.exe")) 67 result=1 68 else: 69 key_name=r"SOFTWARE\NSIS" 70 try: 71 with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,key_name,0,winreg.KEY_READ|winreg.KEY_WOW64_32KEY) as key: 72 context.env["makensis"]=File(os.path.join(winreg.QueryValueEx(key,None)[0],"makensis.exe")) 73 result=1 74 except WindowsError: 75 pass 76 context.Result(result) 77 return result 78 79def CheckWiX(context): 80 result=0 81 context.Message("Checking for WiX toolset") 82 if "WIXTOOLPATH" in os.environ or "WIX" in os.environ: 83 if "WIXTOOLPATH"in os.environ: 84 context.env["WIX"]=os.environ["WIXTOOLPATH"] 85 else: 86 context.env["WIX"]=os.path.join(os.environ["WIX"],"bin") 87 result=1 88 context.Result(result) 89 return result 90 91def validate_spd_version(key,val,env): 92 m=re.match(r"^\d+\.\d+",val) 93 if m is None: 94 raise Exception("Invalid value of spd_version: {}".format(val)) 95 96def CheckSpdVersion(ctx): 97 ctx.Message("Checking Speech Dispatcher version ... ") 98 ver=ctx.env.get("spd_version",None) 99 if ver is not None: 100 ctx.Result(ver) 101 return ver 102 res, ver=ctx.TryAction("pkg-config --modversion speech-dispatcher > $TARGET") 103 ver=ver.strip() 104 if not res: 105 src='#include <stdio.h>\n#include <speech-dispatcher/libspeechd_version.h>\nint main() {\nint major=LIBSPEECHD_MAJOR_VERSION;\nint minor=LIBSPEECHD_MINOR_VERSION;\nprintf("%d.%d",major,minor);\nreturn 0;}' 106 res,ver=ctx.TryRun(src,".c") 107 if not res: 108 ctx.Result(res) 109 return res 110 ctx.env["spd_version"]=ver 111 ctx.Result(ver) 112 return ver 113 114def convert_flags(value): 115 return value.split() 116 117def convert_path(value): 118 return value.split(";" if sys.platform=="win32" else ":") 119 120def setup(): 121 if sys.platform=="win32": 122 SetOption("warn","no-visual-c-missing") 123 global BUILDDIR,var_cache 124 system=platform.system().lower() 125 BUILDDIR=os.path.join("build",system) 126 var_cache=os.path.join(BUILDDIR,"user.conf") 127 Execute(Mkdir(BUILDDIR)) 128 SConsignFile(os.path.join(BUILDDIR,"scons")) 129 130def create_languages_user_var(): 131 langs_dir=Dir("#data").Dir("languages") 132 names=[name for name in sorted(os.listdir(langs_dir.path)) if os.path.isdir(langs_dir.Entry(name).path)] 133 langs=[name.lower() for name in names] 134 name_map=dict(zip(names,langs)) 135 def_langs=langs 136 if sys.platform!="win32": 137 def_langs=[lang for lang in langs if lang not in["georgian"]] 138 print("Georgian language is skipped because of non-free license") 139 help="Which languages to install" 140 return ListVariable("languages",help,def_langs,langs,name_map) 141 142def create_audio_libs_user_var(): 143 libs=["pulse","libao","portaudio"] 144 help="Which audio libraries to use if they are available" 145 return ListVariable("audio_libs",help,libs,libs) 146 147def create_user_vars(): 148 args={"DESTDIR":""} 149 args.update(ARGUMENTS) 150 vars=Variables(var_cache,args) 151 vars.Add(BoolVariable("dev","The build will only be used for development: no global installation, run from the source directory, compile helper utilities",False)) 152 vars.Add(create_languages_user_var()) 153 vars.Add(BoolVariable("enable_mage","Build with MAGE",True)) 154 vars.Add(create_audio_libs_user_var()) 155 vars.Add("spd_version","Speech dispatcher version",validator=validate_spd_version) 156 vars.Add(BoolVariable("release","Whether we are building a release",True)) 157 if sys.platform=="win32": 158 vars.Add(BoolVariable("enable_x64","Additionally build 64-bit versions of all the libraries",True)) 159 vars.Add(BoolVariable("enable_xp_compat","Target Windows XP",False)) 160 vars.Add(PathVariable("msi_repo","Where the msi packages are kept for reuse",None,PathVariable.PathIsDir)) 161 else: 162 vars.Add("prefix","Installation prefix","/usr/local") 163 vars.Add("bindir","Program installation directory","$prefix/bin") 164 vars.Add("libdir","Library installation directory","$prefix/lib") 165 vars.Add("includedir","Header installation directory","$prefix/include") 166 vars.Add("datadir","Data installation directory","$prefix/share") 167 vars.Add("sysconfdir","A directory for configuration files","$prefix/etc") 168 vars.Add("servicedir",".service file installation directory","$datadir/dbus-1/services") 169 vars.Add("DESTDIR","Support for staged installation","") 170 vars.Add(BoolVariable("enable_shared","Build a shared library",True)) 171 if sys.platform=="win32": 172 suffixes=["32","64"] 173 else: 174 suffixes=[""] 175 for suffix in suffixes: 176 vars.Add("CPPPATH"+suffix,"List of directories where to search for headers",[],converter=convert_path) 177 vars.Add("LIBPATH"+suffix,"List of directories where to search for libraries",[],converter=convert_path) 178 vars.Add("CPPFLAGS","C/C++ preprocessor flags",[],converter=convert_flags) 179 vars.Add("CCFLAGS","C/C++ compiler flags",["/O2","/GL","/Gw"] if sys.platform=="win32" else ["-O2"],converter=convert_flags) 180 vars.Add("CFLAGS","C compiler flags",[],converter=convert_flags) 181 vars.Add("CXXFLAGS","C++ compiler flags",[],converter=convert_flags) 182 vars.Add("LINKFLAGS","Linker flags",["/LTCG","/OPT:REF","/OPT:ICF"] if sys.platform=="win32" else [],converter=convert_flags) 183 return vars 184 185def create_base_env(user_vars): 186 env_args={"variables":user_vars} 187 if sys.platform=="win32": 188 env_args["tools"]=["newlines"] 189 else: 190 env_args["tools"]=["default","installer"] 191 env_args["tools"].extend(["textfile","library"]) 192 env_args["LIBS"]=[] 193 env_args["package_name"]="RHVoice" 194 env_args["CPPDEFINES"]=[("RHVOICE","1")] 195 env=Environment(**env_args) 196 if env["dev"]: 197 env["prefix"]=os.path.abspath("local") 198 env["RPATH"]=env.Dir("$libdir").abspath 199 env["package_version"]=get_version(env["release"]) 200 env.Append(CPPDEFINES=("PACKAGE",env.subst(r'\"$package_name\"'))) 201 if env["PLATFORM"]=="win32": 202 env.Append(CPPDEFINES=("WIN32",1)) 203 env.Append(CPPDEFINES=("UNICODE",1)) 204 env.Append(CPPDEFINES=("NOMINMAX",1)) 205 env["libcore"]="RHVoice_core" 206 env["libaudio"]="RHVoice_audio" 207 return env 208 209def display_help(env,vars): 210 Help("Type 'scons' to build the package.\n") 211 if sys.platform!="win32": 212 Help("Then type 'scons install' to install it.\n") 213 Help("Type 'scons --clean install' to uninstall the software.\n") 214 Help("You may use the following configuration variables:\n") 215 Help(vars.GenerateHelpText(env)) 216 217def clone_base_env(base_env,user_vars,arch=None): 218 args={} 219 if sys.platform=="win32": 220 if arch is not None: 221 args["TARGET_ARCH"]=arch 222 args["tools"]=["msvc","mslink","mslib"] 223 env=base_env.Clone(**args) 224 user_vars.Update(env) 225 if env["PLATFORM"]=="win32": 226 env.AppendUnique(CCFLAGS=["/nologo","/MT"]) 227 env.AppendUnique(LINKFLAGS=["/nologo"]) 228 env.AppendUnique(CXXFLAGS=["/EHsc"]) 229 if env["enable_xp_compat"]: 230 env.Tool("xp_compat") 231 if "gcc" in env["TOOLS"]: 232 env.MergeFlags("-pthread") 233 env.AppendUnique(CXXFLAGS=["-std=c++11"]) 234 env.AppendUnique(CFLAGS=["-std=c11"]) 235 if sys.platform=="win32": 236 bits="64" if arch.endswith("64") else "32" 237 env["BUILDDIR"]=os.path.join(BUILDDIR,arch) 238 env["CPPPATH"]=env["CPPPATH"+bits] 239 env["LIBPATH"]=env["LIBPATH"+bits] 240 else: 241 env["BUILDDIR"]=BUILDDIR 242 third_party_dir=os.path.join("src","third-party") 243 for path in Glob(os.path.join(third_party_dir,"*"),strings=True): 244 if os.path.isdir(path): 245 env.Prepend(CPPPATH=("#"+path)) 246 env.Prepend(CPPPATH=(os.path.join("#"+env["BUILDDIR"],"include"),".",os.path.join("#src","include"))) 247 return env 248 249def configure(env): 250 tests={"CheckPKGConfig":CheckPKGConfig,"CheckPKG":CheckPKG,"CheckSpdVersion":CheckSpdVersion} 251 if env["PLATFORM"]=="win32": 252 tests["CheckMSVC"]=CheckMSVC 253 tests["CheckXPCompat"]=CheckXPCompat 254 conf=env.Configure(conf_dir=os.path.join(env["BUILDDIR"],"configure_tests"), 255 log_file=os.path.join(env["BUILDDIR"],"configure.log"), 256 config_h=os.path.join(env["BUILDDIR"],"include","configure.h"), 257 custom_tests=tests) 258 if env["PLATFORM"]=="win32": 259 if not conf.CheckMSVC(): 260 print("Error: Visual C++ is not installed") 261 exit(1) 262 print("Visual C++ version is {}".format(env["MSVC_VERSION"])) 263 if env["enable_xp_compat"] and not conf.CheckXPCompat(): 264 print("Error: Windows XP compatibility cannot be enabled") 265 exit(1) 266 if not conf.CheckCC(): 267 print("The C compiler is not working") 268 exit(1) 269 if not conf.CheckCXX(): 270 print("The C++ compiler is not working") 271 exit(1) 272# has_sox=conf.CheckLibWithHeader("sox","sox.h","C",call='sox_init();',autoadd=0) 273# if not has_sox: 274# print("Error: cannot link with libsox") 275# exit(1) 276# env.PrependUnique(LIBS="sox") 277 has_giomm=False 278 has_pkg_config=conf.CheckPKGConfig() 279 if has_pkg_config: 280 if "pulse" in env["audio_libs"] and not False: 281 env["audio_libs"].remove("pulse") 282 if "libao" in env["audio_libs"] and not conf.CheckPKG("ao"): 283 env["audio_libs"].remove("libao") 284 if "portaudio" in env["audio_libs"] and not False: 285 env["audio_libs"].remove("portaudio") 286 if env["audio_libs"]: 287 conf.CheckSpdVersion() 288 else: 289 env["audio_libs"]=[] 290# has_giomm=conf.CheckPKG("giomm-2.4") 291 if env["PLATFORM"]=="win32": 292 env.AppendUnique(LIBS="kernel32") 293 conf.Finish() 294 env.Prepend(LIBPATH=os.path.join("#"+env["BUILDDIR"],"core")) 295 src_subdirs=["third-party","core","lib"] 296 if env["dev"]: 297 src_subdirs.append("utils") 298 src_subdirs.append("audio") 299 src_subdirs.append("test") 300 if env["audio_libs"]: 301 src_subdirs.append("sd_module") 302 env.Prepend(LIBPATH=os.path.join("#"+env["BUILDDIR"],"audio")) 303 if has_giomm: 304 src_subdirs.append("service") 305 if env["PLATFORM"]=="win32": 306 src_subdirs.append("sapi") 307 else: 308 src_subdirs.append("include") 309 return src_subdirs 310 311def build_binaries(base_env,user_vars,arch=None): 312 env=clone_base_env(base_env,user_vars,arch) 313 if env["BUILDDIR"]!=BUILDDIR: 314 Execute(Mkdir(env["BUILDDIR"])) 315 if arch: 316 print("Configuring the build system for {}".format(arch)) 317 src_subdirs=configure(env) 318 for subdir in src_subdirs: 319 SConscript(os.path.join("src",subdir,"SConscript"), 320 variant_dir=os.path.join(env["BUILDDIR"],subdir), 321 exports={"env":env}, 322 duplicate=0) 323 324def build_for_linux(base_env,user_vars): 325 build_binaries(base_env,user_vars) 326 for subdir in ["data","config"]: 327 SConscript(os.path.join(subdir,"SConscript"),exports={"env":base_env}, 328 variant_dir=os.path.join(BUILDDIR,subdir), 329 duplicate=0) 330 331def preconfigure_for_windows(env): 332 conf=env.Configure(conf_dir=os.path.join(BUILDDIR,"configure_tests"), 333 log_file=os.path.join(BUILDDIR,"configure.log"), 334 custom_tests={"CheckNSIS":CheckNSIS,"CheckWiX":CheckWiX}) 335 conf.CheckWiX() 336 conf.CheckNSIS() 337 conf.Finish() 338 339def build_for_windows(base_env,user_vars): 340 preconfigure_for_windows(base_env) 341 build_binaries(base_env,user_vars,"x86") 342 if base_env["enable_x64"]: 343 build_binaries(base_env,user_vars,"x86_64") 344 if "WIX" in base_env: 345 SConscript(os.path.join("src","wininst","SConscript"), 346 variant_dir=os.path.join(BUILDDIR,"wininst"), 347 exports={"env":base_env}, 348 duplicate=0) 349 SConscript(os.path.join("data","SConscript"), 350 variant_dir=os.path.join(BUILDDIR,"data"), 351 exports={"env":base_env}, 352 duplicate=0) 353 docs=["README.md"]+[os.path.join("licenses",name) for name in os.listdir("licenses") if name!="voices"] 354 for f in docs: 355 base_env.ConvertNewlines(os.path.join(BUILDDIR,f),f) 356 base_env.ConvertNewlinesB(os.path.join(BUILDDIR,"RHVoice.ini"),os.path.join("config","RHVoice.conf")) 357 # env.ConvertNewlinesB(os.path.join(BUILDDIR,"dict.txt"),os.path.join("config","dicts","example.txt")) 358 SConscript(os.path.join("src","nvda-synthDriver","SConscript"), 359 variant_dir=os.path.join(BUILDDIR,"nvda-synthDriver"), 360 exports={"env":base_env}, 361 duplicate=0) 362 363setup() 364vars=create_user_vars() 365base_env=create_base_env(vars) 366display_help(base_env,vars) 367vars.Save(var_cache,base_env) 368if sys.platform=="win32": 369 build_for_windows(base_env,vars) 370else: 371 build_for_linux(base_env,vars) 372