#!/bin/sh
# GPL_3+
cat << 'EEE' > /dev/null
/* Copyright (C) 2021 Momi-g
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
EEE

cat << 'EEE' > /dev/null
#SH_doc
title=mglb section=1 repnl=\040
@name mglb
@_brief merge lib/obj by collecting only dependent objfiles
@_syno
	mglb [-hHV] [-p] basefile src1 [src2...]
@tl;dr
		@(code)@
	~$ mglb yourlib.a libXX.a libYY.a    # ag1:baselib ag2-:obj supplier
	  #>> outlib.a
	  
	~$ mglb yourobj.o libXX.a libZZ.o    # allow filetype: *.a / *.o
	  #>> outlib.a
		@()
@_opt
	@(list_o)
	-hHV: usage, version
	-p: posix mode. in default, rename symbolname with `objcopy(gnu)` command
	if detect collision. raise error if set -p opt.
	@()
@_desc
	'mglb' picks dependent objfiles from other libs and merge to a new static
	library. rename the collision symbols automatically if needs. --
	for exsample:
		@(code)--@
	here is two fat libs
	  libgoogle.a (100MB) api: f1(),f2(), ...regex(), ...	
	  libamazon.a (200MB) api: a1(),b1(),f1()...calc(), ...	
	  
	and you want to make wrapper lib, myregex() + mycalc()
	  libmylib.a (1MB) api: myregex(), mycalc() + dep

	1. write wrapper src
	//src.c
	char* regex(char* s);      //from libgoogle
	int   calc(int a, int b);  //from libamazon
	char* myregex(char* s){ return regex(s); }
	int   mycalc(int a, int b){ return calc(a,b); }

	2. make baselib
	~$ cc -c src.c		#>> src.o
	~$ ar -r libmyutil.a src.o		#>>libmyutil.a

	3. merge. mglb collects and adjusts implement objfiles
	~$ mglb libmyutil.a	libgoogle.a  libamazon.a	#>> outlib.a
	(...or ~$ mglb src.o libgoogle.a  libamazon.a )

	4. finish. rename func/symbol automatically if detect collosion.
	~$ nm outlib.a
	  #>> myregex(), mycalc(), regex(), calc(), f1(), amazon_f1()
	@()
@EXIT_STATUS
	suc/fail == 0/not 0

@notes
	@(code)@
	you may need to edit lib/obj symbols. gnu 'objcopy' will be useful.
	~$ objcopy -N main src.o	#>> remove 'main' symbol(==func) from src.o
	~$ objcopy -L f1 src.o	#>> change f1 symbol to local(static) value
	~$ objcopy --globalize-symbol f1 src.o  #>> change f1 symbol to global
	~$ objcopy --redefine-sym f1=g1 src.o   #>> change f1 symname to g1
	@()

@conforming_to	posix-shell (+ gnu objcopy)
@copyright
	Copyright (C) 2021 Momi-g --
@_ver 2021-11-16 v1.0.2
@_see 'objcopy(1)'
#SH_docE
EEE
usage(){
cat << 'EEE'
HowTo (mglb, merge lib/obj by collecting only dependent objfiles. GPLv3+)
opt: -h, -H(quick guide), -p(osix, dont edit symbol)	needs:nm, objcopy@gnu
------
eg) ~$ mglb yourlib.a libXX.a libYY.a    # ag1:baselib	ag2-:obj supplier
		#>> outlib.a
    ~$ mglb yourobj.o libXX.a libZZ.o    # allow filetype: *.a / *.o
		#>> outlib.a

  your.a/o       libXX.a/o       libYY.a/o            outlib.a             
   aa.o(dp AA.o)  AA.o(dp CCC.o)  AAA.o                1:aa.o  4:BB.o(libYY)
   bb.o(dp BB.o)  BB.o            BB.o (not typo)      2:AA.o  5:bb.o  7:cc.o
   cc.o(dp AAA.o) CC.o	          CCC.o(dp self BB.o)  3:CCC.o 6:BB_.o 8:AAA.o 	
                                                         
 - search order: self >> ag1 >> ag2 >> ...
 - edit: inner depenent symbol will be renamed to avoid collision if -p isnt.
  raise err and exit 1 if detect sym collision under the -p mode.
 
   libYY.a/o  outlib.a           : required sym(f3) is saved. inner dependant-
    AAA.o       -                : global symname is changed.
    BB.o (f2)  BB.o(libYY_BB_f2)  libYY.a :CCC.o: f3(){ ..int rc = f2() ... }
    CCC.o(f3)  CCC.o(f3)          outlib.a:CCC.o: f3(){..rc = libYY_BB_f2()..}
EEE
	exit 0
}

usage_H(){
 cat << 'E E'|sed -e'1d;$d'
/*--copyfrom mglb.1.txt*/
MGLB(1)                     General Commands Manual                    MGLB(1)



