# Copyright (C) 2018 Free Software Foundation, Inc. # 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 2 # 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 . package Automake::LangHandling; use Automake::Condition qw (TRUE FALSE); use Automake::ConfVars; use Automake::ChannelDefs; use Automake::Global; use Automake::Language; use Automake::Location; use Automake::Options; use Automake::Requires; use Automake::Rule; use Automake::RuleDef; use Automake::SilentRules; use Automake::Utils; use Automake::Variable; use Automake::VarDef; use Automake::Wrap qw (makefile_wrap); use Exporter 'import'; use File::Basename; use vars qw (@EXPORT); @EXPORT = qw (check_user_variables lang_sub_obj lang_header_rewrite lang_vala_rewrite lang_yacc_rewrite lang_yaccxx_rewrite lang_lex_rewrite lang_lexxx_rewrite lang_java_rewrite lang_vala_finish_target lang_vala_finish lang_vala_target_hook lang_yacc_target_hook lang_lex_target_hook yacc_lex_finish_helper lang_yacc_finish lang_lex_finish resolve_linker saw_extension register_language derive_suffix pretty_print_rule handle_emacs_lisp handle_python handle_java); # check_user_variables (@LIST) # ---------------------------- # Make sure each variable VAR in @LIST does not exist, suggest using AM_VAR # otherwise. sub check_user_variables { my @dont_override = @_; foreach my $flag (@dont_override) { my $var = var $flag; if ($var) { for my $cond ($var->conditions->conds) { if ($var->rdef ($cond)->owner == VAR_MAKEFILE) { msg_cond_var ('gnu', $cond, $flag, "'$flag' is a user variable, " . "you should not override it;\n" . "use 'AM_$flag' instead"); } } } } } ################################################################ # # Functions to handle files of each language. # Each 'lang_X_rewrite($DIRECTORY, $BASE, $EXT)' function follows a # simple formula: Return value is LANG_SUBDIR if the resulting object # file should be in a subdir if the source file is, LANG_PROCESS if # file is to be dealt with, LANG_IGNORE otherwise. # Much of the actual processing is handled in # handle_single_transform. These functions exist so that # auxiliary information can be recorded for a later cleanup pass. # Note that the calls to these functions are computed, so don't bother # searching for their precise names in the source. # This is just a convenience function that can be used to determine # when a subdir object should be used. sub lang_sub_obj () { return option 'subdir-objects' ? LANG_SUBDIR : LANG_PROCESS; } # Rewrite a single header file. sub lang_header_rewrite { # Header files are simply ignored. return LANG_IGNORE; } # Rewrite a single Vala source file. sub lang_vala_rewrite { my ($directory, $base, $ext) = @_; (my $newext = $ext) =~ s/vala$/c/; return (LANG_SUBDIR, $newext); } # Rewrite a single yacc/yacc++ file. sub lang_yacc_rewrite { my ($directory, $base, $ext) = @_; my $r = lang_sub_obj; (my $newext = $ext) =~ tr/y/c/; return ($r, $newext); } sub lang_yaccxx_rewrite { lang_yacc_rewrite (@_); }; # Rewrite a single lex/lex++ file. sub lang_lex_rewrite { my ($directory, $base, $ext) = @_; my $r = lang_sub_obj; (my $newext = $ext) =~ tr/l/c/; return ($r, $newext); } sub lang_lexxx_rewrite { lang_lex_rewrite (@_); }; # Rewrite a single Java file. sub lang_java_rewrite { return LANG_SUBDIR; } # The lang_X_finish functions are called after all source file # processing is done. Each should handle defining rules for the # language, etc. A finish function is only called if a source file of # the appropriate type has been seen. sub lang_vala_finish_target { my ($self, $name) = @_; my $derived = canonicalize ($name); my $var = var "${derived}_SOURCES"; return unless $var; my @vala_sources = grep { /\.(vala|vapi)$/ } ($var->value_as_list_recursive); # For automake bug#11229. return unless @vala_sources; foreach my $vala_file (@vala_sources) { my $c_file = $vala_file; if ($c_file =~ s/(.*)\.vala$/$1.c/) { $c_file = "\$(srcdir)/$c_file"; $output_rules .= "$c_file: \$(srcdir)/${derived}_vala.stamp\n" . "\t\@if test -f \$@; then :; else rm -f \$(srcdir)/${derived}_vala.stamp; fi\n" . "\t\@if test -f \$@; then :; else \\\n" . "\t \$(MAKE) \$(AM_MAKEFLAGS) \$(srcdir)/${derived}_vala.stamp; \\\n" . "\tfi\n"; $clean_files{$c_file} = MAINTAINER_CLEAN; } } # Add rebuild rules for generated header and vapi files my $flags = var ($derived . '_VALAFLAGS'); if ($flags) { my $lastflag = ''; foreach my $flag ($flags->value_as_list_recursive) { if (grep (/$lastflag/, ('-H', '-h', '--header', '--internal-header', '--vapi', '--internal-vapi', '--gir'))) { my $headerfile = "\$(srcdir)/$flag"; $output_rules .= "$headerfile: \$(srcdir)/${derived}_vala.stamp\n" . "\t\@if test -f \$@; then :; else rm -f \$(srcdir)/${derived}_vala.stamp; fi\n" . "\t\@if test -f \$@; then :; else \\\n" . "\t \$(MAKE) \$(AM_MAKEFLAGS) \$(srcdir)/${derived}_vala.stamp; \\\n" . "\tfi\n"; # valac is not used when building from dist tarballs # distribute the generated files push_dist_common ($headerfile); $clean_files{$headerfile} = MAINTAINER_CLEAN; } $lastflag = $flag; } } my $compile = $self->compile; # Rewrite each occurrence of 'AM_VALAFLAGS' in the compile # rule into '${derived}_VALAFLAGS' if it exists. my $val = "${derived}_VALAFLAGS"; $compile =~ s/\(AM_VALAFLAGS\)/\($val\)/ if set_seen ($val); # VALAFLAGS is a user variable (per GNU Standards), # it should not be overridden in the Makefile... check_user_variables 'VALAFLAGS'; my $dirname = dirname ($name); # Only generate C code, do not run C compiler $compile .= " -C"; my $verbose = verbose_flag ('VALAC'); my $silent = silent_flag (); my $stampfile = "\$(srcdir)/${derived}_vala.stamp"; $output_rules .= "\$(srcdir)/${derived}_vala.stamp: @vala_sources\n". # Since the C files generated from the vala sources depend on the # ${derived}_vala.stamp file, we must ensure its timestamp is older than # those of the C files generated by the valac invocation below (this is # especially important on systems with sub-second timestamp resolution). # Thus we need to create the stamp file *before* invoking valac, and to # move it to its final location only after valac has been invoked. "\t${silent}rm -f \$\@ && echo stamp > \$\@-t\n". "\t${verbose}\$(am__cd) \$(srcdir) && $compile @vala_sources\n". "\t${silent}mv -f \$\@-t \$\@\n"; push_dist_common ($stampfile); $clean_files{$stampfile} = MAINTAINER_CLEAN; } # Add output rules to invoke valac and create stamp file as a witness # to handle multiple outputs. This function is called after all source # file processing is done. sub lang_vala_finish () { my ($self) = @_; foreach my $prog (keys %known_programs) { lang_vala_finish_target ($self, $prog); } while (my ($name) = each %known_libraries) { lang_vala_finish_target ($self, $name); } } # The built .c files should be cleaned only on maintainer-clean # as the .c files are distributed. This function is called for each # .vala source file. sub lang_vala_target_hook { my ($self, $aggregate, $output, $input, %transform) = @_; $clean_files{$output} = MAINTAINER_CLEAN; } # This is a yacc helper which is called whenever we have decided to # compile a yacc file. sub lang_yacc_target_hook { my ($self, $aggregate, $output, $input, %transform) = @_; # If some relevant *YFLAGS variable contains the '-d' flag, we'll # have to to generate special code. my $yflags_contains_minus_d = 0; foreach my $pfx ("", "${aggregate}_") { my $yflagsvar = var ("${pfx}YFLAGS"); next unless $yflagsvar; # We cannot work reliably with conditionally-defined YFLAGS. if ($yflagsvar->has_conditional_contents) { msg_var ('unsupported', $yflagsvar, "'${pfx}YFLAGS' cannot have conditional contents"); } else { $yflags_contains_minus_d = 1 if grep (/^-d$/, $yflagsvar->value_as_list_recursive); } } if ($yflags_contains_minus_d) { # Found a '-d' that applies to the compilation of this file. # Add a dependency for the generated header file, and arrange # for that file to be included in the distribution. # The extension of the output file (e.g., '.c' or '.cxx'). # We'll need it to compute the name of the generated header file. (my $output_ext = basename ($output)) =~ s/.*(\.[^.]+)$/$1/; # We know that a yacc input should be turned into either a C or # C++ output file. We depend on this fact (here and in yacc.am), # so check that it really holds. my $lang = $languages{$extension_map{$output_ext}}; prog_error "invalid output name '$output' for yacc file '$input'" if (!$lang || ($lang->name ne 'c' && $lang->name ne 'cxx')); (my $header_ext = $output_ext) =~ s/c/h/g; # Quote $output_ext in the regexp, so that dots in it are taken # as literal dots, not as metacharacters. (my $header = $output) =~ s/\Q$output_ext\E$/$header_ext/; foreach my $cond (Automake::Rule::define (${header}, 'internal', RULE_AUTOMAKE, TRUE, INTERNAL)) { my $condstr = $cond->subst_string; $output_rules .= "$condstr${header}: $output\n" # Recover from removal of $header . "$condstr\t\@if test ! -f \$@; then rm -f $output; else :; fi\n" . "$condstr\t\@if test ! -f \$@; then \$(MAKE) \$(AM_MAKEFLAGS) $output; else :; fi\n"; } # Distribute the generated file, unless its .y source was # listed in a nodist_ variable. (handle_source_transform() # will set DIST_SOURCE.) push_dist_common ($header) if $transform{'DIST_SOURCE'}; # The GNU rules say that yacc/lex output files should be removed # by maintainer-clean. However, if the files are not distributed, # then we want to remove them with "make clean"; otherwise, # "make distcheck" will fail. $clean_files{$header} = $transform{'DIST_SOURCE'} ? MAINTAINER_CLEAN : CLEAN; } # See the comment above for $HEADER. $clean_files{$output} = $transform{'DIST_SOURCE'} ? MAINTAINER_CLEAN : CLEAN; } # This is a lex helper which is called whenever we have decided to # compile a lex file. sub lang_lex_target_hook { my ($self, $aggregate, $output, $input, %transform) = @_; # The GNU rules say that yacc/lex output files should be removed # by maintainer-clean. However, if the files are not distributed, # then we want to remove them with "make clean"; otherwise, # "make distcheck" will fail. $clean_files{$output} = $transform{'DIST_SOURCE'} ? MAINTAINER_CLEAN : CLEAN; } # This is a helper for both lex and yacc. sub yacc_lex_finish_helper () { return if defined $language_scratch{'lex-yacc-done'}; $language_scratch{'lex-yacc-done'} = 1; # FIXME: for now, no line number. require_conf_file ($configure_ac, FOREIGN, 'ylwrap'); define_variable ('YLWRAP', "$am_config_aux_dir/ylwrap", INTERNAL); } sub lang_yacc_finish () { return if defined $language_scratch{'yacc-done'}; $language_scratch{'yacc-done'} = 1; reject_var 'YACCFLAGS', "'YACCFLAGS' obsolete; use 'YFLAGS' instead"; yacc_lex_finish_helper; } sub lang_lex_finish () { return if defined $language_scratch{'lex-done'}; $language_scratch{'lex-done'} = 1; yacc_lex_finish_helper; } # Given a hash table of linker names, pick the name that has the most # precedence. This is lame, but something has to have global # knowledge in order to eliminate the conflict. Add more linkers as # required. sub resolve_linker { my (%linkers) = @_; foreach my $l (qw(GCJLINK OBJCXXLINK CXXLINK F77LINK FCLINK OBJCLINK UPCLINK)) { return $l if defined $linkers{$l}; } return 'LINK'; } # Called to indicate that an extension was used. sub saw_extension { my ($ext) = @_; $extension_seen{$ext} = 1; } # register_language (%ATTRIBUTE) # ------------------------------ # Register a single language. # Each %ATTRIBUTE is of the form ATTRIBUTE => VALUE. sub register_language { my (%option) = @_; # Set the defaults. $option{'autodep'} = 'no' unless defined $option{'autodep'}; $option{'linker'} = '' unless defined $option{'linker'}; $option{'flags'} = [] unless defined $option{'flags'}; $option{'output_extensions'} = sub { return ( '.$(OBJEXT)', '.lo' ) } unless defined $option{'output_extensions'}; $option{'nodist_specific'} = 0 unless defined $option{'nodist_specific'}; my $lang = new Automake::Language (%option); # Fill indexes. $extension_map{$_} = $lang->name foreach @{$lang->extensions}; $languages{$lang->name} = $lang; my $link = $lang->linker; if ($link) { if (exists $link_languages{$link}) { prog_error ("'$link' has different definitions in " . $lang->name . " and " . $link_languages{$link}->name) if $lang->link ne $link_languages{$link}->link; } else { $link_languages{$link} = $lang; } } # Update the pattern of known extensions. accept_extensions (@{$lang->extensions}); # Update the suffix rules map. foreach my $suffix (@{$lang->extensions}) { foreach my $dest ($lang->output_extensions->($suffix)) { register_suffix_rule (INTERNAL, $suffix, $dest); } } } # derive_suffix ($EXT, $OBJ) # -------------------------- # This function is used to find a path from a user-specified suffix $EXT # to $OBJ or to some other suffix we recognize internally, e.g. 'cc'. sub derive_suffix { my ($source_ext, $obj) = @_; while (!$extension_map{$source_ext} && $source_ext ne $obj) { my $new_source_ext = next_in_suffix_chain ($source_ext, $obj); last if not defined $new_source_ext; $source_ext = $new_source_ext; } return $source_ext; } # Pretty-print something and append to '$output_rules'. sub pretty_print_rule { $output_rules .= makefile_wrap (shift, shift, @_); } sub handle_emacs_lisp () { my @elfiles = am_install_var ('-candist', 'lisp', 'LISP', 'lisp', 'noinst'); return if ! @elfiles; define_pretty_variable ('am__ELFILES', TRUE, INTERNAL, map { $_->[1] } @elfiles); define_pretty_variable ('am__ELCFILES', TRUE, INTERNAL, '$(am__ELFILES:.el=.elc)'); # This one can be overridden by users. define_pretty_variable ('ELCFILES', TRUE, INTERNAL, '$(LISP:.el=.elc)'); push @all, '$(ELCFILES)'; require_variables ($elfiles[0][0], "Emacs Lisp sources seen", TRUE, 'EMACS', 'lispdir'); } sub handle_python () { my @pyfiles = am_install_var ('-defaultdist', 'python', 'PYTHON', 'noinst'); return if ! @pyfiles; require_variables ($pyfiles[0][0], "Python sources seen", TRUE, 'PYTHON'); require_conf_file ($pyfiles[0][0], FOREIGN, 'py-compile'); define_variable ('py_compile', "$am_config_aux_dir/py-compile", INTERNAL); } sub handle_java () { my @sourcelist = am_install_var ('-candist', 'java', 'JAVA', 'noinst', 'check'); return if ! @sourcelist; my @prefixes = am_primary_prefixes ('JAVA', 1, 'noinst', 'check'); my $dir; my @java_sources = (); foreach my $prefix (@prefixes) { (my $curs = $prefix) =~ s/^(?:nobase_)?(?:dist_|nodist_)?//; next if $curs eq 'EXTRA'; push @java_sources, '$(' . $prefix . '_JAVA' . ')'; if (defined $dir) { err_var "${curs}_JAVA", "multiple _JAVA primaries in use" unless $curs eq $dir; } $dir = $curs; } define_pretty_variable ('am__java_sources', TRUE, INTERNAL, "@java_sources"); if ($dir eq 'check') { push (@check, "class$dir.stamp"); } else { push (@all, "class$dir.stamp"); } } 1;