#!/usr/bin/perl eval "exec perl -S $0 $*" if $running_under_some_shell; # # This perl program uses dynamic loading [generated by perload] # $ENV{LC_ALL} = 'C'; # $Id: mlint.SH,v 3.0.1.3 1994/05/06 15:20:42 ram Exp $ # # Copyright (c) 1991-1993, Raphael Manfredi # # You may redistribute only under the terms of the Artistic Licence, # as specified in the README file that comes with the distribution. # You may reuse parts of this distribution only within the terms of # that same Artistic Licence; a copy of which may be found at the root # of the source tree for dist 3.0. # # Original Author: Harlan Stenn # # $Log: mlint.SH,v $ # Revision 3.0.1.3 1994/05/06 15:20:42 ram # patch23: added -L switch to override public unit repository path # # Revision 3.0.1.2 1994/01/24 14:21:00 ram # patch16: added ~/.dist_profile awareness # # Revision 3.0.1.1 1993/08/19 06:42:27 ram # patch1: leading config.sh searching was not aborting properly # # Revision 3.0 1993/08/18 12:10:17 ram # Baseline for dist 3.0 netwide release. # # Perload ON $MC = '/u/vieraat/vieraat/jhi/Perl/lib/dist'; $version = '3.0'; $patchlevel = '70'; $grep = '/usr/bin/grep'; &profile; # Read ~/.dist_profile require 'getopts.pl'; &usage unless &Getopts("hklVL:"); if ($opt_V) { print STDERR "metalint $version PL$patchlevel\n"; exit 0; } elsif ($opt_h) { &usage; } chop($date = `date`); $MC = $opt_L if $opt_L; # May override library path $MC = &tilda_expand($MC); # ~name expansion chop($WD = `pwd`); # Working directory chdir $MC || die "Can't chdir to $MC: $!\n"; chop($MC = `pwd`); # Real metalint lib path (no symbolic links) chdir $WD || die "Can't chdir back to $WD: $!\n"; &init; # Various initializations `mkdir .MT 2>&1` unless -d '.MT'; # For private temporary files &locate_units; # Fill in @ARGV with a unit list &extract_dependencies; # Extract dependencies from units &sanity_checks; # Perform sanity checks if ($opt_k) { print "Leaving subdirectory .MT unremoved so you can peruse it.\n" unless $opt_s; } else { `rm -rf .MT 2>&1`; } print "Done.\n" unless $opt_s; sub main'init { &auto_main'init; } sub auto_main'init { &main'dataload; } sub main'init_except { &auto_main'init_except; } sub auto_main'init_except { &main'dataload; } sub main'usage { &auto_main'usage; } sub auto_main'usage { &main'dataload; } package locate; sub main'locate_units { &auto_main'locate_units; } sub auto_main'locate_units { &main'dataload; } sub locate'dump_list { &auto_locate'dump_list; } sub auto_locate'dump_list { &main'dataload; } sub locate'private_units { &auto_locate'private_units; } sub auto_locate'private_units { &main'dataload; } sub locate'public_units { &auto_locate'public_units; } sub auto_locate'public_units { &main'dataload; } sub locate'units_path { &auto_locate'units_path; } sub auto_locate'units_path { &main'dataload; } package main; sub main'init_extraction { &auto_main'init_extraction; } sub auto_main'init_extraction { &main'dataload; } sub main'end_extraction { &auto_main'end_extraction; } sub auto_main'end_extraction { &main'dataload; } sub main'p_make { &auto_main'p_make; } sub auto_main'p_make { &main'dataload; } sub main'p_obsolete { &auto_main'p_obsolete; } sub auto_main'p_obsolete { &main'dataload; } sub main'p_shell { &auto_main'p_shell; } sub auto_main'p_shell { &main'dataload; } sub main'p_c { &auto_main'p_c; } sub auto_main'p_c { &main'dataload; } sub main'p_config { &auto_main'p_config; } sub auto_main'p_config { &main'dataload; } sub main'p_magic { &auto_main'p_magic; } sub auto_main'p_magic { &main'dataload; } sub main'p_init { &auto_main'p_init; } sub auto_main'p_init { &main'dataload; } sub main'p_default { &auto_main'p_default; } sub auto_main'p_default { &main'dataload; } sub main'p_visible { &auto_main'p_visible; } sub auto_main'p_visible { &main'dataload; } sub main'p_wanted { &auto_main'p_wanted; } sub auto_main'p_wanted { &main'dataload; } sub main'p_layout { &auto_main'p_layout; } sub auto_main'p_layout { &main'dataload; } sub main'p_public { &auto_main'p_public; } sub auto_main'p_public { &main'dataload; } sub main'p_library { &auto_main'p_library; } sub auto_main'p_library { &main'dataload; } sub main'p_include { &auto_main'p_include; } sub auto_main'p_include { &main'dataload; } sub main'p_temp { &auto_main'p_temp; } sub auto_main'p_temp { &main'dataload; } sub main'p_file { &auto_main'p_file; } sub auto_main'p_file { &main'dataload; } sub main'p_lint { &auto_main'p_lint; } sub auto_main'p_lint { &main'dataload; } sub main'p_body { &auto_main'p_body; } sub auto_main'p_body { &main'dataload; } sub main'p_end { &auto_main'p_end; } sub auto_main'p_end { &main'dataload; } sub main'p_unknown { &auto_main'p_unknown; } sub auto_main'p_unknown { &main'dataload; } sub main'sanity_checks { &auto_main'sanity_checks; } sub auto_main'sanity_checks { &main'dataload; } sub main'check_last_declaration { &auto_main'check_last_declaration; } sub auto_main'check_last_declaration { &main'dataload; } sub main'check_definition { &auto_main'check_definition; } sub auto_main'check_definition { &main'dataload; } sub main'declared { &auto_main'declared; } sub auto_main'declared { &main'dataload; } sub main'defined { &auto_main'defined; } sub auto_main'defined { &main'dataload; } sub main'wanted { &auto_main'wanted; } sub auto_main'wanted { &main'dataload; } sub main'visible { &auto_main'visible; } sub auto_main'visible { &main'dataload; } sub main'explore { &auto_main'explore; } sub auto_main'explore { &main'dataload; } sub main'init_depend { &auto_main'init_depend; } sub auto_main'init_depend { &main'dataload; } sub main'extract_dependencies { &auto_main'extract_dependencies; } sub auto_main'extract_dependencies { &main'dataload; } sub main'complete_line { &auto_main'complete_line; } sub auto_main'complete_line { &main'dataload; } sub main'record_obsolete { &auto_main'record_obsolete; } sub auto_main'record_obsolete { &main'dataload; } sub main'dump_obsolete { &auto_main'dump_obsolete; } sub auto_main'dump_obsolete { &main'dataload; } # # Topological sort of Makefile dependencies with cycle enhancing. # package tsort; sub main'tsort { &auto_main'tsort; } sub auto_main'tsort { &main'dataload; } sub tsort'resync { &auto_tsort'resync; } sub auto_tsort'resync { &main'dataload; } sub tsort'sort { &auto_tsort'sort; } sub auto_tsort'sort { &main'dataload; } sub tsort'extract_cycle { &auto_tsort'extract_cycle; } sub auto_tsort'extract_cycle { &main'dataload; } sub tsort'outline_cycle { &auto_tsort'outline_cycle; } sub auto_tsort'outline_cycle { &main'dataload; } sub tsort'visit { &auto_tsort'visit; } sub auto_tsort'visit { &main'dataload; } sub tsort'sort_by_value { &auto_tsort'sort_by_value; } sub auto_tsort'sort_by_value { &main'dataload; } package main; 1; sub main'tilda_expand { &auto_main'tilda_expand; } sub auto_main'tilda_expand { &main'dataload; } sub main'profile { &auto_main'profile; } sub auto_main'profile { &main'dataload; } # Load the calling function from DATA segment and call it. This function is # called only once per routine to be loaded. sub main'dataload { local($__packname__) = (caller(1))[3]; $__packname__ =~ s/::/'/; local($__rpackname__) = $__packname__; local($__at__) = $@; $__rpackname__ =~ s/^auto_//; &perload'load_from_data($__rpackname__); local($__fun__) = "$__rpackname__"; $__fun__ =~ s/'/'load_/; eval "*$__packname__ = *$__fun__;"; # Change symbol table entry die $@ if $@; # Should not happen $@ = $__at__; # Restore value $@ had on entrance &$__fun__; # Call newly loaded function } # Load function name given as argument, fatal error if not existent sub perload'load_from_data { package perload; local($pos) = $Datapos{$_[0]}; # Offset within DATA # Avoid side effects by protecting special variables which will be changed # by the dataloading operation. local($., $_, $@); $pos = &fetch_function_code unless $pos; die "Function $_[0] not found in data section.\n" unless $pos; die "Cannot seek to $pos into data section.\n" unless seek(main'DATA, $pos, 0); local($/) = "\n}"; local($body) = scalar(); local($*) = 1; die "End of file found while loading $_[0].\n" unless $body =~ /^\}$/; eval $body; # Load function into perl space chop($@) && die "$@, while parsing code of $_[0].\n"; } # This function is called only once, and fills in the %Datapos array with # the offset of each of the dataloaded routines held in the data section. sub perload'fetch_function_code { package perload; local($start) = 0; local($., $_); while () { # First move to start of offset table next if /^#/; last if /^$/ && ++$start > 2; # Skip two blank line after end token } $start = tell(main'DATA); # Offsets in table are relative to here local($key, $value); while () { # Load the offset table last if /^$/; # Ends with a single blank line ($key, $value) = split(' '); $Datapos{$key} = $value + $start; } $Datapos{$_[0]}; # All that pain to get this offset... } # # The perl compiler stops here. # __END__ # # Beyond this point lie functions we may never compile. # # # DO NOT CHANGE A IOTA BEYOND THIS COMMENT! # The following table lists offsets of functions within the data section. # Should modifications be needed, change original code and rerun perload # with the -o option to regenerate a proper offset table. # locate'dump_list 3732 locate'private_units 3865 locate'public_units 4652 locate'units_path 6145 main'check_definition 45075 main'check_last_declaration 44532 main'complete_line 51232 main'declared 45427 main'defined 45535 main'dump_obsolete 52875 main'end_extraction 9864 main'explore 46325 main'extract_dependencies 48374 main'init 2172 main'init_depend 47224 main'init_except 2398 main'init_extraction 7659 main'locate_units 3115 main'p_body 24846 main'p_c 14264 main'p_config 15703 main'p_default 17565 main'p_end 29543 main'p_file 21482 main'p_include 21070 main'p_init 17386 main'p_layout 20583 main'p_library 20932 main'p_lint 23193 main'p_magic 16872 main'p_make 9939 main'p_obsolete 13278 main'p_public 20856 main'p_shell 13435 main'p_temp 21147 main'p_unknown 33023 main'p_visible 17806 main'p_wanted 19486 main'profile 58915 main'record_obsolete 51822 main'sanity_checks 33268 main'tilda_expand 58560 main'tsort 54666 main'usage 2715 main'visible 46072 main'wanted 45663 tsort'extract_cycle 56234 tsort'outline_cycle 57202 tsort'resync 55331 tsort'sort 55555 tsort'sort_by_value 58354 tsort'visit 57824 # # End of offset table and beginning of dataloading section. # # General initializations sub main'load_init { package main; &init_except; # Token which have upper-cased letters &init_depend; # The %Depend array records control line handling } # Record the exceptions -- all symbols but these are lower case sub main'load_init_except { package main; $Except{'Mcc'}++; $Except{'Author'}++; $Except{'Date'}++; $Except{'Header'}++; $Except{'Id'}++; $Except{'Locker'}++; $Except{'Log'}++; $Except{'RCSfile'}++; $Except{'Revision'}++; $Except{'Source'}++; $Except{'State'}++; } # Print out metalint's usage and exits sub main'load_usage { package main; print STDERR < 1; undef %condseen; # Reset those once for every unit undef %depseen; # (assuming there is only one depend line) undef %defseen; undef %tempseen; undef %symset; undef %symused; undef %csym; undef %ssym; undef %hcsym; undef %hssym; undef %lintseen; undef %lintchange; undef %lintextern; undef %lintcreated; undef %fileseen; undef %filetmp; undef %filecreated; s|^\s*||; # Remove leading spaces chop; s/:(.*)//; @dep = split(' ', $1); # Dependencies @ary = split(' '); # Locally defined symbols local($nowarn); # True when +Special is seen foreach $sym (@ary) { # Ignore "internal use only" symbols as far as metalint goes. # Actually, we record the presence of a '+' in front of a special # unit name and use that as a hint to suppress the presence of that # special unit in the defined symbol section. $nowarn = ($sym =~ s/^\+//); # We record for each shell symbol the list of units which claim to make # it, so as to report duplicates. if ($sym =~ /^[_a-z]/ || $Except{$sym}) { $shmaster{"\$$sym"} .= "$unit "; ++$defseen{$sym}; } else { warn "$where: special unit '$sym' should not be listed as made.\n" unless $sym eq $unit || $nowarn; } } # Record dependencies for later perusal push(@make, join(' ', @ary) . ':' . join(' ', @dep)); foreach $sym (@dep) { if ($sym =~ /^\+[_A-Za-z]/) { $sym =~ s|^\+||; ++$condseen{$sym}; # Conditional symbol wanted ++$condsym{$sym}; # %condsym has a greater lifetime } else { ++$depseen{$sym}; # Full dependency } # Each 'wanted' special unit (i.e. one starting with a capital letter) # is remembered, so as to prevent exported symbols from being reported # as "undefined". For instance, Myread exports $dflt, $ans and $rp. $shspecial{$unit} .= "$sym " if substr($sym, 0, 1) =~ /^[A-Z]/; # Record all known dependencies (special or not) for this unit $shdepend{$unit} .= "$sym "; # Remember where wanted symbol is defined, so that we can report # stale dependencies later on (i.e. dependencies which refer to non- # existent symbols). $symdep{$sym} .= "$unit "; # This symbol is wanted here } # Make sure we do not want a symbol twice, nor do we want it once as a full # dependency and once as a conditional dependency. foreach $sym (@dep) { if ($sym =~ /^\+[_A-Za-z]/) { $sym =~ s|^\+||; warn "$where: '+$sym' is listed $condseen{$sym} times.\n" if $condseen{$sym} > 1; $condseen{$sym} = 1 if $condseen{$sym}; # Avoid multiple messages } else { warn "$where: '$sym' is listed $depseen{$sym} times.\n" if $depseen{$sym} > 1; $depseen{$sym} = 1 if $depseen{$sym}; # Avoid multiple messages } warn "$where: '$sym' listed as both conditional and full dependency.\n" if $condseen{$sym} && $depseen{$sym}; } # Make sure every unit "inherits" from the symbols exported by 'Init'. $shspecial{$unit} .= 'Init ' unless $shspecial{$unit} =~ /Init\b/; } # Process the ?O: line sub main'load_p_obsolete { package main; local($_) = @_; chop; $Obsolete{"$unit.U"} = $_; # Message to print if unit is used } # Process the ?S: lines sub main'load_p_shell { package main; local($_) = @_; local($where) = "\"$file\", line $. (?S:)"; if (/^(\w+)\s*(\(.*\))*\s*:/) { &check_last_declaration; $s_symbol = $1; print " ?S: $s_symbol\n" if $opt_d; # Make sure we do not define symbol twice and that the symbol is indeed # listed in the ?MAKE: line. warn "$where: duplicate description for variable '\$$s_symbol'.\n" if $ssym{$s_symbol}++; warn "$where: variable '\$$s_symbol' is not listed on ?MAKE: line.\n" unless $defseen{$s_symbol} || $lintseen{$s_symbol}; # Deal with obsolete symbol list (enclosed between parenthesis) &record_obsolete("\$$_") if /\(/; } else { unless ($s_symbol) { warn "$where: syntax error in ?S: construct.\n"; return; } } m|^\.\s*$| && ($s_symbol = ''); # End of comment } # Process the ?C: lines sub main'load_p_c { package main; local($_) = @_; local($where) = "\"$file\", line $. (?C:)"; if (s/^(\w+)\s*~\s*(\S+)\s*(.*):/$1 $3:/) { &check_last_declaration; $c_symbol = $2; # Alias for definition in config.h # Record symbol definition for further duplicate spotting $cmaster{$1} .= "$unit " unless $csym{$1}; print " ?C: $1 ~ $c_symbol\n" if $opt_d; # Make sure we do not define symbol twice warn "$where: duplicate description for symbol '$1'.\n" if $csym{$1}++; # Deal with obsolete symbol list (enclosed between parenthesis) &record_obsolete("$_") if /\(/; } elsif (/^(\w+)\s*(\(.*\))*\s*:/) { &check_last_declaration; $c_symbol = $1; # Record symbol definition for further duplicate spotting $cmaster{$c_symbol} .= "$unit " unless $csym{$c_symbol}; print " ?C: $c_symbol\n" if $opt_d; # Make sure we do not define symbol twice warn "$where: duplicate description for symbol '$c_symbol'.\n" if $csym{$c_symbol}++; # Deal with obsolete symbol list (enclosed between parenthesis) &record_obsolete("$_") if /\(/; } else { unless ($c_symbol) { warn "$where: syntax error in ?C: construct.\n"; return; } } s|^(\w+)|?$c_symbol:/* $1| || # Start of comment (s|^\.\s*$|?$c_symbol: */\n| && ($c_symbol = '', 1)) || # End of comment s|^(.*)|?$c_symbol: *$1|; # Middle of comment &p_config("$_"); # Add comments to config.h.SH } # Process the ?H: lines sub main'load_p_config { package main; local($_) = @_; local($where) = "\"$file\", line $. (?H)" unless $where; s/^\?(\w+)://; # Remove leading '?var:' return unless /^#/; # Look only for cpp lines if (m|^#\$(\w+)\s+(\w+).*\$(\w+)|) { # Case: #$d_var VAR "$var" warn "$where: symbol '$2' was already defined.\n" if $hcsym{$2}++; &check_definition("$1"); &check_definition("$3"); } elsif (m|^#define\s+(\w+)\((.*)\)\s+\$(\w+)|) { # Case: #define VAR(x) $var warn "$where: symbol '$1' was already defined.\n" if $hcsym{$1}++; &check_definition("$3"); } elsif (m|^#\$define\s+(\w+)|) { # Case: #$define VAR warn "$where: symbol '$1' was already defined.\n" if $hcsym{$1}++; } elsif (m|^#\$(\w+)\s+(\w+)|) { # Case: #$d_var VAR warn "$where: symbol '$2' was already defined.\n" if $hcsym{$2}++; &check_definition("$1"); } elsif (m|^#define\s+(\w+).*\$(\w+)|) { # Case: #define VAR "$var" warn "$where: symbol '$1' was already defined.\n" if $hcsym{$1}++; &check_definition("$2"); } elsif (m|^#define\s+(\w+)|) { # Case: #define VAR $hcsym{$1}++; # Multiple occurrences may be legitimate } } # Process the ?M: lines sub main'load_p_magic { package main; local($_) = @_; local($where) = "\"$file\", line $. (?M)"; if (/^(\w+):\s*([\w\s]*)\n$/) { &check_last_declaration; $m_symbol = $1; $msym{$1} = "$unit"; # p_wanted ensure we do not define symbol twice $mdep{$1} = $2; # Save C symbol dependencies &p_wanted("$unit:$m_symbol"); } else { unless ($m_symbol) { warn "$where: syntax error in ?M: construct.\n"; return; } } m|^\.\s*$| && ($m_symbol = ''); # End of comment } # Process the ?INIT: lines sub main'load_p_init { package main; local($where) = "\"$file\", line $. (?INIT)"; &p_body; # Pass it along as a body line (leading ?INIT: removed) } # Process the ?D: lines sub main'load_p_default { package main; local($_) = @_; local($where) = "\"$file\", line $. (?D)"; local($sym) = /^(\w+)=/; $hasdefault{$sym}++; &p_body; # Pass it along as a body line (leading ?D: removed) } # Process the ?V: lines sub main'load_p_visible { package main; # A visible symbol can freely be manipulated by any unit which includes the # current unit in its dependencies. Symbols before ':' may be only used for # reading while symbols after ':' may be used for both reading and writing. # The array %shvisible records symbols as keys. Read-only symbols have a # leading '$' while read-write symbols are recorded as-is. local($where) = "\"$file\", line $. (?V)"; unless (substr($unit, 0, 1) =~ /^[A-Z]/) { warn "$where: visible declaration in non-special unit ignored.\n"; return; } local($read_only) = $_[0] =~ /^([^:]*):?/; local($read_write) = $_[0] =~ /:(.*)/; local(@rsym) = split(' ', $read_only); local(@rwsym) = split(' ', $read_write); local($w); foreach (@rsym) { # Read only symbols warn "$where: wanted variable '\$$_' made visible.\n" if &wanted($_); warn "$where: defined variable '\$$_' made visible.\n" if &defined($_) && !$lintseen{$_}; $w = $shvisible{"\$$_"}; warn "$where: variable '\$$_' already made visible by unit $w.\n" if $w; $w = $shvisible{$_}; warn "$where: variable '\$$_' already read-write visible in $w.\n" if $w; $shvisible{"\$$_"} = $unit unless $w; } foreach (@rwsym) { # Read/write symbols warn "$where: wanted variable '\$$_' made visible.\n" if &wanted($_); warn "$where: defined variable '\$$_' made visible.\n" if &defined($_) && !$lintseen{$_}; $w = $shvisible{$_}; warn "$where: variable '\$$_' already made visible by unit $w.\n" if $w; $w = $shvisible{"\$$_"}; warn "$where: variable '\$$_' already read-only visible in $w.\n" if $w; $shvisible{$_} = $unit unless $w; } } # Process the ?W: lines sub main'load_p_wanted { package main; # Somehow, we should check that none of the symbols to activate are stale # ones, i.e. they all finally resolve to some known target -- FIXME local($active) = $_[0] =~ /^([^:]*):/; # Symbols to activate local($look_symbols) = $_[0] =~ /:(.*)/; # When those are used local(@symbols) = split(' ', $look_symbols); local($where) = "\"$file\", line $. (?W)" unless $where; # A "?W:symbol" line asks metaconfig to define 'symbol' in the wanted file # as a C target iff that word is found within the sources. This is mainly # intended for the built-in interpreter to check for definedness. local($w); foreach (@symbols) { warn "$where: variable '\$$_' already wanted.\n" if &wanted($_); warn "$where: variable '\$$_' also locally defined.\n" if &defined($_); $w = $cwanted{$_}; if ($msym{$_} ne '') { warn "$where: symbol '$_' already listed on a ?M: line in '$w'.\n" if $w; } else { warn "$where: variable '\$$_' already listed on a ?W: line in '$w'.\n" if $w; } $cwanted{$_} = $unit unless $w; } } # Process the ?Y: lines sub main'load_p_layout { package main; local($_) = @_; chop; local($where) = "\"$file\", line $. (?Y)"; s/^\s+//; tr/A-Z/a-z/; # Layouts are record in lowercase warn "$where: unknown layout directive '$_'.\n" unless defined $Lcmp{$_}; } # Process the ?P: lines sub main'load_p_public { package main; # FIXME } # Process the ?L: lines sub main'load_p_library { package main; # There should not be any '-l' in front of the library name # FIXME } # Process the ?I: lines sub main'load_p_include { package main; # FIXME } # Process the ?T: lines sub main'load_p_temp { package main; local($_) = @_; local(@sym) = split(' ', $_); local($where) = "\"$file\", line $. (?T:)"; foreach $sym (@sym) { warn "$where: temporary symbol '\$$sym' multiply declared.\n" if $tempseen{$sym}++ == 1; $tempmaster{$sym} .= "$unit " if $tempseen{$sym} == 1; } } # Process the ?F: lines sub main'load_p_file { package main; local($_) = @_; local(@files) = split(' ', $_); local($where) = "\"$file\", line $. (?F:)"; local($uufile); # Name of file produced in the UU directory local($tmpfile); # Name of a temporary file # We care only about UU files, i.e. files produced in the UU directory # and which are identified by the convention ./filename. Files !filename # are not produced, i.e. they are temporary or externally provided. # The %prodfile table records all the files produced, so we may detect # inconsistencies between units, while %filemaster records the (first) unit # defining a given UU file to make sure that (special) unit is named in the # dependency line when that UU file. Duplicates will be caught in the # sanity check phase thanks to %prodfile. # Temporary files are recorded in %filesetin, so that we may later compare # the list with the UU files to detect possible overwrites. foreach $file (@files) { warn "$where: produced file '$file' multiply declared.\n" if $fileseen{$file}++ == 1; if (($tmpfile = $file) =~ s/^!//) { $filetmp{$tmpfile}++; $filesetin{$tmpfile} .= "$unit " if $fileseen{$file} == 1; next; # Is not a UU file for sure, so skip } $prodfile{$file} .= "$unit " if $fileseen{$file} == 1; ($uufile = $file) =~ s|^\./(\S+)$|$1|; next if $file eq $uufile; # Don't care about non-UU files unless (substr($unit, 0, 1) =~ /^[A-Z]/ || $lintcreated{$uufile}) { warn "$where: UU file '$uufile' in non-special unit ignored.\n"; next; } $filemaster{$uufile} = $unit unless defined $filemaster{$uufile}; $filecreated{$uufile} = 'a'; # Will be automagically incremented } } # Process the ?LINT: lines sub main'load_p_lint { package main; local($_) = @_; local(@sym); s/^\s+//; # Strip leading spaces if (s/^set//) { # Listed variables are set @sym = split(' ', $_); foreach (@sym) { $symset{$_}++; # Shell variable set } } elsif (s/^desc\w+//) { # Listed shell variables are described @sym = split(' ', $_); foreach (@sym) { $ssym{$_}++; # Shell variable described } } elsif (s/^creat\w+//) { # Listed created files in regular units @sym = split(' ', $_); foreach (@sym) { $lintcreated{$_}++; # Persistent UU file created } } elsif (s/^known//) { # Listed C variables are described @sym = split(' ', $_); foreach (@sym) { $csym{$_}++; # C symbol described } } elsif (s/^change//) { # Shell variable ok to be changed @sym = split(' ', $_); foreach (@sym) { $lintchange{$_}++; # Do not complain if changed } } elsif (s/^extern//) { # Variables known to be externally defined @sym = split(' ', $_); foreach (@sym) { $lintextern{$_}++; # Do not complain if used in a ?H: line } } elsif (s/^use//) { # Variables declared as used by unit @sym = split(' ', $_); foreach (@sym) { $lintuse{$_}++; # Do not complain if on ?MAKE and not used } } elsif (s/^def\w+//) { # Listed variables are defined @sym = split(' ', $_); foreach (@sym) { $lintseen{$_}++; # Shell variable defined in this unit } } elsif (m/^empty/) { # Empty unit file $lintempty{$unit}++; } else { local($where) = "\"$file\", line $." unless $where; local($word) = /^(\w+)/; warn "$where: unknown LINT request '$word' ignored.\n"; } } # Process the body of the unit sub main'load_p_body { package main; return unless $makeseen{$unit}; local($_) = @_; local($where) = "\"$file\", line $." unless $where; # Ensure there is no control line in the body of the unit local($control) = /^\?([\w\-]+):/; local($known) = $control ? $Depend{$control} : ""; warn "$where: control sequence '?$control:' ignored within body.\n" if $known && !/^\?X:|^\?LINT:/; if (s/^\?LINT://) { # ?LINT directives allowed within body $_ .= &complete_line(FILE) if s/\\\s*$//; &p_lint($_); } return if $known; # Ingnore interpreted lines and their continuations if ($last_interpreted) { return if /\\$/; # Still part of the interpreted line $last_interpreted = 0; # End of interpreted lines return; # This line was the last interpreted } # Look for interpreted lines and ignore them if (/^@/) { $last_interpreted = /\\$/; # Set flag if line is continued return; # And skip this line } s/^\s+//; # Remove leading spaces # Detect shell ':' "comment" lines, and perform sanity checks on them... # Also spot any of '<>|&;' since those will have their shell behaviour # behaviour if (s/^:\s+//) { s/<\$?\w+>//g; # Remove valid <$var> escapes or old warn "$where: meaningful shell character '$1' in comment line.\n" while s/([<>&\|;]+)//g; require 'shellwords.pl'; eval { &shellwords($_) }; return unless $@; # Ignore comment, no quoting problem local($what) = ''; $what = 'double' if $@ =~ /\bdouble\b/; $what = 'single' if $@ =~ /\bsingle\b/; warn "$where: unmatched $what quote in comment line.\n" if $what; warn "$where: $@" unless $what; return; } # From now on, do all substitutes with ':' since it would be dangerous # to remove things plain and simple. It could yields false matches # afterwards... # Record any attempt made to set a shell variable local($sym); while (s/(\w+)=/:/) { $sym = $1; next if $sym =~ /^\d+/; # Ignore $1 and friends $symset{$sym}++; # Shell variable set # Not part of a $cc -DWHATEVER line and not made nor temporary unless ($sym =~ /^D/ || &defined($sym)) { if (&wanted($sym)) { warn "$where: variable '\$$sym' is changed.\n" unless $lintchange{$sym}; } else { # Record that the variable is set but not listed locally. $shset{$unit} .= "$sym " unless $shset{$unit} =~ /\b$sym\b/ || $lintchange{$sym}; } } } # Now look at the shell variables used: can be $var or ${var} local($var); local($line) = $_; while (s/\$\{?(\w+)\}?/:/) { $var = $1; next if $var =~ /^\d+/; # Ignore $1 and friends # Record variable as undeclared but do not issue a message right now. # That variable could be exported via ?V: (as $dflt in Myread) or be # defined by a special unit (like $inlibc by unit Inlibc). $shunknown{$unit} .= "$var " unless $lintextern{$var} || &declared($var) || $shunknown{$unit} =~ /\b$var\b/; $shused{$unit} .= "\$$var " unless $shused{$unit} =~ /\$$var\b/; } # Now look at private files used by the unit (./file or ..../UU/file) # We look at things like '. ./myread' and `./loc ...` only. local($file); $_ = $line; while ( s!(\.\s+|`\s*)(\S*UU|\.)/([^\$/`\s;]+)\s*!! || s!(`\s*\$?)cat\s+(\./)?([^\$/`\s;]+)\s*`!! || s!if(\s+)(\./)([^\$/`\s;]+)\s*!! ) { $file = $3; # Found some ". ./file" or `./file` execution, `$cat file`, "if prog"... # Record file as used. Later on, we will make sure we had the right # to use that file: either we are in the unit that defines it, or we # include the unit that creates it in our dependencies, relying on ?F:. $fileused{$unit} .= "$file " unless $filetmp{$file} || $fileused{$unit} =~ /\b$file\b/; # Mark temporary file as being used, to spot useless local declarations $filetmp{$file} .= ' used' if defined $filetmp{$file} && $filetmp{$file} !~ /\bused/; } # Try to detect things like . myread or `loc` to warn that they # should rather use . ./myread and `./loc`. Also things like 'if prog', # or usage in conditional expressions such as || and &&. Be sure the file # name is always in $2... while ( s!(\.\s+|`\s*)([^\$/`\s;]+)\s*!:! || # . myread or `loc` s!(if|\|\||&&)\s+([^\$/`\s;]+)\s*!:! # if prog, || prog, && prog ) { $file = $2; $filemisused{$unit} .= "$file " unless $filetmp{$file} || $filemisused{$unit} =~ /\b$file\b/; # Temporary files should be used with ./ anyway $filetmp{$file} .= ' misused' if defined $filetmp{$file} && $filetmp{$file} !~ /\bmisused/; } # Locate file creation, >>file or >file while (s!>>?\s*([^\$/`\s;]+)\s*!:!) { $file = $1; next if $file =~ /&\d+/; # skip >&4 and friends $filecreated{$file}++; } } # Called at the end of each unit sub main'load_p_end { package main; local($last) = @_; # Last processed line local($where) = "\"$file\""; unless ($makeseen{$unit}) { warn "$where: no ?MAKE: line describing dependencies.\n" unless $lintempty{$unit}; return; } # Each unit should end with a blank line. Unfortunately, some units # may also end with an '@end' request and have the blank line above it. # Currently, we do not have enough information to correctly diagnose # whether it is valid or not so just skip it. # Same thing for U/Obsol_sh.U which ends with a shell comment. warn "$where: not ending with a blank line.\n" unless $last =~ /^\s*$/ || $last =~ /^\@end/ || $last =~ /^#|\?/; # For EMACS users. It would be fatal to the Configure script... warn "$where: last line not ending with a new-line character.\n" unless $last =~ /\n$/; # Make sure every shell symbol described in ?MAKE had a description foreach $sym (sort keys %defseen) { warn "$where: symbol '\$$sym' was not described.\n" unless $ssym{$sym}; } # Ensure all the C symbols defined by ?H: lines have a description foreach $sym (sort keys %hcsym) { warn "$where: C symbol '$sym' was not described.\n" unless $csym{$sym}; } # Ensure all the C symbols described by ?C: lines are defined in ?H: foreach $sym (sort keys %csym) { warn "$where: C symbol '$sym' was not defined by any ?H: line.\n" unless $hcsym{$sym}; } # Make sure each defined symbol was set, unless it starts with an # upper-case letter in which case it is not a "true" shell symbol. # I don't care about the special symbols defined in %Except as I know # they are handled correctly. foreach $sym (sort keys %defseen) { warn "$where: variable '\$$sym' should have been set.\n" unless $symset{$sym} || substr($sym, 0, 1) =~ /^[A-Z]/; } # Make sure every non-special unit declared as wanted is indeed needed foreach $sym (sort keys %depseen) { warn "$where: unused dependency variable '\$$sym'.\n" unless $shused{$unit} =~ /\$$sym\b/ || substr($sym, 0, 1) =~ /^[A-Z]/ || $lintchange{$sym} || $lintuse{$sym}; } # Idem for conditionally wanted symbols foreach $sym (sort keys %condseen) { warn "$where: unused conditional variable '\$$sym'.\n" unless $shused{$unit} =~ /\$$sym\b/ || substr($sym, 0, 1) =~ /^[A-Z]/ || $lintchange{$sym} || $lintuse{$sym}; } # Idem for temporary symbols foreach $sym (sort keys %tempseen) { warn "$where: unused temporary variable '\$$sym'.\n" unless $shused{$unit} =~ /\$$sym\b/ || $symset{$sym} || $lintuse{$sym}; } # Idem for local files foreach $file (sort keys %filetmp) { warn "$where: mis-used temporary file '$file'.\n" if $filetmp{$file} =~ /\bmisused/; warn "$where: unused temporary file '$file'.\n" unless $filetmp{$file} =~ /\bused/ || $filetmp{$file} =~ /\bmisused/; } # Make sure each private file listed as created on ?F: is really created. # When found, a private UU file is entered in the %filecreated array # with value 'a'. Each time a file creation occurs in the unit, an # increment is done on that value. Since 'a'++ -> 'b', a numeric value # in %filecreated means a non-local file, which is skipped. An 'a' means # the file was not created... local($value); foreach $file (sort keys %filecreated) { $value = $filecreated{$file}; next if $value > 0; # Skip non UU-files. warn "$where: file '$file' was not created.\n" if $value eq 'a'; } } # An unknown control line sequence was found (held in $proc) sub main'load_p_unknown { package main; warn "\"$file\", line $.: unknown control sequence '?$proc:'.\n"; } # Run sanity checks, to make sure every conditional symbol has a suitable # default value. Also ensure every symbol was defined once. sub main'load_sanity_checks { package main; print "Sanity checks...\n"; local($key, $value); local($w); local(%message); # Record messages on a per-unit basis local(%said); # Avoid duplicate messages # Warn about symbols ever used in conditional dependency with no default while (($key, $value) = each(%condsym)) { unless ($hasdefault{$key}) { $w = (split(' ', $shmaster{"\$$key"}))[0]; $message{$w} .= "#$key "; } } # Warn about any undeclared variables. They are all listed in %shunknown, # being the values while the unit where they appear is the key. If the # symbol is defined by any of the special units included or made visible, # then no warning is issued. local($defined); # True if symbol is defined in one unit local($where); # List of units where symbol is defined local($myself); # The name of the current unit if itself special local($visible); # Symbol made visible via a ?V: line foreach $unit (sort keys %shunknown) { foreach $sym (split(' ', $shunknown{$unit})) { $defined = 0; $where = $shmaster{"\$$sym"}; $defined = 1 if $tempmaster{"\$$sym"} =~ /$unit\b/; $myself = substr($unit, 0, 1) =~ /^[A-Z]/ ? $unit : ''; # Symbol has to be either defined within one of the special units # listed in the dependencies or exported via a ?V: line. unless ($defined) { $defined = &visible($sym, $unit); $spneeded{$unit}++ if $defined; } $message{$unit} .= "\$$sym " unless $defined; } } # Warn about any undeclared files. Files used in one unit are all within # the %fileused table, indexed by unit. If a file is used, it must either # be in the unit that declared it (relying on %filemaster for that) or # the unit listed in %filemaster must be part of our dependency. %said = (); foreach $unit (sort keys %fileused) { foreach $file (split(' ', $fileused{$unit})) { $defined = 0; $where = $filemaster{$file}; # Where file is created $defined = 1 if $unit eq $where; # We're in the unit defining it # Private UU files may be only be created by special units foreach $special (split(' ', $shspecial{$unit})) { last if $defined; $defined = 1 if $where eq $special; } # Exceptions to above rule possible via a ?LINT:create hint, # so parse all known dependencies for the unit... foreach $depend (split(' ', $shdepend{$unit})) { last if $defined; $defined = 1 if $where eq $depend; } $message{$unit} .= "\@$file " unless $defined || $said{"$unit/$file"}++; # Unknown file } } undef %fileused; # Warn about any misused files, kept in %filemisused foreach $unit (sort keys %filemisused) { foreach $file (split(' ', $filemisused{$unit})) { next unless defined $filemaster{$file}; # Skip non UU-files $message{$unit} .= "\@\@$file "; # Misused file } } undef %filemisused; # Warn about temporary files which could be created and inadvertently # override a private UU file (listed in %filemaster). foreach $tmpfile (keys %filesetin) { next unless defined $filemaster{$tmpfile}; $where = $filemaster{$tmpfile}; foreach $unit (split(' ', $filesetin{$tmpfile})) { $message{$unit} .= "\@\@\@$where:$tmpfile "; } } undef %filesetin; # Warn about any set variable which was not listed. foreach $unit (sort keys %shset) { symbol: foreach $sym (split(' ', $shset{$unit})) { next if $shvisible{$sym}; $defined = 0; # Symbol has to be either defined within one of the special units # listed in the dependencies or exported read-write via a ?V: line. # If symbol is exported read-only, report the attempt to set it. $where = $shmaster{"\$$sym"}; study $where; foreach $special (split(' ', $shspecial{$unit})) { $defined = 1 if $where =~ /\b$special\b/; last if $defined; } $visible = 0; $defined = $visible = &visible($sym, $unit) unless $defined; if ($visible && $shvisible{"\$$sym"} ne '') { # We are allowed to set a read-only symbol in the unit which # declared it... next symbol if $shvisible{"\$$sym"} eq $unit; $message{$unit} .= "\&$sym "; # Read-only symbol set next symbol; } $message{$unit} .= "$sym " unless $defined; } } # Warn about any obsolete variable which may be used foreach $unit (sort keys %shused) { foreach $sym (split(' ', $shused{$unit})) { $message{$unit} .= "!$sym " if $Obsolete{$sym} ne ''; } } # Warn about stale dependencies, and prepare successor and predecessor # tables for later topological sort. local($targets, $deps); local(%Succ); # Successors local(%Prec); # Predecessors # Split dependencies and build successors array. foreach $make (@make) { ($targets, $deps) = $make =~ m|(.*):\s*(.*)|; $deps =~ s/\+(\w)/$1/g; # Remove conditional targets foreach $target (split(' ', $targets)) { $Succ{$target} .= $deps . ' '; } } # Special setup for the End target, which normally has a $W dependency for # wanted symbols. In order to detect all the possible cycles, we forge a # huge dependency by making ALL the regular symbols (i.e. those whose first # letter is not uppercased) wanted. local($allwant) = ''; { local($sym, $val); while (($sym, $val) = each %shmaster) { $sym =~ s/^\$//; $allwant .= "$sym " if $val ne ''; } } $Succ{'End'} =~ s/\$W/$allwant/; # Initialize precursors, and spot symbols impossible to 'make', i.e. those # symbols listed in the successors and with no 'make' target. The data # structures %Prec and %Succ will also be used by the cycle lookup code, # in other words, the topological sort. foreach $target (keys %Succ) { $Prec{$target} += 0; # Ensure key is recorded without disturbing. foreach $succ (split(' ', $Succ{$target})) { $Prec{$succ}++; # Successor has one more precursor unless (defined $Succ{$succ} || $said{$succ}++) { foreach $unit (split(' ', $symdep{$succ})) { $message{$unit} .= "?$succ "; # Stale ?MAKE: dependency } } } } undef %symdep; # Check all ?M: dependencies to spot stale ones %said = (); while (($key, $value) = each(%msym)) { next if $value eq ''; # Value is unit name where ?M: occurred foreach $sym (split(' ', $mdep{$key})) { # Loop on C dependencies next if $cmaster{$sym} || $said{$sym}; $message{$value} .= "??$sym "; # Stale ?M: dependency $said{$sym}++; } } undef %said; undef %mdep; undef %msym; # Now actually emit all the warnings local($uv); # Unit defining visible symbol or private file local($w); # Were we are signaling an error foreach $unit (sort keys %message) { undef %said; $w = "\"$unit.U\""; foreach (split(' ', $message{$unit})) { if (s/^#//) { warn "$w: symbol '\$$_' has no default value.\n"; } elsif (s/^\?\?//) { warn "$w: stale ?M: dependency '$_'.\n"; } elsif (s/^\?//) { warn "$w: stale ?MAKE: dependency '$_'.\n"; } elsif (s/^\$//) { if ($shmaster{"\$$_"} ne '') { warn "$w: symbol '\$$_' missing from ?MAKE.\n"; } elsif (($uv = $shvisible{$_}) ne '') { warn "$w: missing $uv from ?MAKE for visible '\$$_'.\n"; } elsif (($uv = $shvisible{"\$$_"}) ne '') { warn "$w: missing $uv from ?MAKE for visible '\$$_'.\n"; } else { warn "\"$unit.U\": unknown symbol '\$$_'.\n"; } ++$said{$_}; } elsif (s/^\&//) { warn "\"$unit.U\": read-only symbol '\$$_' is set.\n"; ++$said{$_}; } elsif (s/^!//) { warn "\"$unit.U\": obsolete symbol '$_' is used.\n"; ++$said{$_}; } elsif (s/^\@\@\@//) { $uv = '?'; # To spot format errors s/^(\w+):// && ($uv = $1); warn "$w: local file '$_' may override the one set by $uv.U.\n"; } elsif (s/^\@\@//) { $uv = $filemaster{$_}; warn "$w: you might not always get file '$_' from $uv.U.\n"; } elsif (s/^\@//) { if ($uv = $filemaster{$_}) { warn "$w: missing $uv from ?MAKE for private file '$_'.\n"; } else { warn "$w: unknown private file '$_'.\n"; } ++$said{"\@$_"}; } else { warn "\"$unit.U\": undeclared symbol '\$$_' is set.\n" unless $said{$_}; } } } # Memory cleanup undef %message; undef %said; undef %shused; undef %shset; undef %shspecial; undef %shvisible; undef %filemaster; # Spot multiply defined C symbols foreach $sym (keys %cmaster) { @sym = split(' ', $cmaster{$sym}); if (@sym > 1) { warn "C symbol '$sym' is defined in the following units:\n"; foreach (@sym) { print STDERR "\t$_.U\n"; } } } undef %cmaster; # Memory cleanup # Warn about multiply defined symbols. There are three kind of symbols: # target symbols, obsolete symbols and temporary symbols. # For each of these sets, we make sure the intersection with the other sets # is empty. Besides, we make sure target symbols are only defined once. local(@sym); foreach $sym (keys %shmaster) { @sym = split(' ', $shmaster{$sym}); if (@sym > 1) { warn "Shell symbol '$sym' is defined in the following units:\n"; foreach (@sym) { print STDERR "\t$_.U\n"; } } $message{$sym} .= 'so ' if $Obsolete{$sym}; $message{$sym} .= 'st ' if $tempmaster{$sym}; } foreach $sym (keys %tempmaster) { $message{$sym} .= 'ot ' if $Obsolete{$sym}; } local($_); while (($sym, $_) = each %message) { if (/so/) { if (/ot/) { warn "Shell symbol '$sym' is altogether:\n"; @sym = split(' ', $shmaster{$sym}); @sym = grep(s/$/.U/, @sym); print STDERR "...defined in: ", join(', ', @sym), "\n"; print STDERR "...obsoleted by $Obsolete{$sym}.\n"; @sym = split(' ', $tempmaster{$sym}); @sym = grep(s/$/.U/, @sym); print STDERR "...used as temporary in:", join(', ', @sym), "\n"; } else { warn "Shell symbol '$sym' is both defined and obsoleted:\n"; @sym = split(' ', $shmaster{$sym}); @sym = grep(s/$/.U/, @sym); print STDERR "...defined in: ", join(', ', @sym), "\n"; print STDERR "...obsoleted by $Obsolete{$sym}.\n"; } } elsif (/st/) { # Cannot be ot as it would imply so warn "Shell symbol '$sym' is both defined and used as temporary:\n"; @sym = split(' ', $shmaster{$sym}); @sym = grep(s/$/.U/, @sym); print STDERR "...defined in: ", join(', ', @sym), "\n"; @sym = split(' ', $tempmaster{$sym}); @sym = grep(s/$/.U/, @sym); print STDERR "...used as temporary in:", join(', ', @sym), "\n"; } elsif (/ot/) { warn "Shell symbol '$sym' obsoleted also used as temporary:\n"; print STDERR "...obsoleted by $Obsolete{$sym}.\n"; @sym = split(' ', $tempmaster{$sym}); @sym = grep(s/$/.U/, @sym); print STDERR "...used as temporary in:", join(', ', @sym), "\n"; } } # Spot multiply defined files, either private or public ones foreach $file (keys %prodfile) { @sym = split(' ', $prodfile{$file}); if (@sym > 1) { warn "File '$file' is defined in the following units:\n"; foreach (@sym) { print STDERR "\t$_\n"; } } } undef %prodfile; # Memory cleanup (we still need %shmaster for tsort) undef %message; undef %tempmaster; undef %Obsolete; # Make sure there is no dependency cycle print "Looking for dependency cycles...\n"; &tsort(*Succ, *Prec); # Destroys info from %Prec } # Make sure last declaration ended correctly with a ?S:. or ?C:. line. # The variable '$where' was correctly positionned by the calling routine. sub main'load_check_last_declaration { package main; warn "$where: definition of '\$$s_symbol' not closed by '?S:.'.\n" if $s_symbol ne ''; warn "$where: definition of '$c_symbol' not closed by '?C:.'.\n" if $c_symbol ne ''; warn "$where: magic definition of '$m_symbol' not closed by '?M:.'.\n" if $m_symbol ne ''; $s_symbol = $c_symbol = $m_symbol = ''; } # Make sure the variable is mentionned on the ?MAKE line, if possible in the # definition section. # The variable '$where' was correctly positionned by the calling routine. sub main'load_check_definition { package main; local($var) = @_; warn "$where: variable '\$$var' not even listed on ?MAKE: line.\n" unless $defseen{$var} || $condseen{$var} || $depseen{$var}; warn "$where: variable '\$$var' is defined externally.\n" if !$lintextern{$var} && !$defseen{$var} && &wanted($var); } # Is symbol declared somewhere? sub main'load_declared { package main; &defined($_[0]) || &wanted($_[0]); } # Is symbol defined by unit? sub main'load_defined { package main; $tempseen{$_[0]} || $defseen{$_[0]} || $lintseen{$_[0]}; } # Is symbol wanted by unit? sub main'load_wanted { package main; $depseen{$_[0]} || $condseen{$_[0]}; } # Is symbol visible from the unit? # Locate visible symbols throughout the special units. Each unit having # some special dependencies (special units wanted) have an entry in the # %shspecial array, listing all those special dependencies. And each # symbol made visible by ONE special unit has an entry in the %shvisible # array. sub main'load_visible { package main; local($symbol, $unit) = @_; local(%explored); # Special units we've already explored &explore($symbol, $unit); # Perform recursive search } # Recursively explore the dependencies to locate a visible symbol sub main'load_explore { package main; local($symbol, $unit) = @_; # If unit was already explored, we know it has not been found by following # that path. return 0 if defined $explored{$unit}; $explored{$unit} = 0; # Assume nothing found in this unit local($specials) = $shspecial{$unit}; # Don't waste any time if unit does not have any special units listed # in its dependencies. return 0 unless $specials; foreach $special (split(' ', $specials)) { return 1 if ( $shvisible{"\$$symbol"} eq $unit || $shvisible{$symbol} eq $unit || &explore($symbol, $special) ); } 0; } # The %Depend array records the functions we use to process the configuration # lines in the unit, with a special meaning. It is important that all the # known control symbols be listed below, so that metalint does not complain. # The %Lcmp array contains valid layouts and their comparaison value. sub main'load_init_depend { package main; %Depend = ( 'MAKE', 'p_make', # The ?MAKE: line records dependencies 'INIT', 'p_init', # Initializations printed verbatim 'LINT', 'p_lint', # Hints for metalint 'RCS', 'p_ignore', # RCS comments are ignored 'C', 'p_c', # C symbols 'D', 'p_default', # Default value for conditional symbols 'E', 'p_example', # Example of usage 'F', 'p_file', # Produced files 'H', 'p_config', # Process the config.h lines 'I', 'p_include', # Added includes 'L', 'p_library', # Added libraries 'M', 'p_magic', # Process the confmagic.h lines 'O', 'p_obsolete', # Unit obsolescence 'P', 'p_public', # Location of PD implementation file 'S', 'p_shell', # Shell variables 'T', 'p_temp', # Shell temporaries used 'V', 'p_visible', # Visible symbols like 'rp', 'dflt' 'W', 'p_wanted', # Wanted value for interpreter 'X', 'p_ignore', # User comment is ignored 'Y', 'p_layout', # User-defined layout preference ); %Lcmp = ( 'top', -1, 'default', 0, 'bottom', 1, ); } # Extract dependencies from units held in @ARGV sub main'load_extract_dependencies { package main; local($proc); # Procedure used to handle a ctrl line local($file); # Current file scanned local($dir, $unit); # Directory and unit's name local($old_version) = 0; # True when old-version unit detected local($mc) = "$MC/U"; # Public metaconfig directory local($line); # Last processed line for metalint printf "Extracting dependency lists from %d units...\n", $#ARGV+1 unless $opt_s; chdir $WD; # Back to working directory &init_extraction; # Initialize extraction files $dependencies = ' ' x (50 * @ARGV); # Pre-extend $dependencies = ''; # We do not want to use the <> construct here, because we need the # name of the opened files (to get the unit's name) and we want to # reset the line number for each files, and do some pre-processing. file: while ($file = shift(@ARGV)) { close FILE; # Reset line number $old_version = 0; # True if unit is an old version if (open(FILE, $file)) { ($dir, $unit) = ('', $file) unless ($dir, $unit) = ($file =~ m|(.*)/(.*)|); $unit =~ s|\.U$||; # Remove extension } else { warn("Can't open $file.\n"); } # If unit is in the standard public directory, keep only the unit name $file = "$unit.U" if $dir eq $mc; print "$dir/$unit.U:\n" if $opt_d; line: while () { $line = $_; # Save last processed unit line if (s/^\?([\w\-]+)://) { # We may have found a control line $proc = $Depend{$1}; # Look for a procedure to handle it unless ($proc) { # Unknown control line $proc = $1; # p_unknown expects symbol in '$proc' eval '&p_unknown'; # Signal error (metalint only) next line; # And go on next line } # Long lines may be escaped with a final backslash $_ .= &complete_line(FILE) if s/\\\s*$//; # Run macros substitutions s/%)); next file; } } } continue { warn(" Warning: $file is a pre-3.0 version.\n") if $old_version; &$ending($line) if $ending; # Post-processing for metalint } &end_extraction; # End the extraction process } # The first line was escaped with a final \ character. Every following line # is to be appended to it (until we found a real \n not escaped). Note that # the leading spaces of the continuation line are removed, so any space should # be added before the former \ if needed. sub main'load_complete_line { package main; local($file) = @_; # File where lines come from local($_); local($read) = ''; # Concatenation of all the continuation lines found while (<$file>) { s/^\s+//; # Remove leading spaces if (s/\\\s*$//) { # Still followed by a continuation line $read .= $_; } else { # We've reached the end of the continuation return $read . $_; } } } # Record obsolete symbols association (new versus old), that is to say for a # given old symbol, $Obsolete{'old'} = new symbol to be used. A '$' is prepended # for all shell variables sub main'load_record_obsolete { package main; local($_) = @_; local(@obsoleted); # List of obsolete symbols local($symbol); # New symbol which must be used local($dollar) = s/^\$// ? '$':''; # The '$' or a null string # Syntax for obsolete symbols specification is # list of symbols (obsolete ones): if (/^(\w+)\s*\((.*)\)\s*:$/) { $symbol = "$dollar$1"; @obsoleted = split(' ', $2); # List of obsolete symbols } else { if (/^(\w+)\s*\((.*):$/) { warn "\"$file\", line $.: final ')' before ':' missing.\n"; $symbol = "$dollar$1"; @obsoleted = split(' ', $2); } else { warn "\"$file\", line $.: syntax error.\n"; return; } } foreach $val (@obsoleted) { $_ = $dollar . $val; if (defined $Obsolete{$_}) { warn "\"$file\", line $.: '$_' already obsoleted by '$Obsolete{$_}'.\n"; } else { $Obsolete{$_} = $symbol; # Record (old, new) tuple } } } # Dump obsolete symbols used in file 'Obsolete'. Also write Obsol_h.U and # Obsol_sh.U to record old versus new mappings if the -o option was used. sub main'load_dump_obsolete { package main; unless (-f 'Obsolete') { open(OBSOLETE, ">Obsolete") || die "Can't create Obsolete.\n"; } open(OBSOL_H, ">.MT/Obsol_h.U") || die "Can't create .MT/Obsol_h.U.\n"; open(OBSOL_SH, ">.MT/Obsol_sh.U") || die "Can't create .MT/Obsol_sh.U.\n"; local($file); # File where obsolete symbol was found local($old); # Name of this old symbol local($new); # Value of the new symbol to be used # Leave a blank line at the top so that anny added ^L will stand on a line # by itself (the formatting process adds a ^L when a new page is needed). format OBSOLETE_TOP = File | Old symbol | New symbol -----------------------------------+----------------------+--------------------- . format OBSOLETE = @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | @<<<<<<<<<<<<<<<<<<< | @<<<<<<<<<<<<<<<<<<< $file, $old, $new . local(%seen); foreach $key (sort keys %ofound) { ($file, $old, $new) = ($key =~ /^(\S+)\s+(\S+)\s+(\S+)/); write(OBSOLETE) unless $file eq 'XXX'; next unless $opt_o; # Obsolete mapping done only with -o next if $seen{$old}++; # Already remapped, thank you if ($new =~ s/^\$//) { # We found an obsolete shell symbol $old =~ s/^\$//; print OBSOL_SH "$old=\"\$$new\"\n"; } else { # We found an obsolete C symbol print OBSOL_H "#ifdef $new\n"; print OBSOL_H "#define $old $new\n"; print OBSOL_H "#endif\n\n"; } } close OBSOLETE; close OBSOL_H; close OBSOL_SH; if (-s 'Obsolete') { print "*** Obsolete symbols found -- see file 'Obsolete' for a list.\n"; } else { unlink 'Obsolete'; } undef %ofound; # Not needed any more } # Perform the topological sort of the items and outline cycles. sub main'load_tsort { package tsort; local(*Succ, *Prec) = @_; # Tables of succesors and predecessors local(@Out); # The outsider set local(@keys); # Current active precursors local($item); # Item to sort for (@keys = keys %Prec; @keys || @Out; @keys = keys %Prec) { &resync; # Resynchronize outsiders if (@Out == 0) { # Cycle detected &extract_cycle(*Prec, *Succ); next; } $item = shift(@Out); # Sort current item (don't care which one) &sort($item); # Update internal structures } } # Resynchronize the outsiders stack (those items that have no more precursors). # If the outsiders stack becomes empty, then there is a cycle. sub tsort'load_resync { package tsort; foreach $target (keys %Prec) { if ($Prec{$target} == 0) { delete $Prec{$target}; # We're done with this item push(@Out, $target); # Ready to be sorted } } } # Sort item sub tsort'load_sort { package tsort; local($item) = @_; print "(ok) $item\n" if $main'opt_d && !$Cycle; print "(fx) $item\n" if $main'opt_d && $Cycle; foreach $succ (split(' ', $Succ{$item})) { # The test for definedness is necessary, since when a cycle is found, # one item is forced out of %Prec. If we had the guarantee of no # cycle, the the test would not be necessary and no decrementation # could go past 0. $Prec{$succ}-- if defined $Prec{$succ}; } } # Extract cycle... We look through the %Prec array and find all those items # with the same lowest value. Those are a cycle, so we dump them, and make # them new outsiders by resetting their count to 0. sub tsort'load_extract_cycle { package tsort; local(*Prec, *Succ) = @_; local($item) = (&sort_by_value(*Prec))[0]; local($min) = $Prec{$item}; # Minimum value local($key, $value); local(%candidate); # Superset of the cycle we found warn " Cycle found for:\n"; $Cycle++; while (($key, $value) = each %Prec) { $candidate{$key}++ if $value == $min; } local(%state); # State of visited nodes (1 = cycle, -1 = dead) local($CYCLE) = 1; # Possible member of a cycle local($DEAD) = -1; # Dead end, no cycling possible foreach $key (keys %candidate) { last if $CYCLE == &visit($key, $Succ{$key}); } while (($key, $value) = each %candidate) { next unless $state{$key} == $CYCLE; $Prec{$key} = 0; # Members of cycle are new outsiders warn "\t(#$Cycle) $key\n"; } local(%involved); # Items involved in the cycle... while (($key, $value) = each %state) { $involved{$key}++ if $state{$key} == $CYCLE; } &outline_cycle(*Succ, *involved); } sub tsort'load_outline_cycle { package tsort; local(*Succ, *member) = @_; local($key, $value); local($depends); local($unit); warn " Cycle involves:\n"; while (($key, $value) = each %Succ) { next unless $member{$key}; $depends = ''; foreach $item (split(' ', $value)) { $depends .= "$item " if $member{$item}; } $unit = $main'shmaster{"\$$key"}; $unit =~ s/\s+$//; $unit = '?' if $unit eq ''; warn "\t($unit) $key: $depends\n"; } } # Visit a tree node, following all its successors, until we find a cycle. # Return $CYCLE if the exploration of the node leaded to a cycle, $DEAD # otherwise. sub tsort'load_visit { package tsort; local($node, $children) = @_; # A node and its children # If we have already visited the node, return the status value attached # to it. return $state{$node} if $state{$node}; $state{$node} = $CYCLE; # Assume member of cycle local($all_dead) = 1; # Set to 0 if at least one cycle found foreach $child (split(' ', $children)) { $all_dead = 0 if $CYCLE == &visit($child, $Succ{$child}); } $state{$node} = $DEAD if $all_dead; $state{$node}; } # Sort associative array by value sub tsort'load_sort_by_value { package tsort; local(*x) = @_; sub _by_value { $x{$a} <=> $x{$b}; } sort _by_value keys %x; } # Perform ~name expansion ala ksh... # (banish csh from your vocabulary ;-) sub main'load_tilda_expand { package main; local($path) = @_; return $path unless $path =~ /^~/; $path =~ s:^~([^/]+):(getpwnam($1))[$[+7]:e; # ~name $path =~ s:^~:$ENV{'HOME'} || (getpwuid($<))[$[+7]:e; # ~ $path; } # Set up profile components into %Profile, add any profile-supplied options # into @ARGV and return the command invocation name. sub main'load_profile { package main; local($profile) = &tilda_expand($ENV{'DIST'} || '~/.dist_profile'); local($me) = $0; # Command name $me =~ s|.*/(.*)|$1|; # Keep only base name return $me unless -s $profile; local(*PROFILE); # Local file descriptor local($options) = ''; # Options we get back from profile unless (open(PROFILE, $profile)) { warn "$me: cannot open $profile: $!\n"; return; } local($_); local($component); while () { next if /^\s*#/; # Skip comments next unless /^$me/o; if (s/^$me://o) { # progname: options chop; $options .= $_; # Merge options if more than one line } elsif (s/^$me-([^:]+)://o) { # progname-component: value $component = $1; chop; s/^\s+//; # Trim leading and trailing spaces s/\s+$//; $Profile{$component} = $_; } } close PROFILE; return unless $options; require 'shellwords.pl'; local(@opts); eval '@opts = &shellwords($options)'; # Protect against mismatched quotes unshift(@ARGV, @opts); return $me; # Return our invocation name } # # End of dataloading section. #