2015-06-12 23:03:31 +00:00
#!/usr/bin/env python3
2017-11-19 13:29:49 +00:00
" Script to generate a build order respecting package dependencies. "
2015-06-12 23:03:31 +00:00
2022-06-18 19:50:23 +00:00
import json , os , re , sys
2015-06-12 23:03:31 +00:00
2015-12-24 06:52:06 +00:00
from itertools import filterfalse
2023-04-28 00:43:18 +00:00
termux_arch = os . getenv ( ' TERMUX_ARCH ' ) or ' aarch64 '
2023-09-09 14:22:47 +00:00
termux_global_library = os . getenv ( ' TERMUX_GLOBAL_LIBRARY ' ) or ' false '
termux_pkg_library = os . getenv ( ' TERMUX_PACKAGE_LIBRARY ' ) or ' bionic '
2023-04-28 00:43:18 +00:00
2015-12-24 06:52:06 +00:00
def unique_everseen ( iterable , key = None ) :
2017-11-19 13:29:49 +00:00
""" List unique elements, preserving order. Remember all elements ever seen.
See https : / / docs . python . org / 3 / library / itertools . html #itertools-recipes
Examples :
unique_everseen ( ' AAAABBBCCDAABBB ' ) - - > A B C D
unique_everseen ( ' ABBCcAD ' , str . lower ) - - > A B C D """
2015-12-24 06:52:06 +00:00
seen = set ( )
seen_add = seen . add
if key is None :
for element in filterfalse ( seen . __contains__ , iterable ) :
seen_add ( element )
yield element
else :
for element in iterable :
k = key ( element )
if k not in seen :
seen_add ( k )
yield element
2015-12-24 03:43:35 +00:00
def die ( msg ) :
2017-11-19 13:29:49 +00:00
" Exit the process with an error message. "
2015-12-24 03:43:35 +00:00
sys . exit ( ' ERROR: ' + msg )
2015-06-12 23:03:31 +00:00
2023-02-14 04:24:04 +00:00
def parse_build_file_dependencies_with_vars ( path , vars ) :
" Extract the dependencies specified in the given variables of a build.sh or *.subpackage.sh file. "
2017-11-19 13:29:49 +00:00
dependencies = [ ]
with open ( path , encoding = " utf-8 " ) as build_script :
for line in build_script :
2023-02-14 04:24:04 +00:00
if line . startswith ( vars ) :
2019-01-13 17:45:40 +00:00
dependencies_string = line . split ( ' DEPENDS= ' ) [ 1 ]
for char in " \" ' \n " :
dependencies_string = dependencies_string . replace ( char , ' ' )
2017-11-19 13:29:49 +00:00
2019-01-13 17:45:40 +00:00
# Split also on '|' to dependencies with '|', as in 'nodejs | nodejs-current':
for dependency_value in re . split ( ' ,| \\ | ' , dependencies_string ) :
# Replace parenthesis to ignore version qualifiers as in "gcc (>= 5.0)":
dependency_value = re . sub ( r ' \ (.*? \ ) ' , ' ' , dependency_value ) . strip ( )
2022-12-21 20:36:04 +00:00
arch = os . getenv ( ' TERMUX_ARCH ' )
2023-04-07 01:04:09 +00:00
if arch is None :
arch = ' aarch64 '
2022-12-21 20:36:04 +00:00
if arch == " x86_64 " :
arch = " x86-64 "
dependency_value = re . sub ( r ' \ $ { TERMUX_ARCH/_/-} ' , arch , dependency_value )
2015-12-24 06:20:47 +00:00
2019-01-13 17:45:40 +00:00
dependencies . append ( dependency_value )
2015-12-24 06:20:47 +00:00
2017-11-19 13:29:49 +00:00
return set ( dependencies )
2015-12-24 06:20:47 +00:00
2023-02-14 04:24:04 +00:00
def parse_build_file_dependencies ( path ) :
" Extract the dependencies of a build.sh or *.subpackage.sh file. "
return parse_build_file_dependencies_with_vars ( path , ( ' TERMUX_PKG_DEPENDS ' , ' TERMUX_PKG_BUILD_DEPENDS ' , ' TERMUX_SUBPKG_DEPENDS ' , ' TERMUX_PKG_DEVPACKAGE_DEPENDS ' ) )
def parse_build_file_antidependencies ( path ) :
" Extract the antidependencies of a build.sh file. "
return parse_build_file_dependencies_with_vars ( path , ' TERMUX_PKG_ANTI_BUILD_DEPENDS ' )
2023-04-28 00:43:18 +00:00
def parse_build_file_excluded_arches ( path ) :
2023-07-05 01:59:05 +00:00
" Extract the excluded arches specified in a build.sh or *.subpackage.sh file. "
2023-04-28 00:43:18 +00:00
arches = [ ]
with open ( path , encoding = " utf-8 " ) as build_script :
for line in build_script :
2023-07-05 01:59:05 +00:00
if line . startswith ( ( ' TERMUX_PKG_BLACKLISTED_ARCHES ' , ' TERMUX_SUBPKG_EXCLUDED_ARCHES ' ) ) :
2023-04-28 00:43:18 +00:00
arches_string = line . split ( ' ARCHES= ' ) [ 1 ]
for char in " \" ' \n " :
arches_string = arches_string . replace ( char , ' ' )
for arches_value in re . split ( ' , ' , arches_string ) :
2023-04-30 20:16:08 +00:00
arches . append ( arches_value . strip ( ) )
2023-04-28 00:43:18 +00:00
return set ( arches )
2023-09-20 08:25:22 +00:00
def parse_build_file_variable_bool ( path , var ) :
2024-04-12 12:47:25 +00:00
value = ' false '
2023-09-20 08:25:22 +00:00
with open ( path , encoding = " utf-8 " ) as build_script :
for line in build_script :
if line . startswith ( var ) :
2024-04-12 12:47:25 +00:00
value = line . split ( ' = ' ) [ - 1 ] . replace ( ' \n ' , ' ' )
2023-09-20 08:25:22 +00:00
break
2024-04-12 12:47:25 +00:00
return value == ' true '
def add_prefix_glibc_to_pkgname ( name ) :
return name . replace ( " -static " , " -glibc-static " ) if " static " == name . split ( " - " ) [ - 1 ] else name + " -glibc "
2023-09-20 08:25:22 +00:00
2015-12-24 05:04:28 +00:00
class TermuxPackage ( object ) :
2017-11-19 13:29:49 +00:00
" A main package definition represented by a directory with a build.sh file. "
2019-03-20 16:54:44 +00:00
def __init__ ( self , dir_path , fast_build_mode ) :
2017-11-04 00:18:32 +00:00
self . dir = dir_path
2023-09-20 08:25:22 +00:00
self . fast_build_mode = fast_build_mode
2017-11-04 00:18:32 +00:00
self . name = os . path . basename ( self . dir )
2023-09-09 14:22:47 +00:00
self . pkgs_cache = [ ]
2023-08-30 20:40:08 +00:00
if " gpkg " in self . dir . split ( " / " ) [ - 2 ] . split ( " - " ) and " glibc " not in self . name . split ( " - " ) :
2024-04-12 12:47:25 +00:00
self . name = add_prefix_glibc_to_pkgname ( self . name )
2015-12-24 06:20:47 +00:00
# search package build.sh
build_sh_path = os . path . join ( self . dir , ' build.sh ' )
if not os . path . isfile ( build_sh_path ) :
2017-11-04 00:18:32 +00:00
raise Exception ( " build.sh not found for package ' " + self . name + " ' " )
2015-12-24 06:20:47 +00:00
2017-11-19 13:29:49 +00:00
self . deps = parse_build_file_dependencies ( build_sh_path )
2023-02-14 04:24:04 +00:00
self . antideps = parse_build_file_antidependencies ( build_sh_path )
2023-04-28 00:43:18 +00:00
self . excluded_arches = parse_build_file_excluded_arches ( build_sh_path )
2023-09-20 08:25:22 +00:00
self . only_installing = parse_build_file_variable_bool ( build_sh_path , ' TERMUX_PKG_ONLY_INSTALLING ' )
self . separate_subdeps = parse_build_file_variable_bool ( build_sh_path , ' TERMUX_PKG_SEPARATE_SUB_DEPENDS ' )
2024-04-12 12:47:25 +00:00
self . accept_dep_scr = parse_build_file_variable_bool ( build_sh_path , ' TERMUX_PKG_ACCEPT_PKG_IN_DEP ' )
2019-08-06 12:39:42 +00:00
2023-09-09 14:22:47 +00:00
if os . getenv ( ' TERMUX_ON_DEVICE_BUILD ' ) == " true " and termux_pkg_library == " bionic " :
2019-08-06 12:39:42 +00:00
always_deps = [ ' libc++ ' ]
for dependency_name in always_deps :
if dependency_name not in self . deps and self . name not in always_deps :
self . deps . add ( dependency_name )
2015-12-24 06:20:47 +00:00
# search subpackages
self . subpkgs = [ ]
for filename in os . listdir ( self . dir ) :
2017-11-19 13:29:49 +00:00
if not filename . endswith ( ' .subpackage.sh ' ) :
continue
2017-11-04 00:18:32 +00:00
subpkg = TermuxSubPackage ( self . dir + ' / ' + filename , self )
2023-07-05 01:59:05 +00:00
if termux_arch in subpkg . excluded_arches :
continue
2015-12-24 06:20:47 +00:00
self . subpkgs . append ( subpkg )
2023-02-14 04:24:04 +00:00
2019-08-12 19:48:55 +00:00
subpkg = TermuxSubPackage ( self . dir + ' / ' + self . name + ' -static ' + ' .subpackage.sh ' , self , virtual = True )
self . subpkgs . append ( subpkg )
2017-11-19 13:29:49 +00:00
self . needed_by = set ( ) # Populated outside constructor, reverse of deps.
2015-12-24 06:20:47 +00:00
def __repr__ ( self ) :
return " < {} ' {} ' > " . format ( self . __class__ . __name__ , self . name )
2023-09-20 08:25:22 +00:00
def recursive_dependencies ( self , pkgs_map , dir_root = None ) :
2017-11-19 13:29:49 +00:00
" All the dependencies of the package, both direct and indirect. "
result = [ ]
2023-09-20 08:25:22 +00:00
is_root = dir_root == None
if is_root :
dir_root = self . dir
2024-04-12 12:47:25 +00:00
if is_root or not self . fast_build_mode or not self . separate_subdeps :
2023-09-20 08:25:22 +00:00
for subpkg in self . subpkgs :
if f " { self . name } -static " != subpkg . name :
self . deps . add ( subpkg . name )
self . deps | = subpkg . deps
self . deps - = self . antideps
self . deps . discard ( self . name )
if not self . fast_build_mode or self . dir == dir_root :
self . deps . difference_update ( [ subpkg . name for subpkg in self . subpkgs ] )
2017-11-19 13:29:49 +00:00
for dependency_name in sorted ( self . deps ) :
2023-09-09 14:22:47 +00:00
if termux_global_library == " true " and termux_pkg_library == " glibc " and " glibc " not in dependency_name . split ( " - " ) :
2024-04-12 12:47:25 +00:00
mod_dependency_name = add_prefix_glibc_to_pkgname ( dependency_name )
2023-09-09 14:22:47 +00:00
dependency_name = mod_dependency_name if mod_dependency_name in pkgs_map else dependency_name
if dependency_name not in self . pkgs_cache :
self . pkgs_cache . append ( dependency_name )
2023-08-30 20:40:08 +00:00
dependency_package = pkgs_map [ dependency_name ]
2024-04-12 12:47:25 +00:00
if dependency_package . dir != dir_root and dependency_package . only_installing and not self . fast_build_mode :
2023-09-20 08:25:22 +00:00
continue
result + = dependency_package . recursive_dependencies ( pkgs_map , dir_root )
2024-04-12 12:47:25 +00:00
if dependency_package . accept_dep_scr or dependency_package . dir != dir_root :
2023-09-20 08:25:22 +00:00
result + = [ dependency_package ]
2017-11-19 13:29:49 +00:00
return unique_everseen ( result )
2015-12-24 06:20:47 +00:00
2017-11-19 13:29:49 +00:00
class TermuxSubPackage :
" A sub-package represented by a $ {PACKAGE_NAME} .subpackage.sh file. "
2019-03-20 18:46:03 +00:00
def __init__ ( self , subpackage_file_path , parent , virtual = False ) :
2015-12-24 06:20:47 +00:00
if parent is None :
raise Exception ( " SubPackages should have a parent " )
2017-11-04 00:18:32 +00:00
self . name = os . path . basename ( subpackage_file_path ) . split ( ' .subpackage.sh ' ) [ 0 ]
2023-09-02 09:20:16 +00:00
if " gpkg " in subpackage_file_path . split ( " / " ) [ - 3 ] . split ( " - " ) and " glibc " not in self . name . split ( " - " ) :
2024-04-12 12:47:25 +00:00
self . name = add_prefix_glibc_to_pkgname ( self . name )
2015-12-24 06:20:47 +00:00
self . parent = parent
2019-04-05 22:45:58 +00:00
self . deps = set ( [ parent . name ] )
2023-09-20 08:25:22 +00:00
self . only_installing = parent . only_installing
2024-04-12 12:47:25 +00:00
self . accept_dep_scr = parent . accept_dep_scr
2023-07-05 01:59:05 +00:00
self . excluded_arches = set ( )
2019-04-05 22:45:58 +00:00
if not virtual :
self . deps | = parse_build_file_dependencies ( subpackage_file_path )
2023-07-05 01:59:05 +00:00
self . excluded_arches | = parse_build_file_excluded_arches ( subpackage_file_path )
2019-03-02 22:34:54 +00:00
self . dir = parent . dir
self . needed_by = set ( ) # Populated outside constructor, reverse of deps.
2015-12-24 06:20:47 +00:00
def __repr__ ( self ) :
return " < {} ' {} ' parent= ' {} ' > " . format ( self . __class__ . __name__ , self . name , self . parent )
2015-12-24 03:43:35 +00:00
2023-09-20 08:25:22 +00:00
def recursive_dependencies ( self , pkgs_map , dir_root = None ) :
2019-03-02 22:34:54 +00:00
""" All the dependencies of the subpackage, both direct and indirect.
Only relevant when building in fast - build mode """
result = [ ]
2023-09-20 08:25:22 +00:00
if dir_root == None :
dir_root = self . dir
2019-03-02 22:34:54 +00:00
for dependency_name in sorted ( self . deps ) :
2019-03-20 16:54:44 +00:00
if dependency_name == self . parent . name :
self . parent . deps . discard ( self . name )
2019-03-02 22:34:54 +00:00
dependency_package = pkgs_map [ dependency_name ]
2019-03-26 12:21:44 +00:00
if dependency_package not in self . parent . subpkgs :
2023-09-20 08:25:22 +00:00
result + = dependency_package . recursive_dependencies ( pkgs_map , dir_root = dir_root )
2024-04-12 12:47:25 +00:00
if dependency_package . accept_dep_scr or dependency_package . dir != dir_root :
2023-09-20 08:25:22 +00:00
result + = [ dependency_package ]
2019-03-02 22:34:54 +00:00
return unique_everseen ( result )
2022-06-18 19:50:23 +00:00
def read_packages_from_directories ( directories , fast_build_mode , full_buildmode ) :
2017-11-19 13:29:49 +00:00
""" Construct a map from package name to TermuxPackage.
2019-03-02 22:34:54 +00:00
Subpackages are mapped to the parent package if fast_build_mode is false . """
2017-11-19 13:29:49 +00:00
pkgs_map = { }
2017-11-04 00:18:32 +00:00
all_packages = [ ]
2017-11-19 13:29:49 +00:00
2022-06-18 19:50:23 +00:00
if full_buildmode :
# Ignore directories and get all folders from repo.json file
with open ( ' repo.json ' ) as f :
data = json . load ( f )
2023-08-30 20:40:08 +00:00
directories = [ ]
for d in data . keys ( ) :
if d != " pkg_format " :
directories . append ( d )
2022-06-18 19:50:23 +00:00
2017-11-19 13:29:49 +00:00
for package_dir in directories :
2017-11-04 00:18:32 +00:00
for pkgdir_name in sorted ( os . listdir ( package_dir ) ) :
dir_path = package_dir + ' / ' + pkgdir_name
if os . path . isfile ( dir_path + ' /build.sh ' ) :
2019-03-20 16:54:44 +00:00
new_package = TermuxPackage ( package_dir + ' / ' + pkgdir_name , fast_build_mode )
2015-12-24 03:47:38 +00:00
2023-04-28 00:43:18 +00:00
if termux_arch in new_package . excluded_arches :
continue
2017-11-19 13:29:49 +00:00
if new_package . name in pkgs_map :
die ( ' Duplicated package: ' + new_package . name )
else :
pkgs_map [ new_package . name ] = new_package
all_packages . append ( new_package )
2015-12-24 06:20:47 +00:00
2017-11-19 13:29:49 +00:00
for subpkg in new_package . subpkgs :
2023-07-05 01:59:05 +00:00
if termux_arch in subpkg . excluded_arches :
continue
2017-11-19 13:29:49 +00:00
if subpkg . name in pkgs_map :
die ( ' Duplicated package: ' + subpkg . name )
2019-03-02 22:34:54 +00:00
elif fast_build_mode :
pkgs_map [ subpkg . name ] = subpkg
2017-11-19 13:29:49 +00:00
else :
pkgs_map [ subpkg . name ] = new_package
all_packages . append ( subpkg )
2015-12-24 03:47:38 +00:00
2017-11-19 13:29:49 +00:00
for pkg in all_packages :
for dependency_name in pkg . deps :
if dependency_name not in pkgs_map :
die ( ' Package %s depends on non-existing package " %s " ' % ( pkg . name , dependency_name ) )
dep_pkg = pkgs_map [ dependency_name ]
2019-03-02 22:34:54 +00:00
if fast_build_mode or not isinstance ( pkg , TermuxSubPackage ) :
2017-11-19 13:29:49 +00:00
dep_pkg . needed_by . add ( pkg )
return pkgs_map
2015-12-24 06:20:47 +00:00
2017-11-19 13:29:49 +00:00
def generate_full_buildorder ( pkgs_map ) :
" Generate a build order for building all packages. "
2015-12-24 06:52:06 +00:00
build_order = [ ]
2015-12-24 05:04:28 +00:00
# List of all TermuxPackages without dependencies
2015-12-24 06:20:47 +00:00
leaf_pkgs = [ pkg for name , pkg in pkgs_map . items ( ) if not pkg . deps ]
if not leaf_pkgs :
die ( ' No package without dependencies - where to start? ' )
2015-12-24 03:47:38 +00:00
2016-09-16 09:48:02 +00:00
# Sort alphabetically:
pkg_queue = sorted ( leaf_pkgs , key = lambda p : p . name )
2015-12-24 03:47:38 +00:00
# Topological sorting
2015-12-24 06:20:47 +00:00
visited = set ( )
2017-11-19 13:29:49 +00:00
# Tracks non-visited deps for each package
remaining_deps = { }
for name , pkg in pkgs_map . items ( ) :
remaining_deps [ name ] = set ( pkg . deps )
for subpkg in pkg . subpkgs :
remaining_deps [ subpkg . name ] = set ( subpkg . deps )
2015-12-24 06:20:47 +00:00
while pkg_queue :
pkg = pkg_queue . pop ( 0 )
if pkg . name in visited :
continue
# print("Processing {}:".format(pkg.name), pkg.needed_by)
2015-12-24 06:34:27 +00:00
visited . add ( pkg . name )
2015-12-24 03:47:38 +00:00
build_order . append ( pkg )
2015-12-24 06:20:47 +00:00
for other_pkg in sorted ( pkg . needed_by , key = lambda p : p . name ) :
2015-12-24 06:34:27 +00:00
# Remove this pkg from deps
2015-12-24 06:20:47 +00:00
remaining_deps [ other_pkg . name ] . discard ( pkg . name )
# ... and all its subpackages
remaining_deps [ other_pkg . name ] . difference_update (
[ subpkg . name for subpkg in pkg . subpkgs ]
)
2015-12-24 06:34:27 +00:00
if not remaining_deps [ other_pkg . name ] : # all deps were already appended?
pkg_queue . append ( other_pkg ) # should be processed
2015-12-24 06:20:47 +00:00
if set ( pkgs_map . values ( ) ) != set ( build_order ) :
2015-12-24 03:47:38 +00:00
print ( " ERROR: Cycle exists. Remaining: " )
2015-12-24 06:20:47 +00:00
for name , pkg in pkgs_map . items ( ) :
2015-12-24 03:47:38 +00:00
if pkg not in build_order :
2015-12-24 06:20:47 +00:00
print ( name , remaining_deps [ name ] )
2015-12-24 03:47:38 +00:00
sys . exit ( 1 )
2015-12-24 06:52:06 +00:00
return build_order
2015-12-24 03:47:38 +00:00
2019-03-02 22:34:54 +00:00
def generate_target_buildorder ( target_path , pkgs_map , fast_build_mode ) :
2017-11-19 13:29:49 +00:00
" Generate a build order for building the dependencies of the specified package. "
if target_path . endswith ( ' / ' ) :
target_path = target_path [ : - 1 ]
2015-12-24 06:52:06 +00:00
2017-11-19 13:29:49 +00:00
package_name = os . path . basename ( target_path )
2023-08-30 20:40:08 +00:00
if " gpkg " in target_path . split ( " / " ) [ - 2 ] . split ( " - " ) and " glibc " not in package_name . split ( " - " ) :
package_name + = " -glibc "
2017-11-19 13:29:49 +00:00
package = pkgs_map [ package_name ]
2019-03-20 16:54:44 +00:00
# Do not depend on any sub package
if fast_build_mode :
package . deps . difference_update ( [ subpkg . name for subpkg in package . subpkgs ] )
2017-11-19 13:29:49 +00:00
return package . recursive_dependencies ( pkgs_map )
2015-12-24 06:29:34 +00:00
2017-11-19 13:29:49 +00:00
def main ( ) :
" Generate the build order either for all packages or a specific one. "
2019-03-02 22:34:54 +00:00
import argparse
parser = argparse . ArgumentParser ( description = ' Generate order in which to build dependencies for a package. Generates ' )
parser . add_argument ( ' -i ' , default = False , action = ' store_true ' ,
help = ' Generate dependency list for fast-build mode. This includes subpackages in output since these can be downloaded. ' )
parser . add_argument ( ' package ' , nargs = ' ? ' ,
help = ' Package to generate dependency list for. ' )
parser . add_argument ( ' package_dirs ' , nargs = ' * ' ,
2022-04-16 06:50:09 +00:00
help = ' Directories with packages. Can for example point to " ../community-packages/packages " . Note that the packages suffix is no longer added automatically if not present. ' )
2019-03-02 22:34:54 +00:00
args = parser . parse_args ( )
fast_build_mode = args . i
package = args . package
2019-03-02 22:47:13 +00:00
packages_directories = args . package_dirs
2019-03-02 22:34:54 +00:00
if not package :
full_buildorder = True
else :
full_buildorder = False
if fast_build_mode and full_buildorder :
die ( ' -i mode does not work when building all packages ' )
2017-11-04 00:18:32 +00:00
if not full_buildorder :
2019-03-02 22:34:54 +00:00
for path in packages_directories :
2017-11-04 00:18:32 +00:00
if not os . path . isdir ( path ) :
die ( ' Not a directory: ' + path )
2019-03-02 22:34:54 +00:00
if package :
if package [ - 1 ] == " / " :
package = package [ : - 1 ]
if not os . path . isdir ( package ) :
die ( ' Not a directory: ' + package )
if not os . path . relpath ( os . path . dirname ( package ) , ' . ' ) in packages_directories :
packages_directories . insert ( 0 , os . path . dirname ( package ) )
2022-06-18 19:50:23 +00:00
pkgs_map = read_packages_from_directories ( packages_directories , fast_build_mode , full_buildorder )
2015-12-24 06:29:34 +00:00
2017-11-04 00:18:32 +00:00
if full_buildorder :
2017-11-19 13:29:49 +00:00
build_order = generate_full_buildorder ( pkgs_map )
2015-12-24 06:52:06 +00:00
else :
2019-03-02 22:34:54 +00:00
build_order = generate_target_buildorder ( package , pkgs_map , fast_build_mode )
2015-12-24 06:29:34 +00:00
2017-11-19 13:29:49 +00:00
for pkg in build_order :
2023-08-30 20:40:08 +00:00
pkg_name = pkg . name
2023-09-09 14:22:47 +00:00
if termux_global_library == " true " and termux_pkg_library == " glibc " and " glibc " not in pkg_name . split ( " - " ) :
2024-04-12 12:47:25 +00:00
pkg_name = add_prefix_glibc_to_pkgname ( pkgname )
2023-08-30 20:40:08 +00:00
print ( " %-30s %s " % ( pkg_name , pkg . dir ) )
2017-11-19 13:29:49 +00:00
if __name__ == ' __main__ ' :
main ( )