#!/bin/bash # This is a helper for rpm which collects 'Provides' and 'Requires' information from OCaml files. # It reads a list of filenames from STDIN. # It expects as argument either '--provides|-P' or '--requires|-R'. # Additional optional arguments are: # -f "ocamlobjinfo command" # -c # ignored, recognized just for compat reasons # -i NAME # omit the Requires/Provides for this bytecode unit name # -x NAME # omit the Requires/Provides for this native unit name # # OCaml object files contain either bytecode or native code. # Each bytecode variant provides a certain interface, which is represented by a hash. # Each native variant provides a certain interface and a certain implementation, which are represented by hashes. # Each variant may also require a certain interface and/or implementation provided by other files. # The details for each file can be inspected with 'ocamlobjinfo'. # # Each file contains at least one module. # Information about each module follows after a line starting with "Name:" or "Unit name:": # # cma/cmi/cmo (bytecode): # Unit name: NAME # Interfaces imported: # HASH NAME # HASH NAME_FROM_OTHER_MODULE # # cmx/cmxa/cmxs (native): # Name: NAME # CRC of implementation: HASH # Interfaces imported: # HASH NAME # HASH NAME_FROM_OTHER_MODULE # Implementations imported: # HASH NAME_FROM_OTHER_MODULE # # cmxs files are recoqnized, but need to be ignored. # They contain references of the interfaces and implementations # compiled into them. # # The hash may contain just '-', in which case it is ignored. # # Output: # ocaml(NAME) = HASH # for interfaces (bytecode and native) # ocamlx(NAME) = HASH # for implementations (native) set -e # OCAMLOBJINFO=ocamlobjinfo rpm_prefix_interface='ocaml' rpm_prefix_implementation='ocamlx' # parse() { local filename="$1" ${OCAMLOBJINFO} "${filename}" | awk ' BEGIN { debug=0 mode=ENVIRON["mode"] RPM_BUILD_ROOT=ENVIRON["RPM_BUILD_ROOT"] rpm_prefix_interface=ENVIRON["rpm_prefix_interface"] rpm_prefix_implementation=ENVIRON["rpm_prefix_implementation"] state="find" unit="" split(ENVIRON["ignore_implementation"], ignore_implementation_a) for (i in ignore_implementation_a) { val=ignore_implementation_a[i] if (debug) printf "INFO: ignore_implementation %s\n", val > "/dev/stderr" ignore_implementation[val]=1 } split(ENVIRON["ignore_interface"], ignore_interface_a) for (i in ignore_interface_a) { val=ignore_interface_a[i] if (debug) printf "INFO: ignore_interface %s\n", val > "/dev/stderr" ignore_interface[val]=1 } } /^File / { if (RPM_BUILD_ROOT != "" ) { file=substr($2,length(RPM_BUILD_ROOT)+1) } else { file=$2 } state="file" next } /^Unit name:/ { unit=$3 state="cma" next } /^Name:/ { unit=$2 state="cmx" next } /^CRC of implementation:/ { if (state == "cmx") { if (ignore_implementation[unit] != "") { if (ignore_implementation[unit] != "seen") { printf "INFO: ignoring Provides %s(%s)=%s from %s\n", rpm_prefix_implementation, unit, $4, file > "/dev/stderr" ignore_implementation[unit]="seen" } } else { implementation_provides[unit]=$4 } } else { printf "WARN: state %s, expected cmx, got %s\n", state, $0 > "/dev/stderr" } state="crc" next } /^Interfaces imported:/ { state="interface" next } /^Implementations imported:/ { state="implementation" next } /^\t/ { if (state == "interface" && NF > 1 && match($1, "^-") == 0) { if (unit == $2) { if (ignore_interface[unit] != "") { if (ignore_interface[unit] != "seen") { printf "INFO: ignoring Provides %s(%s)=%s from %s\n", rpm_prefix_interface, unit, $1, file > "/dev/stderr" ignore_interface[unit]="seen" } } else { interface_provides[unit]=$1 } } else { if (ignore_interface[$2] != "") { if (ignore_interface[$2] != "seen") { printf "INFO: ignoring Requires %s(%s)=%s from %s\n", rpm_prefix_interface, $2, $1, file > "/dev/stderr" ignore_interface[$2]="seen" } } else { interface_requires[$2]=$1 } } next } else if (state == "implementation" && NF > 1 && match($1, "^-") == 0) { if (unit == $2) { if (ignore_implementation[unit] != "") { if (ignore_implementation[unit] != "seen") { printf "INFO: ignoring Provides %s(%s)=%s from %s\n", rpm_prefix_implementation, unit, $1, file > "/dev/stderr" ignore_implementation[unit]="seen" } } else { implementation_provides[unit]=$1 } } else { if (ignore_implementation[$2] != "") { if (ignore_implementation[$2] != "seen") { printf "INFO: ignoring Requires %s(%s)=%s from %s\n", rpm_prefix_implementation, $2, $1, file > "/dev/stderr" ignore_implementation[$2]="seen" } } else { implementation_requires[$2]=$1 } } next } else { next } } /^.*/ { state="find" } END { if (mode == "provides") { for (i in interface_provides) { printf "%s(%s) = %s\n", rpm_prefix_interface, i, interface_provides[i] } for (i in implementation_provides) { printf "%s(%s) = %s\n", rpm_prefix_implementation, i, implementation_provides[i] } } if (mode == "requires") { for (i in interface_requires) { printf "%s(%s) = %s\n", rpm_prefix_interface, i, interface_requires[i] } for (i in implementation_requires) { printf "%s(%s) = %s\n", rpm_prefix_implementation, i, implementation_requires[i] } } } ' } # # usage() { echo >&2 "Usage: ${0##*/} -provides|-requires [-f 'ocamlobjinfo cmd']" } # mode= ignore_implementation_a=() ignore_interface_a=() while test "$#" -gt 0 do : "${1}" "${2}" case "${1}" in -P|--provides) mode='provides' ;; -R|--requires) mode='requires' ;; -i) ignore_interface_a+=("$2") ; shift ;; -x) ignore_implementation_a+=("$2") ; shift ;; -f) OCAMLOBJINFO="$2"; shift ;; -h|--help) usage ; exit 0 ;; -c) ;; # ignored --) break ;; *) usage ; exit 1 ;; esac shift done if test -z "${mode}" then usage exit 1 fi # export rpm_prefix_interface export rpm_prefix_implementation export mode export ignore_implementation="${ignore_implementation_a[@]}" export ignore_interface="${ignore_interface_a[@]}" # while read filename do case "${filename}" in *.cma) parse "${filename}" ;; *.cmi) parse "${filename}" ;; *.cmo) parse "${filename}" ;; *.cmx) parse "${filename}" ;; *.cmxa) parse "${filename}" ;; *.cmxs) ;; *) continue ;; esac done # vim: tw=666 ts=2 shiftwidth=2 et