NAME
       mglb - merge lib/obj by collecting only dependent objfiles

SYNOPSIS
       mglb [-hHV] [-p] basefile src1 [src2...]

TL;DR
       ~$ mglb yourlib.a libXX.a libYY.a    # ag1:baselib ag2-:obj supplier
         #>> outlib.a

       ~$ mglb yourobj.o libXX.a libZZ.o    # allow filetype: *.a / *.o
         #>> outlib.a


OPTIONS
       -hHV   usage, version

       -p     posix mode. in default, rename symbolname with objcopy(gnu) com‐
              mand if detect collision. raise error if set -p opt.

DESCRIPTION
       'mglb' picks dependent objfiles from other libs  and  merge  to  a  new
       static library. rename the collision symbols automatically if needs.
       for exsample:

       here is two fat libs
         libgoogle.a (100MB) api: f1(),f2(), ...regex(), ...
         libamazon.a (200MB) api: a1(),b1(),f1()...calc(), ...

       and you want to make wrapper lib, myregex() + mycalc()
         libmylib.a (1MB) api: myregex(), mycalc() + dep

       1. write wrapper src
       //src.c
       char* regex(char* s);      //from libgoogle
       int   calc(int a, int b);  //from libamazon
       char* myregex(char* s){ return regex(s); }
       int   mycalc(int a, int b){ return calc(a,b); }

       2. make baselib
       ~$ cc -c src.c      #>> src.o
       ~$ ar -r libmyutil.a src.o         #>>libmyutil.a

       3. merge. mglb collects and adjusts implement objfiles
       ~$ mglb libmyutil.a libgoogle.a  libamazon.a #>> outlib.a
       (...or ~$ mglb src.o libgoogle.a  libamazon.a )

       4. finish. rename func/symbol automatically if detect collosion.
       ~$ nm outlib.a
         #>> myregex(), mycalc(), regex(), calc(), f1(), amazon_f1()


EXIT_STATUS
       suc/fail == 0/not 0

NOTES
       you may need to edit lib/obj symbols. gnu 'objcopy' will be useful.
       ~$ objcopy -N main src.o #>> remove 'main' symbol(==func) from src.o
       ~$ objcopy -L f1 src.o   #>> change f1 symbol to local(static) value
       ~$ objcopy --globalize-symbol f1 src.o  #>> change f1 symbol to global
       ~$ objcopy --redefine-sym f1=g1 src.o   #>> change f1 symname to g1


CONFORMING_TO
       posix-shell (+ gnu objcopy)

COPYRIGHT
       Copyright (C) 2021 Momi-g


VERSION
       2021-11-16 v1.0.2

SEE_ALSO
       'objcopy(1)'



                                                                       MGLB(1)
/*--copyend mglb.1.txt*/
E E
exit 0
}

version_info(){
cat << 'EEE'
mglb v1.0.2
Copyright (C) 2021 Momi-g
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
EEE
exit 0
}
filetest(){
 [ -f "$1" ]||err "ERR: file $1 not found"
 [ "${1##*.}" = a ]||[ "${1##*.}" = o ]||err "ERR: file $1 sfix must be .a or .o"
}

mglb_main(){
set -eu
tmpdir=""
pmode=0
[ $# = 0 ]&&usage

OPTIND=1
while getopts hHVp char
do
	case $char in
	h) usage;;
	H) usage_H;;
	V) version_info;;
	p) pmode=1;;
	'?') exit 1;;
	esac
done
shift $((OPTIND - 1))

