2023-08-16 20:34:21 +00:00
|
|
|
#!/usr/bin/env sh
|
2023-09-25 01:19:30 +00:00
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2023-10-13 08:16:41 +00:00
|
|
|
# SPDX-FileCopyrightText: 2022-2023 Alper Nebi Yasak <alpernebiyasak@gmail.com>
|
2023-09-25 01:19:30 +00:00
|
|
|
# SPDX-FileCopyrightText: 2022 Ferass El Hafidi <vitali64pmemail@protonmail.com>
|
2023-10-07 05:55:10 +00:00
|
|
|
# SPDX-FileCopyrightText: 2023 Leah Rowe <leah@libreboot.org>
|
2023-08-16 20:34:21 +00:00
|
|
|
|
|
|
|
set -u -e
|
|
|
|
|
2023-08-23 17:56:31 +00:00
|
|
|
. "include/err.sh"
|
2023-09-27 20:46:20 +00:00
|
|
|
. "include/option.sh"
|
2023-10-07 05:55:10 +00:00
|
|
|
. "include/git.sh"
|
|
|
|
|
2023-12-27 15:18:21 +00:00
|
|
|
eval "$(setvars "" xarch cfgsdir codedir config config_name xlang mode \
|
2023-12-30 16:03:29 +00:00
|
|
|
elfdir listfile project target target_dir targets tree _f target1 \
|
2023-12-30 19:25:26 +00:00
|
|
|
bootstrapargs autoconfargs cmakedir makeargs autogenargs)"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
2023-08-16 20:34:21 +00:00
|
|
|
main()
|
|
|
|
{
|
2023-10-20 03:10:50 +00:00
|
|
|
while getopts f:b:m:u:c:x:s:l:n: option; do
|
2023-10-07 05:55:10 +00:00
|
|
|
_f="${1}"
|
|
|
|
case "${1}" in
|
|
|
|
-b) : ;;
|
|
|
|
-u) mode="oldconfig" ;;
|
|
|
|
-m) mode="menuconfig" ;;
|
|
|
|
-c) mode="distclean" ;;
|
|
|
|
-x) mode="crossgcc-clean" ;;
|
|
|
|
-f) mode="fetch" ;;
|
2023-10-13 08:16:41 +00:00
|
|
|
-s) mode="savedefconfig" ;;
|
|
|
|
-l) mode="olddefconfig" ;;
|
|
|
|
-n) mode="nconfig" ;;
|
2023-10-07 05:55:10 +00:00
|
|
|
*) err "Invalid option" ;;
|
|
|
|
esac
|
|
|
|
shift; project="${OPTARG#src/}"; shift
|
|
|
|
done
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -z "$_f" ] && err "missing flag (-m/-u/-b/-c/-x/-f/-s/-l/-n)"
|
|
|
|
[ -z "$project" ] && err "project name not specified"
|
2023-12-30 13:08:29 +00:00
|
|
|
elfdir="elf/${project}"
|
2023-12-30 16:03:29 +00:00
|
|
|
cfgsdir="config/${project}"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
2024-01-01 15:02:34 +00:00
|
|
|
remkdir "${tmpgit%/*}"
|
2023-12-23 08:43:42 +00:00
|
|
|
check_project
|
|
|
|
|
2023-12-22 13:05:32 +00:00
|
|
|
_cmd="build_projects"
|
|
|
|
[ -f "config/${project}/build.list" ] && _cmd="build_targets"
|
|
|
|
$_cmd $@
|
2023-10-07 05:55:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
build_projects()
|
|
|
|
{
|
2023-12-24 09:04:36 +00:00
|
|
|
[ $# -gt 0 ] && x_ ./update trees $_f $@
|
2023-12-27 17:26:37 +00:00
|
|
|
[ "$mode" = "fetch" ] && eval "fetch_project_repo; return 0;"
|
2023-08-27 13:14:49 +00:00
|
|
|
|
2023-12-30 16:46:49 +00:00
|
|
|
load_project_config "$cfgsdir"
|
2023-12-30 16:03:29 +00:00
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
codedir="src/${project}"
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -d "$codedir" ] || x_ ./update trees -f "$project"
|
2023-10-06 21:59:36 +00:00
|
|
|
|
2023-12-24 09:04:36 +00:00
|
|
|
[ "$mode" = "distclean" ] && mode="clean"
|
2023-10-07 05:55:10 +00:00
|
|
|
run_make_command || return 0
|
2023-08-16 20:34:21 +00:00
|
|
|
}
|
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
build_targets()
|
2023-10-03 12:21:30 +00:00
|
|
|
{
|
2023-12-24 09:04:36 +00:00
|
|
|
[ "$elfdir" = "elf/coreboot" ] && \
|
2023-10-10 05:04:47 +00:00
|
|
|
elfdir="elf/coreboot_nopayload_DO_NOT_FLASH"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -d "$cfgsdir" ] || err "directory, $cfgsdir, does not exist"
|
2023-10-03 12:21:30 +00:00
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
listfile="${cfgsdir}/build.list"
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -f "$listfile" ] || err "list file, $listfile, does not exist"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
|
|
|
# Build for all targets if no argument is given
|
2023-12-24 09:04:36 +00:00
|
|
|
[ $# -gt 0 ] && target1="$1"
|
|
|
|
[ "$target1" = "utils" ] && [ "$project" = "coreboot" ] && \
|
2023-10-07 05:55:10 +00:00
|
|
|
shift 1
|
2023-12-24 09:04:36 +00:00
|
|
|
targets=$(items "$cfgsdir") || \
|
|
|
|
err "Cannot get options for $cfgsdir"
|
2023-10-03 12:21:30 +00:00
|
|
|
[ $# -gt 0 ] && targets=$@
|
|
|
|
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -z "$mode" ] && x_ mkdir -p "$elfdir"
|
2023-10-07 05:55:10 +00:00
|
|
|
handle_targets
|
|
|
|
}
|
|
|
|
|
|
|
|
handle_targets()
|
|
|
|
{
|
2023-12-24 09:04:36 +00:00
|
|
|
for x in $targets; do
|
2023-10-07 05:55:10 +00:00
|
|
|
target="${x}"
|
|
|
|
printf "Running 'make %s' for project '%s, target '%s''\n" \
|
2023-12-24 09:04:36 +00:00
|
|
|
"$mode" "$project" "$target"
|
|
|
|
[ "$project" != "coreboot" ] || [ -n "$mode" ] || \
|
|
|
|
[ "$target1" = "utils" ] || \
|
|
|
|
x_ ./vendor download $target
|
2023-10-07 05:55:10 +00:00
|
|
|
x_ handle_defconfig
|
|
|
|
done
|
|
|
|
|
2023-12-22 13:05:32 +00:00
|
|
|
[ "$target1" = "utils" ] && [ "$project" = "coreboot" ] && return 0
|
2023-12-28 16:34:45 +00:00
|
|
|
[ -z "${mode}" ] && printf "Done! Check %s/\n\n" "$elfdir"; return 0
|
2023-10-03 12:21:30 +00:00
|
|
|
}
|
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
handle_defconfig()
|
2023-08-16 20:34:21 +00:00
|
|
|
{
|
2023-12-24 09:04:36 +00:00
|
|
|
handle_src_tree "$target" || return 0
|
2023-08-16 20:34:21 +00:00
|
|
|
|
2023-12-27 17:26:37 +00:00
|
|
|
[ "$target1" = "utils" ] && [ "$project" = "coreboot" ] && \
|
|
|
|
eval "handle_coreboot_utils \"$tree\"; return 0"
|
2023-08-16 20:34:21 +00:00
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
for y in "${target_dir}/config"/*; do
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -f "$y" ] || continue
|
|
|
|
config="$y"
|
2023-12-16 07:56:26 +00:00
|
|
|
config_name="${config#"${target_dir}/config/"}"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
|
|
|
printf "handle/make/config %s %s: handling config %s\n" \
|
2023-12-24 09:04:36 +00:00
|
|
|
"$project" "$target" "$config_name"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -n "$mode" ] || check_config || continue
|
2023-10-07 05:55:10 +00:00
|
|
|
handle_makefile
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -n "$mode" ] || copy_elf
|
2023-10-07 05:55:10 +00:00
|
|
|
done
|
|
|
|
}
|
|
|
|
|
|
|
|
handle_src_tree()
|
|
|
|
{
|
|
|
|
target_dir="${cfgsdir}/${target}"
|
2023-12-27 17:26:37 +00:00
|
|
|
[ "$mode" = "fetch" ] && eval "fetch_project_trees; return 1;"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
2023-12-30 16:46:49 +00:00
|
|
|
load_project_config "$target_dir"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
2023-12-30 16:03:29 +00:00
|
|
|
x_ mkdir -p "${elfdir}/${target}"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -z "$tree" ] && err "handle_src_tree $project/$tree: tree unset"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
|
|
|
codedir="src/${project}/${tree}"
|
|
|
|
|
2023-12-24 09:04:36 +00:00
|
|
|
if [ ! -d "$codedir" ]; then
|
|
|
|
if [ "$mode" = "distclean" ] || \
|
|
|
|
[ "$mode" = "crossgcc-clean" ]; then
|
2023-12-24 06:32:19 +00:00
|
|
|
printf "Directory %s missing; skipping clean\n" \
|
2023-12-24 09:04:36 +00:00
|
|
|
"$codedir" 1>&2
|
2023-10-07 05:55:10 +00:00
|
|
|
return 1
|
|
|
|
fi
|
2023-12-24 09:04:36 +00:00
|
|
|
x_ ./update trees -f "$project" "$target"
|
2023-10-07 05:55:10 +00:00
|
|
|
fi
|
|
|
|
|
2023-12-22 13:05:32 +00:00
|
|
|
[ "$target1" = "utils" ] && [ "$project" = "coreboot" ] && return 0
|
2023-12-28 16:34:45 +00:00
|
|
|
[ -z "$mode" ] && check_cross_compiler; return 0
|
2023-08-16 20:34:21 +00:00
|
|
|
}
|
|
|
|
|
2023-12-30 16:46:49 +00:00
|
|
|
load_project_config()
|
2023-12-30 16:03:29 +00:00
|
|
|
{
|
|
|
|
eval "$(setvars "" xarch xlang tree bootstrapargs autoconfargs)"
|
|
|
|
|
|
|
|
[ -f "${1}/target.cfg" ] || return 0
|
|
|
|
. "${1}/target.cfg" || \
|
2023-12-30 16:46:49 +00:00
|
|
|
err "load_project_config ${1}: cannot load target.cfg"; return 0
|
2023-12-30 16:03:29 +00:00
|
|
|
}
|
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
check_cross_compiler()
|
2023-08-16 20:34:21 +00:00
|
|
|
{
|
2023-12-27 15:18:21 +00:00
|
|
|
for _xarch in $xarch; do
|
|
|
|
cbdir="src/coreboot/${tree}"
|
|
|
|
[ "$project" != "coreboot" ] && cbdir="src/coreboot/default"
|
2023-12-21 10:48:07 +00:00
|
|
|
|
2023-12-27 15:18:21 +00:00
|
|
|
x_ ./update trees -f coreboot ${cbdir#src/coreboot/}
|
2023-12-21 10:48:07 +00:00
|
|
|
|
2023-12-27 15:18:21 +00:00
|
|
|
export PATH="${PWD}/${cbdir}/util/crossgcc/xgcc/bin:$PATH"
|
|
|
|
export CROSS_COMPILE="${xarch% *}-"
|
|
|
|
[ -n "${xlang}" ] && export BUILD_LANGUAGES="$xlang"
|
2023-08-16 20:34:21 +00:00
|
|
|
|
2023-12-27 15:18:21 +00:00
|
|
|
[ -d "${cbdir}/util/crossgcc/xgcc/${_xarch}/" ] && continue
|
|
|
|
x_ make -C "$cbdir" crossgcc-${_xarch%-*} CPUS=$(nproc)
|
2023-12-21 10:48:07 +00:00
|
|
|
done
|
2023-10-07 05:55:10 +00:00
|
|
|
}
|
2023-08-16 20:34:21 +00:00
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
check_config()
|
|
|
|
{
|
2023-12-28 16:34:45 +00:00
|
|
|
[ -f "$config" ] || err "check_config: ${project}/${target}: no config"
|
2023-10-07 05:55:10 +00:00
|
|
|
|
|
|
|
dest_dir="${elfdir}/${target}/${config_name}"
|
|
|
|
# TODO: very hacky check. do it properly (based on build.list)
|
|
|
|
for elftest in "${dest_dir}"/*; do
|
2023-12-24 09:04:36 +00:00
|
|
|
[ -f "$elftest" ] || continue
|
2023-10-07 05:55:10 +00:00
|
|
|
printf "Build already exists, so skipping build\n" 1>&2
|
|
|
|
return 1
|
2023-08-16 20:34:21 +00:00
|
|
|
done
|
2023-12-24 09:04:36 +00:00
|
|
|
x_ mkdir -p "$dest_dir"
|
2023-08-16 20:34:21 +00:00
|
|
|
}
|
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
handle_makefile()
|
2023-08-16 20:34:21 +00:00
|
|
|
{
|
2023-12-24 09:04:36 +00:00
|
|
|
x_ make clean -C "$codedir"
|
|
|
|
x_ cp "$config" "${codedir}/.config"
|
|
|
|
[ -n "$mode" ] || make -C "$codedir" silentoldconfig || \
|
|
|
|
make -C "$codedir" oldconfig || :
|
2023-10-07 05:55:10 +00:00
|
|
|
|
2023-12-24 09:04:36 +00:00
|
|
|
run_make_command || err "handle_makefile $codedir: no makefile!"
|
2023-10-02 04:21:20 +00:00
|
|
|
|
2023-12-24 09:04:36 +00:00
|
|
|
if [ -e "${codedir}/.git" ] && [ "$project" = "u-boot" ] && \
|
|
|
|
[ "$mode" = "distclean" ]; then
|
|
|
|
x_ git -C "$codedir" clean -fdx
|
2023-12-22 13:05:32 +00:00
|
|
|
elif [ "$mode" = "oldconfig" ] || [ "$mode" = "olddefconfig" ] || \
|
|
|
|
[ "$mode" = "menuconfig" ] || [ "$mode" = "nconfig" ]; then
|
2023-12-24 09:04:36 +00:00
|
|
|
x_ cp "${codedir}/.config" "$config"
|
|
|
|
elif [ "$mode" = "savedefconfig" ]; then
|
|
|
|
x_ cp "${codedir}/defconfig" "$config"
|
2023-10-07 05:55:10 +00:00
|
|
|
fi
|
2023-08-16 20:34:21 +00:00
|
|
|
}
|
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
run_make_command()
|
2023-08-16 20:34:21 +00:00
|
|
|
{
|
update/trees: generic cmake handling
it is no longer hardcoded just to be handled for uefiextract.
it is now defined as cmakedir in target.cfg, for a single or
multi tree project. if multi tree, it is applied to the specific
tree, and has to be defined per tree
the way it works is: as per cmakelist, a project will define
which directory is to be built, and it will then generate
a makefile in the main source tree (the build tree in cmake
language, where the main CMakeLists.txt file exists)
when the makefile has been generated, the project is then treated
like any other project. the way cmake works, if a makefile has
already been generated by it, in a given directory, running it
again will fail and not affect anything; if it fails but the
makefile doesn't exist, then something is wrong, but if the
makefile does exist, then it's all fine and nothing happens
at present, this is only used for uefiextract, which is part
of src/uefitool
Signed-off-by: Leah Rowe <leah@libreboot.org>
2023-12-30 16:52:34 +00:00
|
|
|
check_cmake "$codedir"
|
2023-12-30 13:08:29 +00:00
|
|
|
[ -z "$mode" ] && check_autoconf "$codedir"
|
|
|
|
check_makefile "$codedir" || return 1
|
|
|
|
|
2023-12-30 20:30:24 +00:00
|
|
|
[ "$project" = "coreboot" ] && [ -z "$mode" ] && x_ \
|
|
|
|
printf "%s\n" "${version%%-*}" > "$codedir/.coreboot-version"
|
2023-08-21 23:34:15 +00:00
|
|
|
|
2023-12-30 19:25:26 +00:00
|
|
|
make $mode -j$(nproc) $makeargs -C "$codedir" || \
|
2023-12-30 13:08:29 +00:00
|
|
|
err "run_make $codedir: !make $mode"
|
|
|
|
|
2023-12-24 09:04:36 +00:00
|
|
|
[ "$mode" != "clean" ] && return 0
|
|
|
|
make -C "$codedir" distclean 2>/dev/null || :
|
2023-08-16 20:34:21 +00:00
|
|
|
}
|
|
|
|
|
update/trees: generic cmake handling
it is no longer hardcoded just to be handled for uefiextract.
it is now defined as cmakedir in target.cfg, for a single or
multi tree project. if multi tree, it is applied to the specific
tree, and has to be defined per tree
the way it works is: as per cmakelist, a project will define
which directory is to be built, and it will then generate
a makefile in the main source tree (the build tree in cmake
language, where the main CMakeLists.txt file exists)
when the makefile has been generated, the project is then treated
like any other project. the way cmake works, if a makefile has
already been generated by it, in a given directory, running it
again will fail and not affect anything; if it fails but the
makefile doesn't exist, then something is wrong, but if the
makefile does exist, then it's all fine and nothing happens
at present, this is only used for uefiextract, which is part
of src/uefitool
Signed-off-by: Leah Rowe <leah@libreboot.org>
2023-12-30 16:52:34 +00:00
|
|
|
check_cmake()
|
|
|
|
{
|
|
|
|
[ -z "${cmakedir}" ] || \
|
|
|
|
check_makefile "${1}" || \
|
|
|
|
cmake -B "${1}" "${1}/${cmakedir}" || \
|
|
|
|
check_makefile "${1}" || \
|
|
|
|
err "check_cmake ${1}: can't cmake ${cmakedir}"
|
|
|
|
[ -z "${cmakedir}" ] || check_makefile "${1}" || \
|
|
|
|
err "check_cmake ${1}: could not generate Makefile"
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2023-12-30 13:08:29 +00:00
|
|
|
check_autoconf()
|
|
|
|
{
|
|
|
|
(
|
|
|
|
_cfgopt=""
|
update/trees: generic cmake handling
it is no longer hardcoded just to be handled for uefiextract.
it is now defined as cmakedir in target.cfg, for a single or
multi tree project. if multi tree, it is applied to the specific
tree, and has to be defined per tree
the way it works is: as per cmakelist, a project will define
which directory is to be built, and it will then generate
a makefile in the main source tree (the build tree in cmake
language, where the main CMakeLists.txt file exists)
when the makefile has been generated, the project is then treated
like any other project. the way cmake works, if a makefile has
already been generated by it, in a given directory, running it
again will fail and not affect anything; if it fails but the
makefile doesn't exist, then something is wrong, but if the
makefile does exist, then it's all fine and nothing happens
at present, this is only used for uefiextract, which is part
of src/uefitool
Signed-off-by: Leah Rowe <leah@libreboot.org>
2023-12-30 16:52:34 +00:00
|
|
|
cd "${1}" || err "!cd $1"
|
2023-12-30 16:03:29 +00:00
|
|
|
[ -f "bootstrap" ] && x_ ./bootstrap $bootstrapargs
|
2023-12-30 19:25:26 +00:00
|
|
|
[ -f "autogen.sh" ] && x_ ./autogen.sh ${autogenargs}
|
2023-12-30 16:03:29 +00:00
|
|
|
[ -f "configure" ] && x_ ./configure $autoconfargs; return 0
|
update/trees: generic cmake handling
it is no longer hardcoded just to be handled for uefiextract.
it is now defined as cmakedir in target.cfg, for a single or
multi tree project. if multi tree, it is applied to the specific
tree, and has to be defined per tree
the way it works is: as per cmakelist, a project will define
which directory is to be built, and it will then generate
a makefile in the main source tree (the build tree in cmake
language, where the main CMakeLists.txt file exists)
when the makefile has been generated, the project is then treated
like any other project. the way cmake works, if a makefile has
already been generated by it, in a given directory, running it
again will fail and not affect anything; if it fails but the
makefile doesn't exist, then something is wrong, but if the
makefile does exist, then it's all fine and nothing happens
at present, this is only used for uefiextract, which is part
of src/uefitool
Signed-off-by: Leah Rowe <leah@libreboot.org>
2023-12-30 16:52:34 +00:00
|
|
|
) || err "can't bootstrap project: $1"
|
2023-12-30 13:08:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
check_makefile()
|
|
|
|
{
|
|
|
|
[ -f "${1}/Makefile" ] || [ -f "${1}/makefile" ] || \
|
|
|
|
[ -f "${1}/GNUmakefile" ] || return 1; return 0
|
|
|
|
}
|
|
|
|
|
2023-10-07 05:55:10 +00:00
|
|
|
copy_elf()
|
2023-08-16 20:34:21 +00:00
|
|
|
{
|
2023-12-16 07:56:26 +00:00
|
|
|
while read -r f; do
|
2023-10-07 05:55:10 +00:00
|
|
|
[ ! -f "${codedir}/$f" ] || \
|
2023-12-24 09:04:36 +00:00
|
|
|
x_ cp "${codedir}/${f}" "$dest_dir"
|
|
|
|
done < "$listfile"
|
|
|
|
x_ make clean -C "$codedir"
|
much, much stricter, more verbose error handling
lbmk is much more likely to crash now, in error conditions,
which is a boon for further auditing.
also: in "fetch", remove the downloaded program
if fail() was called.
this would also be done for gnulib, when downloading
grub, but done in such a way that gnulib goes first.
where calls to err write "ERROR" in the string, they
no longer say "ERROR" because the "err" function itself
now does that automatically.
also: listmodes/listoptions (in "lbmk") now reports an
error if no scripts and/or directories are found.
also: where a warning is given, but not an error, i've
gone through in some places and redirected the output
to stderr, not stdout
as part of error checks: running anything as root, except
for the "./build dependencies *" commands, is no longer
permitted and lbmk will throw an error
mrc downloads: debugfs output no longer redirected to /dev/null,
and stderr no longer redirected to stdout. everything is verbose.
certain non-error states are also more verbose. for example,
patch_rom in blobs/inject will now state when injection succeeds
certain actual errors(bugs) were fixed:
for example, build/release/roms now correctly prepares the blobs
hash files for a given target, containing only the files and
checksums in the list. Previously, a printf message was included.
Now, with this new code: blobutil/inject rightly verifies hashes.
doing all of this in one giant patch is cleaner
than 100 patches changing each file. even this is yet part
of a much larger audit going on in the Libreboot project.
Signed-off-by: Leah Rowe <leah@libreboot.org>
2023-08-24 19:19:41 +00:00
|
|
|
}
|
|
|
|
|
2023-08-16 20:34:21 +00:00
|
|
|
main $@
|