1#!/bin/sh
2###########################################################################
3#
4#  Window Maker window manager
5#
6#  Copyright (c) 2014 Christophe CURIS
7#  Copyright (c) 2014 Window Maker Team
8#
9#  This program is free software; you can redistribute it and/or modify
10#  it under the terms of the GNU General Public License as published by
11#  the Free Software Foundation; either version 2 of the License, or
12#  (at your option) any later version.
13#
14#  This program is distributed in the hope that it will be useful,
15#  but WITHOUT ANY WARRANTY; without even the implied warranty of
16#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17#  GNU General Public License for more details.
18#
19#  You should have received a copy of the GNU General Public License along
20#  with this program; if not, write to the Free Software Foundation, Inc.,
21#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22#
23###########################################################################
24#
25# generate-mapfile-from-header.sh:
26#   from a list of C header files, extract the name of the functions and
27#   global variables that are considered public for a library, and generate
28#   a 'version script' for ld with that list, so that all other symbols
29#   will be considered local.
30#
31# The goal is that the symbol that are not part of the public API should
32# not be visible to the user because it can be a source of problems (from
33# name clash to evolutivity issues).
34#
35###########################################################################
36#
37# Please note that this script is writen in sh+awk on purpose: this script
38# is gonna be run on the machine of the person who is trying to compile
39# WindowMaker, and as such we cannot be sure to find any scripting language
40# in a known version and that works (python/ruby/tcl/perl/php/you-name-it).
41#
42# So for portability, we stick to the same sh+awk constraint as Autotools
43# to limit the problem, see for example:
44#   http://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Portable-Shell.html
45#
46###########################################################################
47
48# Report an error on stderr and exit with status 1 to tell make could not work
49arg_error() {
50    echo "$0: $@" >&2
51    exit 1
52}
53
54# print help and exit with success status
55print_help() {
56    echo "$0: generate a script for ld to keep only the symbols from C header"
57    echo "Usage: $0 [options...] input_file[s]"
58    echo "valid options are:"
59    echo "  -l         : add symbol's definition line number for debug"
60    echo "  -n name    : name to use for the library"
61    echo "  -v version : set the library version for ld"
62    exit 0
63}
64
65# Extract command line arguments
66while [ $# -gt 0 ]; do
67    case $1 in
68        -l)
69            debug_info='	/* " symbol_type " at " FILENAME ":" NR " */'
70          ;;
71
72        -n)
73            shift
74            echo "$1" | grep '^[A-Za-z][A-Za-z_0-9]*$' > /dev/null || arg_error "invalid name \"$1\" for a library"
75            libname="$1"
76          ;;
77
78        -v)
79            shift
80            # Version may be 'x:y:z', we keep only 'x'
81            version="`echo "$1" | sed -e 's,:.*$,,' `"
82            # the version should only be a number
83            echo "$version" | grep '^[1-9][0-9]*$' > /dev/null || \
84                arg_error "version \"$1\" is not valid"
85          ;;
86
87        -h|-help|--help) print_help ;;
88        -*) arg_error "unknow option '$1'" ;;
89
90        *)
91            [ -r "$1" ] || arg_error "source file \"$1\" is not readable"
92            input_files="$input_files $1"
93          ;;
94    esac
95    shift
96done
97
98# Check consistency of command-line
99[ "x$input_files" = "x" ] && arg_error "no source file given"
100
101# Automatic values
102if [ -z "$libname" ]; then
103    # build the library name from the first header name, remove path stuff, extension and invalid characters
104    libname="`echo "$input_files" | sed -e 's,^ ,,;s, .*$,,;s,^.*/\([^/]*\)$,\1,;s,\.[^.]*$,,;s,[^A-Za-z_0-9],_,g;s,^_*,,' `"
105fi
106
107# Parse the input file and find the function declarations; extract the names
108# and place them in the 'global' section so that ld will keep the symbols;
109# generate the rest of the script so that other symbols will not be kept.
110awk '
111BEGIN {
112  print "/* Generated version-script for ld */";
113  print "";
114  print "'"$libname$version"'";
115  print "{";
116  print "  global:";
117}
118
119/^#[ \t]*define/ {
120  # Ignore macro definition because their content could be mis-interpreted
121  while (/\\$/) {
122    if (getline == 0) { break; }
123  }
124  next;
125}
126
127/\/\*/ {
128  # Remove comments to avoid mis-detection
129  pos = index($0, "/*");
130  comment = substr($0, pos + 2);
131  $0 = substr($0, 1, pos - 1);
132  while (1) {
133    pos = index(comment, "*/");
134    if (pos > 0) { break; }
135    getline comment;
136  }
137  $0 = $0 substr(comment, pos + 2);
138}
139
140/extern[ \t].*;/ {
141  line = $0;
142
143  # Remove everything from the ";"
144  sub(/;.*$/, "", line);
145
146  # Check if it is a global variable
147  nb = split(line, words, /[\t *]/);
148
149  valid = 1;
150  for (i = 1; i < nb; i++) {
151    # If the word looks valid, do not abort
152    if (words[i] ~ /^[A-Za-z_0-9]*$/) { continue; }
153
154    # Treat the case were the variable is an array, and there was space(s) after the name
155    if (words[i] ~ /^\[/) { nb = i - 1; break; }
156
157    # If we are here, the word is not valid; we stop processing the line here but we do
158    # not use "next" so the line may match function prototype handler below
159    valid = 0;
160    break;
161  }
162
163  if (valid) {
164    # Remove array size, if any
165    sub(/\[.*$/, "", words[nb]);
166
167    if (words[nb] ~ /^[A-Za-z][A-Za-z_0-9]*$/) {
168      symbol_type = "variable";
169      print "    " words[nb] ";'"$debug_info"'";
170      next;
171    }
172  }
173}
174
175/^[ \t]*typedef/ {
176  # Skip type definition because they could be mis-interpreted when it
177  # defines a prototype for a callback function
178  nb_braces = 0;
179  while (1) {
180    # Count the number of brace pairs to skip the content of the definition
181    nb = split($0, dummy_array, /\{/);
182    nb_braces = nb_braces + nb;
183
184    nb = split($0, dummy_array, /\}/);
185    nb_braces = nb_braces - nb;
186
187    # Stop when we have out count of pair of braces and there is the final ";"
188    if ((nb_braces == 0) && (dummy_array[nb] ~ /;/)) { break; }
189
190    if (getline == 0) {
191      break;
192    }
193  }
194  next;
195}
196
197/[\t ]\**[A-Z_a-z][A-Z_a-z0-9]*[\t ]*\(/ {
198  # Get everything until the end of the definition, to avoid mis-interpreting
199  # arguments and attributes on next lines
200  while (1) {
201    pos = index($0, ";");
202    if (pos != 0) { break; }
203
204    getline nxt;
205    $0 = $0 nxt;
206  }
207
208  # Remove the argument list
209  sub(/[ \t]*\(.*$/, "");
210
211  # Trim possible indentation at beginning of line
212  sub(/^[\t ]*/, "");
213
214  # If it does not start by a keyword, it is probably not a valid function
215  if (!/^[A-Z_a-z]/) { next; }
216
217  nb = split($0, words, /[\t *]/);
218  for (i = 1; i < nb; i++) {
219    # Do not consider "static" because it is used for functions to be inlined
220    if (words[i] == "static") { next; }
221
222    # Allow empty keyword, that were just "*"
223    if (words[i] == "") { continue; }
224
225    # If it does not match, it is unlikely to be a function
226    if (words[i] !~ /^[A-Z_a-z][A-Z_a-z0-9]*$/) { next; }
227  }
228
229  # If we arrived so far, everything looks valid and the last argument of
230  # the array contains the name of the function
231  symbol_type = "function";
232  print "    " words[nb] ";'"$debug_info"'";
233}
234
235END {
236  print "";
237  print "  local:";
238  print "    *;";
239  print "};";
240}
241' $input_files
242