#--test/init
cdir=`pwd`||err "ERR: pwd failed"
filetest "$1"
nm -h>/dev/null||err "ERR: nm failed"
[ -e outlib.a ]&&[ ! -f outlib.a ]&&err "ERR: outlib.a exists and not regular file"
tmpdir=`mktemp -d`||err "ERR: mktemp failed"
echo "make tmpdir: $tmpdir" >/dev/stderr
[ -f outlib.a ]&&rm outlib.a
[ ${1##*.} = "a" ]&&cp $1 outlib.a
[ ${1##*.} = "o" ]&&ar -r outlib.a $1
shift

#--coreloop $2-
for i;do
	cd "$cdir"
	#search req Usyms: output 3clm, myf a.o outlib_a_
	rq=`nmgr U outlib.a`
	buf=`nmgr '[^U]' outlib.a`
	rq=`cllr "$buf" "$rq"|awk 'NF!=0{print $1}'|sort -u`
	
	#detect Tsyms
	buf=`nmgr '[^U]' $i`
	addl=`cll "$rq" "$buf"`	# Tsymslist:  myf a.o outlib_a_

	[ "$addl" = "" ]&&continue
	if [ "$addl" != "" ]&&[ "${1##*.}" = o ]; then
		cp "$i" "$tmpdir"
		cd "$tmpdir"
		f_addobj "$addl" ""	# if file.o, add direct
		continue
	fi
	
	# if file.a, search recursive dependance
	addlsv="$addl"	#root request+resolved  syms [^U] 
	while :;do
		olist=`pl "$addl"|awk 'NF!=0{print $2}'|sort -u`	#追加予定の.oリスト
		# 内部依存調査のため追加予定.o全部から一括でUを集める
		# .ofileが候補だけど.aなのでarからUフィルタ+fileで全oの全Uをgrepする
		# nmgrは.oも.aも対応してる
		buf=`nmgr U $i|awk 'NF!=0{print $2 " " $1 " " $3}'`	#.o sym pre
		o_undef=`cll "$olist" "$buf"|awk 'NF!=0{print $2 " " $1 " " $3}'|sort -u`
		#cllはfld1 とfld2を検査して衝突ならag2のf2を出力。
		#具体的には追加予定の.oに未定義関数があるならその関数をリスト出力
		#sortのために$1 $2を触ってるが出力は同じになるようにしてる
		
		df=`nmgr '[^U]' $i`	#ar内の全Tリスト
		buf=`cll "$o_undef" "$df"`	# .o U をar Tでフィルタ >>衝突が内部依存追加分
		df=`pl "$addl" "$buf"|sort -u`	#break処理の関係で先に追加。NULL行ok
		[ "$df" = "$addl" ]&&break	#サチったら脱出
		addl="$df"
	done
	# addlは sym .o pfixの羅列 .oファイル名のみ取り出す addlも後で使う
	ofile=`pl "$addl"|awk '{print $2}'|sort -u`
# echo "@addl
# $addl
# @"
# echo "#addlsv
# $addlsv"
# echo "$ofile"
# exit
	cp $i $tmpdir
	cd "$tmpdir"
	ar -x $i
	rm `(ls;pl "$ofile")|sort|uniq -u`
	f_addobj "$addlsv" #svはreqなのでrenameしない奴リスト 追加処理は共通でrename
	# 衝突はsym名とfile名の二つがあるが、symをチェック>>fileチェックの純になる
done

rm -R "$tmpdir"
echo "del tmpdir: $tmpdir" >/dev/stderr
}

err(){
echo "`basename $0`: $*" >/dev/stderr;
cd "$cdir"
[ "$tmpdir" = "" ]||rm -rf "$tmpdir"
exit 1
}
pl()(printf '%s\n' "$@")

f_addobj(){
	# cwd == /tmp/XXX + *.o exists
	addlsv="$1"	# req orgsyms list(3fld): sym lib_file_sym.o lib_file_ (pfix)
	#libは.aならつくし.oならつかない
	buf=""
	# add pfix(libXX_file_) if supply is *.a
	fpfix=`pl "$addlsv"|awk '{print $3}'|head -n 1`	#pfixの取得 誰でもよかった
	lpfix="${fpfix%_*_}_"

	#下でpfix系だけど.oは_headになるのでよろしくない。空っぽにしておく
	[ $# = 2 ]&&lpfix=""

# --sym rename
	# all [^U] sym/file/pfix list. rename target
	buf=`ls *.o`
	buf=`nmgr '[^U]' $buf|sort -u`
	# remove orgsym from list and change 3fld >> 2fld, new old rep
	buf=`(cllr "$addlsv" "$buf")|sort|uniq -u|awk '{print $1 " " $1 " " $3 $1}'`
	# remove '_' system name sym from list
	addsym=`pl "$buf"|grep -v ^_`
	booked=`nmgr '[^U]' $cdir/outlib.a|sort -u|awk '{$3="";print}'`
	# cmpr symname coll and add '_' suffix if hit
	while :;do
		ck=`cll "$booked" "$addsym"`
		[ ${#ck} = 0 ]&&break
		[ $pmode = 1 ]&&err "ERR: detect symbol collision under posix mode: "$ck

		sf=`cllr "$booked" "$addsym"|sort -u`	# sv no coll
		buf=`pl "$ck"|awk '{$1=$3;$3=$3 "_";print $0}'`	# add suffix if hit
		addsym=`pl "$buf" "$sf"|sort -u`	# bind + loop until uniq all
	done
	optstr=`pl "$buf"|awk '{print "--redefine-sym " $2 "=" $1}'|sort -u|tr '\n' ' '`

	# adopt new symname to all objfiles
	ar -r outlib_.a *.o
	[ $pmode = 0 ] && objcopy $optstr outlib_.a
	ar -x outlib_.a
	rm outlib_.a
	echo "del outlib_.a" >/dev/stderr

#---file_rename add. edit file.o name if detect collision
	for bi in *.o;do
		add="$lpfix"
		bfile="$bi"
		while :;do
			buf=`ar -t "$cdir/"outlib.a "$bi" 2>/dev/null`
			[ ${#buf} = 0 ]&&break
			if [ "$add" = "" ];then
				while [ -f $bi ];do bi="${bi%.o}_.o"; done	#変更が先住民を潰すかも
				mv $bfile $bi
				bfile=$bi
				continue
			fi
			bi="$add$bi"
			add=""
			while [ -f $bi ];do bi="${bi%.o}_.o"; done
			mv $bfile $bi
			bfile=$bi
		done
		ar -r "$cdir/"outlib.a "$bi"
	done
	rm *.o
}

# name fid の二項目ならなる ar1 ag2をuniq-dして $1が衝突ならag2を出力
# uq1 ag1 ag2 #>> myf xxx.a[fc.o] 
cll()(pl "$1" "" "$2"| awk 'NF==0{m=1} m==0{ar[$1]=1;next} $1 in ar{print $0}')
cllr()(pl "$1" "" "$2"|awk 'NF==0{m=1} m==0{ar[$1]=1;next} !($1 in ar){print $0}')

# nmgr [^U] *.o	...lib.aからTの fcname fname lib_aobj.oを吐く
# myf a.o outlib_a_ ラストはfunc,fileの頭で即rename完了になる形にしてる
# obj.oの場合は$3が_単体になる
# ファイル名, 関数名、rename候補を準備しておく重要関数
# $1はfld3のタイプ指定 基本はUかそれ以外のg系のみ扱う
# グローバルシンボルで未定義とここで定義の切り分けと抽出
nmgr(){
tp="$1";shift
nm "$@" -AgP|awk -v tp="$tp" '$3~tp{print $2 " " $1}'|
#grep -v ^_|
sort -u|tr '][:' '@@ '|sed -e 's/\([^[:blank:]@]*\)@\([^@]*\)@/\2 \1_\2/g'|
sed -e 's/[.][ao]//g'|awk 'NF==2{$0=$0 " " $2}{$2=$2 ".o";$3=$3 "_"}{print}'
}
#最初にtgtのobj.aoからUをリストする _系の関数名は避ける
#tgtがaだったらTを調べて存在すればUから削除

mglb_main "$@"		#SH_MAIN



cat<<'EEE'>/dev/null
 change log
 --
2021-11-16  Momi-g	<dmy@dmy.dmy>

	* mglb(filetest): bugfix ${1##.a} >> ${1##*.a}

2021-09-07  Momi-g	<dmy@dmy.dmy>

	* mglb(all): set brp

2021-04-23  Momi-g	<dmy@dmy.dmy>

	* mglb(all) : add -p opt. fix err. fix help.

EEE


