forked from coreutils/gnulib
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGLEmiter.py
1252 lines (1184 loc) · 62.4 KB
/
GLEmiter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright (C) 2002-2024 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 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 <https://www.gnu.org/licenses/>.
from __future__ import annotations
#===============================================================================
# Define global imports
#===============================================================================
import os
import re
import subprocess as sp
from collections.abc import Callable
from .constants import (
UTILS,
joinpath,
lines_to_multiline,
combine_lines_matching,
substart,
relinverse,
)
from .GLInfo import GLInfo
from .GLConfig import GLConfig
from .GLModuleSystem import GLModule
from .GLModuleSystem import GLModuleTable
from .GLMakefileTable import GLMakefileTable
# Regular expressions used to convert Automake conditionals to GNU Make syntax.
# Each tuple is the arguments given to re.sub in the correct order.
_CONVERT_TO_GNU_MAKE = [
(re.compile(r'^if (.*)', re.MULTILINE), r'ifneq (,$(\1_CONDITION))'),
(re.compile(r'%reldir%/'), r''),
(re.compile(r'%reldir%'), r'.')
]
def _convert_to_gnu_make(snippet: str) -> str:
'''Convert a Automake snippet to GNU Make syntax.'''
if type(snippet) is not str:
raise TypeError(f'snippet must be a str, not {type(snippet).__name__}')
for regexp in _CONVERT_TO_GNU_MAKE:
snippet = re.sub(regexp[0], regexp[1], snippet)
return snippet
def _eliminate_NMD_from_line(line: str, automake_subdir: bool) -> str | None:
'''Eliminate occurrences of @NMD@ and @!NMD@ from the given line. The
modified line is returned or None if the line should be removed.
line is the current line in the snippet being operated on.
automake_subdir is a bool that is True if --automake-subdir is in use,
else False.'''
if automake_subdir:
clean = '@NMD@'
eliminate = '@!NMD@'
else:
clean = '@!NMD@'
eliminate = '@NMD@'
# Check if we should eliminate the line from the output.
if line.startswith(eliminate):
return None
# Check if we should clean the mark but keep the line.
if line.startswith(clean):
return line.replace(clean, '')
return line
def _eliminate_NMD(snippet: str, automake_subdir: bool) -> str:
'''Return the Automake snippet with occurrences of @NMD@ and @!NMD@
removed.
snippet is the Automake snippet being operated on.
automake_subdir is a bool that is True if --automake-subdir is in use,
else False.'''
result = []
for line in snippet.splitlines():
line = _eliminate_NMD_from_line(line, automake_subdir)
if line != None:
result.append(line)
return lines_to_multiline(result)
#===============================================================================
# Define GLEmiter class
#===============================================================================
class GLEmiter:
'''This class is used to emit the contents of necessary files.'''
info: GLInfo
config: GLConfig
def __init__(self, config: GLConfig) -> None:
'''Create GLEmiter instance.'''
self.info = GLInfo()
if type(config) is not GLConfig:
raise TypeError('config must be a GLConfig, not %s'
% type(config).__name__)
self.config = config
def __repr__(self) -> str:
'''x.__repr__() <==> repr(x)'''
result = '<pygnulib.GLEmiter %s>' % hex(id(self))
return result
def copyright_notice(self) -> str:
'''Emit a header for a generated file.'''
emit = '# %s' % self.info.copyright_range()
emit += '''
#
# This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
#
# As a special exception to the GNU General Public License,
# this file may be distributed as part of a program that
# contains a configuration script generated by Autoconf, under
# the same distribution terms as the rest of that program.
#
# Generated by gnulib-tool.\n'''
return emit
def shellvars_init(self, gentests: bool, base: str) -> str:
'''Emits some shell variable assignments.
gentests is a bool that is True if a tests Makefile.am is being
generated, False otherwise.
base is a string representing the base directory relative to the
top-level directory.'''
if type(gentests) is not bool:
raise TypeError(f'gentests must be a bool, not {type(gentests).__name__}')
if type(base) is not str:
raise TypeError(f'base must be a str, not {type(base).__name__}')
sourcebase = self.config['sourcebase']
testsbase = self.config['testsbase']
automake_subdir = self.config['automake_subdir']
automake_subdir_tests = self.config['automake_subdir_tests']
# Define the base directory, relative to the top-level directory.
emit = f' gl_source_base=\'{base}\'\n'
# Define the prefix for the file name of generated files.
if gentests and automake_subdir_tests:
# When tests share the same Makefile as the whole project, they
# share the same base prefix.
if base == testsbase:
emit += f' gl_source_base_prefix=\'$(top_build_prefix){sourcebase}/\'\n'
else:
emit += f' gl_source_base_prefix=\'$(top_build_prefix){base}/\'\n'
elif not gentests and automake_subdir:
emit += f' gl_source_base_prefix=\'$(top_build_prefix){base}/\'\n'
else:
emit += ' gl_source_base_prefix=\n'
return emit
def autoconfSnippet(self, module: GLModule, toplevel: bool, disable_libtool: bool,
disable_gettext: bool, replace_auxdir: bool, indentation: str) -> str:
'''Emit the autoconf snippet of a module.
GLConfig: include_guard_prefix.
module is a GLModule instance, which is processed.
toplevel is a bool variable, False means a subordinate use of pygnulib.
disable_libtool is a bool variable; it tells whether to disable libtool
handling even if it has been specified through the GLConfig class.
disable_gettext is a bool variable; it tells whether to disable
AM_GNU_GETTEXT invocations.
replace_auxdir is a bool variable; it tells whether to replace
'build-aux' directory in AC_CONFIG_FILES.
indentation is a string which contain spaces to prepend on each line.'''
if type(module) is not GLModule:
raise TypeError('module must be a GLModule, not %s'
% type(module).__name__)
if type(toplevel) is not bool:
raise TypeError('toplevel must be a bool, not %s'
% type(toplevel).__name__)
if type(disable_libtool) is not bool:
raise TypeError('disable_libtool must be a bool, not %s'
% type(disable_libtool).__name__)
if type(disable_gettext) is not bool:
raise TypeError('disable_gettext must be a bool, not %s'
% type(disable_gettext).__name__)
if type(indentation) is not str:
raise TypeError('indentation must be a string, not %s'
% type(indentation).__name__)
if not indentation.isspace():
raise ValueError('indentation must contain only whitespaces')
auxdir = self.config['auxdir']
libtool = self.config['libtool']
include_guard_prefix = self.config['include_guard_prefix']
emit = ''
if module.name in ['gnumakefile', 'maintainer-makefile']:
# These modules are meant to be used only in the top-level directory.
flag = toplevel
else: # if module.name not in ['gnumakefile', 'maintainer-makefile']
flag = True
if flag:
snippet = module.getAutoconfSnippet()
snippet = snippet.replace('${gl_include_guard_prefix}',
include_guard_prefix)
lines = [ f'{indentation}{line}'
for line in snippet.split('\n')
if line.strip() ]
snippet = lines_to_multiline(lines)
if disable_libtool:
snippet = snippet.replace('$gl_cond_libtool', 'false')
snippet = snippet.replace('gl_libdeps', 'gltests_libdeps')
snippet = snippet.replace('gl_ltlibdeps', 'gltests_ltlibdeps')
if disable_gettext:
snippet = snippet.replace('AM_GNU_GETTEXT([external])',
'dnl you must add AM_GNU_GETTEXT([external]) or similar to configure.ac.')
else:
# Don't indent AM_GNU_GETTEXT_VERSION line, as that confuses
# autopoint through at least GNU gettext version 0.18.2.
snippet = re.compile(r'^ *AM_GNU_GETTEXT_VERSION', re.M).sub(r'AM_GNU_GETTEXT_VERSION', snippet)
emit += snippet
if module.name == 'alloca' and libtool and not disable_libtool:
emit += 'changequote(,)dnl\n'
emit += "LTALLOCA=`echo \"$ALLOCA\" | sed -e 's/\\.[^.]* /.lo /g;s/\\.[^.]*$/.lo/'`\n"
emit += 'changequote([, ])dnl\n'
emit += 'AC_SUBST([LTALLOCA])'
if replace_auxdir:
regex = r'AC_CONFIG_FILES\(\[(.*)\:build-aux/(.*)\]\)'
repl = r'AC_CONFIG_FILES([\1:%s/\2])' % auxdir
pattern = re.compile(regex, re.M)
emit = pattern.sub(repl, emit)
lines = [ line
for line in emit.split('\n')
if line.strip() ]
emit = lines_to_multiline(lines)
return emit
def autoconfSnippets(self, modules: list[GLModule], referenceable_modules: list[GLModule],
moduletable: GLModuleTable, module_filter: Callable[[GLModule], bool], toplevel: bool,
disable_libtool: bool, disable_gettext: bool, replace_auxdir: bool) -> str:
'''Collect and emit the autoconf snippets of a set of modules.
GLConfig: conddeps.
basemodules argument represents list of modules; every module in this list
must be a GLModule instance; this list of modules is used to sort all
modules after they were processed.
modules argument represents list of modules; every module in this list must
be a GLModule instance.
referenceable_modules is the list of modules which may be referenced as
dependencies.
moduletable is a GLModuleTable instance, which contains necessary
information about dependencies of the modules.
module_filter is a function that accepts a GLModule and returns a bool describing
whether or not Autoconf snippets should be emitted for it.
toplevel is a bool variable, False means a subordinate use of pygnulib.
disable_libtool is a bool variable; it tells whether to disable libtool
handling even if it has been specified through the GLConfig class.
disable_gettext is a bool variable; it tells whether to disable
AM_GNU_GETTEXT invocations.
replace_auxdir is a bool variable; it tells whether to replace
'build-aux' directory in AC_CONFIG_FILES.'''
for module in modules:
if type(module) is not GLModule:
raise TypeError('each module must be a GLModule instance')
for module in referenceable_modules:
if type(module) is not GLModule:
raise TypeError('each referenceable module must be a GLModule instance')
if type(moduletable) is not GLModuleTable:
raise TypeError('moduletable must be a GLModuleTable, not %s'
% type(moduletable).__name__)
if not callable(module_filter):
raise TypeError('module_filter must be a function, not %s'
% type(module_filter).__name__)
if type(toplevel) is not bool:
raise TypeError('toplevel must be a bool, not %s'
% type(toplevel).__name__)
if type(disable_libtool) is not bool:
raise TypeError('disable_libtool must be a bool, not %s'
% type(disable_libtool).__name__)
if type(disable_gettext) is not bool:
raise TypeError('disable_gettext must be a bool, not %s'
% type(disable_gettext).__name__)
if type(replace_auxdir) is not bool:
raise TypeError('replace_auxdir must be a bool, not %s'
% type(replace_auxdir).__name__)
conddeps = self.config['conddeps']
macro_prefix = self.config['macro_prefix']
emit = ''
if not conddeps:
# Ignore the conditions, and enable all modules unconditionally.
for module in modules:
if module_filter(module):
emit += self.autoconfSnippet(module, toplevel,
disable_libtool, disable_gettext, replace_auxdir, ' ')
else: # if conddeps
# Emit the autoconf code for the unconditional modules.
for module in modules:
if module_filter(module):
if not moduletable.isConditional(module):
emit += self.autoconfSnippet(module, toplevel,
disable_libtool, disable_gettext, replace_auxdir, ' ')
# Initialize the shell variables indicating that the modules are enabled.
for module in modules:
if module_filter(module):
if moduletable.isConditional(module):
emit += ' %s=false\n' % module.getShellVar()
# Emit the autoconf code for the conditional modules, each in a separate
# function. This makes it possible to support cycles among conditional
# modules.
for module in modules:
if module_filter(module):
if moduletable.isConditional(module):
shellfunc = module.getShellFunc()
shellvar = module.getShellVar()
emit += ' %s ()\n' % shellfunc
emit += ' {\n'
emit += ' if $%s; then :; else\n' % shellvar
emit += self.autoconfSnippet(module, toplevel,
disable_libtool, disable_gettext, replace_auxdir, ' ')
emit += ' %s=true\n' % shellvar
depmodules = module.getDependenciesWithoutConditions()
# Intersect dependencies with the modules list.
depmodules = sorted(set(depmodules).intersection(referenceable_modules))
for depmodule in depmodules:
if moduletable.isConditional(depmodule):
shellfunc = depmodule.getShellFunc()
condition = moduletable.getCondition(module, depmodule)
if condition != None and condition != True:
emit += ' if %s; then\n' % condition
emit += ' %s\n' % shellfunc
emit += ' fi\n'
else: # if condition == None or condition == True
emit += ' %s\n' % shellfunc
# if not moduletable.isConditional(depmodule)
else:
# The autoconf code for $dep has already been emitted above and
# therefore is already executed when this code is run.
pass
emit += ' fi\n'
emit += ' }\n'
# Emit the dependencies from the unconditional to the conditional modules.
for module in modules:
if module_filter(module):
if not moduletable.isConditional(module):
depmodules = module.getDependenciesWithoutConditions()
# Intersect dependencies with the modules list.
depmodules = sorted(set(depmodules).intersection(referenceable_modules))
for depmodule in depmodules:
if moduletable.isConditional(depmodule):
shellfunc = depmodule.getShellFunc()
condition = moduletable.getCondition(module, depmodule)
if condition != None and condition != True:
emit += ' if %s; then\n' % condition
emit += ' %s\n' % shellfunc
emit += ' fi\n'
else: # if condition == None or condition == True
emit += ' %s\n' % shellfunc
# if not moduletable.isConditional(depmodule)
else:
# The autoconf code for $dep has already been emitted above and
# therefore is already executed when this code is run.
pass
# Define the Automake conditionals.
emit += ' m4_pattern_allow([^%s_GNULIB_ENABLED_])\n' % macro_prefix
for module in modules:
if module_filter(module):
if moduletable.isConditional(module):
condname = module.getConditionalName()
shellvar = module.getShellVar()
emit += ' AM_CONDITIONAL([%s], [$%s])\n' % (condname, shellvar)
lines = [ line
for line in emit.split('\n')
if line.strip() ]
emit = lines_to_multiline(lines)
return emit
def preEarlyMacros(self, require: bool, indentation: str, modules: list[GLModule]) -> str:
'''Collect and emit the pre-early section.
require parameter can be True (AC_REQUIRE) or False (direct call).
indentation parameter is a string.
modules argument represents list of modules; every module in this list must
be a GLModule instance.'''
emit = '\n' + indentation + '# Pre-early section.\n'
# We need to call gl_USE_SYSTEM_EXTENSIONS before gl_PROG_AR_RANLIB.
# Doing AC_REQUIRE in configure-ac.early is not early enough.
if any(module.name == 'extensions' for module in modules):
if require:
emit += indentation + 'AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])\n'
else:
emit += indentation + 'gl_USE_SYSTEM_EXTENSIONS\n'
if require:
emit += indentation + 'AC_REQUIRE([gl_PROG_AR_RANLIB])\n'
else:
emit += indentation + 'gl_PROG_AR_RANLIB\n'
emit += '\n'
return emit
def po_Makevars(self) -> str:
'''Emit the contents of po/ makefile parameterization.
GLConfig: pobase, podomain.'''
pobase = self.config['pobase']
podomain = self.config['podomain']
emit = ''
emit += '## DO NOT EDIT! GENERATED AUTOMATICALLY!\n'
emit += '%s\n' % self.copyright_notice()
emit += '# Usually the message domain is the same as the package name.\n'
emit += "# But here it has a '-gnulib' suffix.\n"
emit += 'DOMAIN = %s-gnulib\n\n' % podomain
emit += '# These two variables depend on the location of this directory.\n'
emit += 'subdir = %s\n' % pobase
emit += 'top_builddir = %s\n' % relinverse(pobase)
emit += '''
# These options get passed to xgettext.
XGETTEXT_OPTIONS = \\
--keyword=_ --flag=_:1:pass-c-format \\
--keyword=N_ --flag=N_:1:pass-c-format \\
--keyword='proper_name:1,"This is a proper name. See the gettext manual, section Names."' \\
--keyword='proper_name_lite:1,"This is a proper name. See the gettext manual, section Names."' \\
--keyword='proper_name_utf8:1,"This is a proper name. See the gettext manual, section Names."' \\
--flag=error:3:c-format --flag=error_at_line:5:c-format
# This is the copyright holder that gets inserted into the header of the
# $(DOMAIN).pot file. gnulib is copyrighted by the FSF.
COPYRIGHT_HOLDER = Free Software Foundation, Inc.
# This is the email address or URL to which the translators shall report
# bugs in the untranslated strings:
# - Strings which are not entire sentences, see the maintainer guidelines
# in the GNU gettext documentation, section 'Preparing Strings'.
# - Strings which use unclear terms or require additional context to be
# understood.
# - Strings which make invalid assumptions about notation of date, time or
# money.
# - Pluralisation problems.
# - Incorrect English spelling.
# - Incorrect formatting.
# It can be your email address, or a mailing list address where translators
# can write to without being subscribed, or the URL of a web page through
# which the translators can contact you.
MSGID_BUGS_ADDRESS = [email protected]
# This is the list of locale categories, beyond LC_MESSAGES, for which the
# message catalogs shall be used. It is usually empty.
EXTRA_LOCALE_CATEGORIES =
# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'
# context. Possible values are "yes" and "no". Set this to yes if the
# package uses functions taking also a message context, like pgettext(), or
# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.
USE_MSGCTXT = no\n'''
return emit
def po_POTFILES_in(self, files: list[str]) -> str:
'''Emit the file list to be passed to xgettext.
GLConfig: sourcebase.'''
sourcebase = self.config['sourcebase'] + os.path.sep
emit = ''
emit += '## DO NOT EDIT! GENERATED AUTOMATICALLY!\n'
emit += '%s\n' % self.copyright_notice()
emit += '# List of files which contain translatable strings.\n'
for file in files:
if file.startswith('lib/'):
emit += '%s\n' % substart('lib/', sourcebase, file)
return emit
def initmacro_start(self, macro_prefix_arg: str, gentests: bool) -> str:
'''Emit the first few statements of the gl_INIT macro.
macro_prefix_arg is the prefix of gl_EARLY and gl_INIT macros to use.
gentests is True if a tests Makefile.am is being generated, False
otherwise.'''
if type(macro_prefix_arg) is not str:
raise TypeError('macro_prefix_arg must be a string, not %s'
% type(macro_prefix_arg).__name__)
if type(gentests) is not bool:
raise TypeError('gentests must be a bool, not %s'
% type(gentests).__name__)
module_indicator_prefix = self.config.getModuleIndicatorPrefix()
emit = ''
# Overriding AC_LIBOBJ and AC_REPLACE_FUNCS has the effect of storing
# platform-dependent object files in ${macro_prefix_arg}_LIBOBJS instead
# of LIBOBJS. The purpose is to allow several gnulib instantiations under
# a single configure.ac file. (AC_CONFIG_LIBOBJ_DIR does not allow this
# flexibility).
# Furthermore it avoids an automake error like this when a Makefile.am
# that uses pieces of gnulib also uses $(LIBOBJ):
# automatically discovered file `error.c' should not be explicitly mentioned.
emit += ' m4_pushdef([AC_LIBOBJ], m4_defn([%s_LIBOBJ]))\n' % macro_prefix_arg
emit += ' m4_pushdef([AC_REPLACE_FUNCS], m4_defn([%s_REPLACE_FUNCS]))\n' % macro_prefix_arg
# Overriding AC_LIBSOURCES has the same purpose of avoiding the automake
# error when a Makefile.am that uses pieces of gnulib also uses $(LIBOBJ):
# automatically discovered file `error.c' should not be explicitly mentioned
# We let automake know about the files to be distributed through the
# EXTRA_lib_SOURCES variable.
emit += ' m4_pushdef([AC_LIBSOURCES], m4_defn([%s_LIBSOURCES]))\n' % macro_prefix_arg
# Create data variables for checking the presence of files that are
# mentioned as AC_LIBSOURCES arguments. These are m4 variables, not shell
# variables, because we want the check to happen when the configure file is
# created, not when it is run. ${macro_prefix_arg}_LIBSOURCES_LIST is the
# list of files to check for. ${macro_prefix_arg}_LIBSOURCES_DIR is the
# subdirectory in which to expect them.
emit += ' m4_pushdef([%s_LIBSOURCES_LIST], [])\n' % macro_prefix_arg
emit += ' m4_pushdef([%s_LIBSOURCES_DIR], [])\n' % macro_prefix_arg
# Scope for m4 macros.
emit += ' m4_pushdef([GL_MACRO_PREFIX], [%s])\n' % macro_prefix_arg
# Scope the GNULIB_<modulename> variables.
emit += ' m4_pushdef([GL_MODULE_INDICATOR_PREFIX], [%s])\n' % module_indicator_prefix
emit += ' gl_COMMON\n'
if gentests:
emit += ' AC_REQUIRE([gl_CC_ALLOW_WARNINGS])\n'
emit += ' AC_REQUIRE([gl_CXX_ALLOW_WARNINGS])\n'
return emit
def initmacro_end(self, macro_prefix_arg: str, gentests: bool) -> str:
'''Emit the last few statements of the gl_INIT macro.
macro_prefix_arg is the prefix of gl_EARLY, gl_INIT macros to use.
gentests is a bool that is True if a tests Makefile.am is being
generated, False otherwise.'''
if type(macro_prefix_arg) is not str:
raise TypeError('macro_prefix_arg must be a string, not %s'
% type(macro_prefix_arg).__name__)
if type(gentests) is not bool:
raise TypeError('gentests must be a bool, not %s'
% type(gentests).__name__)
sourcebase = self.config['sourcebase']
testsbase = self.config['testsbase']
automake_subdir = self.config['automake_subdir']
automake_subdir_tests = self.config['automake_subdir_tests']
libtool = self.config['libtool']
emit = ''
# Check the presence of files that are mentioned as AC_LIBSOURCES
# arguments. The check is performed only when autoconf is run from the
# directory where the configure.ac resides; if it is run from a different
# directory, the check is skipped.
if automake_subdir and not gentests and sourcebase != '' and sourcebase != '.':
subdir = f'{sourcebase}/'
elif automake_subdir_tests and gentests and testsbase != '' and testsbase != '.':
subdir = f'{testsbase}/'
else:
subdir = ''
if libtool:
libobjdeps = f'{macro_prefix_arg}_libobjdeps="${macro_prefix_arg}_libobjdeps {subdir}$i_dir/\\$(DEPDIR)/$i_base.Plo"'
else:
libobjdeps = f'{macro_prefix_arg}_libobjdeps="${macro_prefix_arg}_libobjdeps {subdir}$i_dir/\\$(DEPDIR)/$i_base.Po"'
emit += fr''' m4_ifval({macro_prefix_arg}_LIBSOURCES_LIST, [
m4_syscmd([test ! -d ]m4_defn([{macro_prefix_arg}_LIBSOURCES_DIR])[ ||
for gl_file in ]{macro_prefix_arg}_LIBSOURCES_LIST[ ; do
if test ! -r ]m4_defn([{macro_prefix_arg}_LIBSOURCES_DIR])[/$gl_file ; then
echo "missing file ]m4_defn([{macro_prefix_arg}_LIBSOURCES_DIR])[/$gl_file" >&2
exit 1
fi
done])dnl
m4_if(m4_sysval, [0], [],
[AC_FATAL([expected source file, required through AC_LIBSOURCES, not found])])
])
m4_popdef([GL_MODULE_INDICATOR_PREFIX])
m4_popdef([GL_MACRO_PREFIX])
m4_popdef([{macro_prefix_arg}_LIBSOURCES_DIR])
m4_popdef([{macro_prefix_arg}_LIBSOURCES_LIST])
m4_popdef([AC_LIBSOURCES])
m4_popdef([AC_REPLACE_FUNCS])
m4_popdef([AC_LIBOBJ])
AC_CONFIG_COMMANDS_PRE([
{macro_prefix_arg}_libobjs=
{macro_prefix_arg}_ltlibobjs=
{macro_prefix_arg}_libobjdeps=
if test -n "${macro_prefix_arg}_LIBOBJS"; then
# Remove the extension.
changequote(,)dnl
sed_drop_objext='s/\.o$//;s/\.obj$//'
sed_dirname1='s,//*,/,g'
sed_dirname2='s,\(.\)/$,\1,'
sed_dirname3='s,^[^/]*$,.,'
sed_dirname4='s,\(.\)/[^/]*$,\1,'
sed_basename1='s,.*/,,'
changequote([, ])dnl
for i in `for i in ${macro_prefix_arg}_LIBOBJS; do echo "$i"; done | sed -e "$sed_drop_objext" | sort | uniq`; do
{macro_prefix_arg}_libobjs="${macro_prefix_arg}_libobjs {subdir}$i.$ac_objext"
{macro_prefix_arg}_ltlibobjs="${macro_prefix_arg}_ltlibobjs {subdir}$i.lo"
i_dir=`echo "$i" | sed -e "$sed_dirname1" -e "$sed_dirname2" -e "$sed_dirname3" -e "$sed_dirname4"`
i_base=`echo "$i" | sed -e "$sed_basename1"`
{libobjdeps}
done
fi
AC_SUBST([{macro_prefix_arg}_LIBOBJS], [${macro_prefix_arg}_libobjs])
AC_SUBST([{macro_prefix_arg}_LTLIBOBJS], [${macro_prefix_arg}_ltlibobjs])
AC_SUBST([{macro_prefix_arg}_LIBOBJDEPS], [${macro_prefix_arg}_libobjdeps])
])
'''
return emit
def initmacro_done(self, macro_prefix_arg: str, sourcebase_arg: str) -> str:
'''Emit a few statements after the gl_INIT macro.
GLConfig: sourcebase.'''
if type(macro_prefix_arg) is not str:
raise TypeError('macro_prefix_arg must be a string, not %s'
% type(macro_prefix_arg).__name__)
if type(sourcebase_arg) is not str:
raise TypeError('sourcebase_arg must be a string, not %s'
% type(sourcebase_arg).__name__)
emit = ''
emit += '''\
# Like AC_LIBOBJ, except that the module name goes
# into %V1%_LIBOBJS instead of into LIBOBJS.
AC_DEFUN([%V1%_LIBOBJ], [
AS_LITERAL_IF([$1], [%V1%_LIBSOURCES([$1.c])])dnl
%V1%_LIBOBJS="$%V1%_LIBOBJS $1.$ac_objext"
])
# Like AC_REPLACE_FUNCS, except that the module name goes
# into %V1%_LIBOBJS instead of into LIBOBJS.
AC_DEFUN([%V1%_REPLACE_FUNCS], [
m4_foreach_w([gl_NAME], [$1], [AC_LIBSOURCES(gl_NAME[.c])])dnl
AC_CHECK_FUNCS([$1], , [%V1%_LIBOBJ($ac_func)])
])
# Like AC_LIBSOURCES, except the directory where the source file is
# expected is derived from the gnulib-tool parameterization,
# and alloca is special cased (for the alloca-opt module).
# We could also entirely rely on EXTRA_lib..._SOURCES.
AC_DEFUN([%V1%_LIBSOURCES], [
m4_foreach([_gl_NAME], [$1], [
m4_if(_gl_NAME, [alloca.c], [], [
m4_define([%V1%_LIBSOURCES_DIR], [%V2%])
m4_append([%V1%_LIBSOURCES_LIST], _gl_NAME, [ ])
])
])
])\n'''
emit = emit.replace('%V1%', macro_prefix_arg)
emit = emit.replace('%V2%', sourcebase_arg)
return emit
def lib_Makefile_am(self, destfile: str, modules: list[GLModule], moduletable: GLModuleTable,
makefiletable: GLMakefileTable, actioncmd: str, for_test: bool) -> str:
'''Emit the contents of the library Makefile. Returns it as a string.
GLConfig: localpath, sourcebase, libname, pobase, auxdir, makefile_name, libtool,
macro_prefix, podomain, conddeps, witness_c_macro.
destfile is a filename relative to destdir of Makefile being generated.
modules is a list of GLModule instances.
moduletable is a GLModuleTable instance.
makefiletable is a GLMakefileTable instance.
actioncmd is a string variable, which represents the actioncmd; it can be
an empty string e.g. when user wants to generate files for GLTestDir.
for_test is a bool variable; it must be set to True if creating a package
for testing, False otherwise.'''
if type(destfile) is not str:
raise TypeError('destfile must be a string, not %s'
% type(destfile).__name__)
for module in modules:
if type(module) is not GLModule:
raise TypeError('each module must be a GLModule instance')
if type(moduletable) is not GLModuleTable:
raise TypeError('moduletable must be a GLModuleTable, not %s'
% type(moduletable).__name__)
if type(makefiletable) is not GLMakefileTable:
raise TypeError('makefiletable must be a GLMakefileTable, not %s'
% type(makefiletable).__name__)
if type(actioncmd) is not str:
raise TypeError('actioncmd must be a string, not %s'
% type(actioncmd).__name__)
if type(for_test) is not bool:
raise TypeError('for_test must be a bool, not %s'
% type(for_test).__name__)
sourcebase = self.config['sourcebase']
libname = self.config['libname']
pobase = self.config['pobase']
auxdir = self.config['auxdir']
gnu_make = self.config['gnu_make']
makefile_name = self.config['makefile_name']
automake_subdir = self.config['automake_subdir']
libtool = self.config['libtool']
macro_prefix = self.config['macro_prefix']
podomain = self.config['podomain']
conddeps = self.config['conddeps']
witness_c_macro = self.config['witness_c_macro']
include_guard_prefix = self.config['include_guard_prefix']
module_indicator_prefix = self.config.getModuleIndicatorPrefix()
destfile = os.path.normpath(destfile)
emit = ''
# When using GNU make, or when creating an includable Makefile.am snippet,
# augment variables with += instead of assigning them.
if gnu_make or makefile_name:
assign = '+='
else: # if not makefile_name
assign = '='
if libtool:
libext = 'la'
perhapsLT = 'LT'
eliminate_LDFLAGS = False
else: # if not libtool
libext = 'a'
perhapsLT = ''
eliminate_LDFLAGS = True
if for_test:
# When creating a package for testing: Attempt to provoke failures,
# especially link errors, already during "make" rather than during
# "make check", because "make check" is not possible in a cross-compiling
# situation. Turn check_PROGRAMS into noinst_PROGRAMS.
edit_check_PROGRAMS = True
else: # if not for_test
edit_check_PROGRAMS = False
emit += '## DO NOT EDIT! GENERATED AUTOMATICALLY!\n'
if not gnu_make:
emit += '## Process this file with automake to produce Makefile.in.\n'
emit += self.copyright_notice()
if actioncmd:
emit += '# Reproduce by:\n%s\n' % actioncmd
emit += '\n'
uses_subdirs = False
# Compute allsnippets variable.
allsnippets = ''
for module in modules:
if module.isNonTests():
# Get conditional snippet, edit it and save to amsnippet1.
amsnippet1 = module.getAutomakeSnippet_Conditional()
amsnippet1 = amsnippet1.replace('lib_LIBRARIES', 'lib%_LIBRARIES')
amsnippet1 = amsnippet1.replace('lib_LTLIBRARIES', 'lib%_LTLIBRARIES')
if eliminate_LDFLAGS:
pattern = re.compile(r'^(lib_LDFLAGS[\t ]*\+=.*$\n)', re.M)
amsnippet1 = pattern.sub(r'', amsnippet1)
# Replace NMD, so as to remove redundant "$(MKDIR_P) '.'" invocations.
# The logic is similar to how we define gl_source_base_prefix.
amsnippet1 = _eliminate_NMD(amsnippet1, automake_subdir)
pattern = re.compile(r'lib_([A-Z]+)', re.M)
amsnippet1 = pattern.sub(r'%s_%s_\1' % (libname, libext),
amsnippet1)
amsnippet1 = amsnippet1.replace('$(GNULIB_', '$(' + module_indicator_prefix + '_GNULIB_')
amsnippet1 = amsnippet1.replace('lib%_LIBRARIES', 'lib_LIBRARIES')
amsnippet1 = amsnippet1.replace('lib%_LTLIBRARIES', 'lib_LTLIBRARIES')
if edit_check_PROGRAMS:
amsnippet1 = amsnippet1.replace('check_PROGRAMS', 'noinst_PROGRAMS')
amsnippet1 = amsnippet1.replace('${gl_include_guard_prefix}',
include_guard_prefix)
if module.name == 'alloca':
amsnippet1 += '%s_%s_LIBADD += @%sALLOCA@\n' % (libname, libext, perhapsLT)
amsnippet1 += '%s_%s_DEPENDENCIES += @%sALLOCA@\n' % (libname, libext, perhapsLT)
amsnippet1 = combine_lines_matching(re.compile(r'%s_%s_SOURCES' % (libname, libext)),
amsnippet1)
# Get unconditional snippet, edit it and save to amsnippet2.
amsnippet2 = module.getAutomakeSnippet_Unconditional()
pattern = re.compile(r'lib_([A-Z]+)', re.M)
amsnippet2 = pattern.sub(r'%s_%s_\1' % (libname, libext),
amsnippet2)
amsnippet2 = amsnippet2.replace('$(GNULIB_',
'$(' + module_indicator_prefix + '_GNULIB_')
# Skip the contents if it's entirely empty.
if (amsnippet1 + amsnippet2).strip() != '':
allsnippets += '## begin gnulib module %s\n' % module.name
if gnu_make:
allsnippets += 'ifeq (,$(OMIT_GNULIB_MODULE_%s))\n' % module.name
allsnippets += '\n'
if conddeps:
if moduletable.isConditional(module):
name = module.getConditionalName()
if gnu_make:
allsnippets += 'ifneq (,$(%s_CONDITION))\n' % name
else:
allsnippets += 'if %s\n' % name
if gnu_make:
allsnippets += _convert_to_gnu_make(amsnippet1)
else:
allsnippets += amsnippet1
if conddeps:
if moduletable.isConditional(module):
allsnippets += 'endif\n'
if gnu_make:
allsnippets += _convert_to_gnu_make(amsnippet2)
else:
allsnippets += amsnippet2
if gnu_make:
allsnippets += 'endif\n'
allsnippets += '## end gnulib module %s\n\n' % module.name
# Test whether there are some source files in subdirectories.
for file in module.getFiles():
if (file.startswith('lib/')
and file.endswith('.c')
and file.count('/') > 1):
uses_subdirs = True
break
if not makefile_name:
subdir_options = ''
# If there are source files in subdirectories, prevent collision of the
# object files (example: hash.c and libxml/hash.c).
if uses_subdirs:
subdir_options = ' subdir-objects'
emit += 'AUTOMAKE_OPTIONS = 1.14 gnits%s\n' % subdir_options
emit += '\n'
if not makefile_name:
emit += 'SUBDIRS =\n'
emit += 'noinst_HEADERS =\n'
emit += 'noinst_LIBRARIES =\n'
emit += 'noinst_LTLIBRARIES =\n'
emit += 'pkgdata_DATA =\n'
emit += 'EXTRA_DIST =\n'
emit += 'BUILT_SOURCES =\n'
emit += 'SUFFIXES =\n'
emit += 'MOSTLYCLEANFILES %s core *.stackdump\n' % assign
if not makefile_name:
emit += 'MOSTLYCLEANDIRS =\n'
emit += 'CLEANFILES =\n'
emit += 'DISTCLEANFILES =\n'
emit += 'MAINTAINERCLEANFILES =\n'
if gnu_make:
emit += '# Start of GNU Make output.\n'
result = sp.run([UTILS['autoconf'], '-t', 'AC_SUBST:$1 = @$1@',
joinpath(self.config['destdir'], 'configure.ac')],
capture_output=True)
if result.returncode == 0:
# sort -u
emit += lines_to_multiline(sorted({ x.strip()
for x in result.stdout.decode(encoding='utf-8').splitlines() }))
else:
emit += '== gnulib-tool GNU Make output failed as follows ==\n'
emit += ['# stderr: ' + x + '\n' for x in
result.stderr.decode(encoding='utf-8').splitlines()]
emit += '# End of GNU Make output.\n'
else:
emit += '# No GNU Make output.\n'
# Execute edits that apply to the Makefile.am being generated.
for current_edit in range(0, makefiletable.count()):
dictionary = makefiletable[current_edit]
if 'var' in dictionary:
if destfile == joinpath(dictionary['dir'], 'Makefile.am'):
val = dictionary['val']
if dictionary['var'] == 'SUBDIRS' and dictionary['dotfirst']:
# The added subdirectory ${val} needs to be mentioned after '.'.
# Since we don't have '.' among SUBDIRS so far, add it now.
val = f'. {val}'
emit += '%s += %s\n' % (dictionary['var'], val)
del dictionary['var']
# Define two parts of cppflags variable.
cppflags_part1 = ''
if witness_c_macro:
cppflags_part1 = ' -D%s=1' % witness_c_macro
cppflags_part2 = ''
if for_test:
cppflags_part2 = ' -DGNULIB_STRICT_CHECKING=1'
cppflags = '%s%s' % (cppflags_part1, cppflags_part2)
if not makefile_name:
emit += '\n'
emit += 'AM_CPPFLAGS =%s\n' % cppflags
emit += 'AM_CFLAGS =\n'
else: # if makefile_name
if cppflags:
emit += '\n'
emit += 'AM_CPPFLAGS +=%s\n' % cppflags
emit += '\n'
# Test if one of the snippets or the user's Makefile.am already specifies an
# installation location for the library. Don't confuse automake by saying
# it should not be installed.
# First test if allsnippets already specify an installation location.
lib_gets_installed = False
regex = r'^[a-zA-Z0-9_]*_%sLIBRARIES *\+{0,1}= *%s\.%s' % (perhapsLT, libname, libext)
pattern = re.compile(regex, re.M)
if pattern.findall(allsnippets):
lib_gets_installed = True
else:
# Then test if $sourcebase/Makefile.am (if it exists) specifies it.
if makefile_name:
path = joinpath(sourcebase, 'Makefile.am')
if os.path.isfile(path):
with open(path, mode='r', newline='\n', encoding='utf-8') as file:
data = file.read()
if pattern.findall(data):
lib_gets_installed = True
if not lib_gets_installed:
# By default, the generated library should not be installed.
emit += 'noinst_%sLIBRARIES += %s.%s\n' % (perhapsLT, libname, libext)
emit += '\n'
emit += '%s_%s_SOURCES =\n' % (libname, libext)
if not for_test:
emit += '%s_%s_CFLAGS = $(AM_CFLAGS) $(GL_CFLAG_GNULIB_WARNINGS)\n' % (libname, libext)
# Here we use $(LIBOBJS), not @LIBOBJS@. The value is the same. However,
# automake during its analysis looks for $(LIBOBJS), not for @LIBOBJS@.
emit += '%s_%s_LIBADD = $(%s_%sLIBOBJS)\n' % (libname, libext, macro_prefix, perhapsLT)
emit += '%s_%s_DEPENDENCIES = $(%s_%sLIBOBJS)\n' % (libname, libext, macro_prefix, perhapsLT)
emit += 'EXTRA_%s_%s_SOURCES =\n' % (libname, libext)
if libtool:
emit += '%s_%s_LDFLAGS = $(AM_LDFLAGS)\n' % (libname, libext)
emit += '%s_%s_LDFLAGS += -no-undefined\n' % (libname, libext)
# Synthesize an ${libname}_${libext}_LDFLAGS augmentation by combining
# the link dependencies of all modules.
links = [ module.getLink()
for module in modules
if module.isNonTests() ]
lines = [ line
for link in links
for line in link.split('\n')
if line != '' ]
pattern = re.compile(r' when linking with libtool.*')
lines = [ pattern.sub(r'', line)
for line in lines ]
lines = sorted(set(lines))
for line in lines:
emit += '%s_%s_LDFLAGS += %s\n' % (libname, libext, line)
emit += '\n'
if pobase:
emit += 'AM_CPPFLAGS += -DDEFAULT_TEXT_DOMAIN=\\"%s-gnulib\\"\n' % podomain
emit += '\n'
allsnippets = allsnippets.replace('$(top_srcdir)/build-aux/',
'$(top_srcdir)/%s/' % auxdir)
emit += allsnippets
emit += '\n'
emit += 'mostlyclean-local: mostlyclean-generic\n'
emit += '\t@for dir in \'\' $(MOSTLYCLEANDIRS); do \\\n'
emit += '\t if test -n "$$dir" && test -d $$dir; then \\\n'
emit += '\t echo "rmdir $$dir"; rmdir $$dir; \\\n'
emit += '\t fi; \\\n'
emit += '\tdone; \\\n'
emit += '\t:\n'
# Emit rules to erase .Po and .Plo files for AC_LIBOBJ invocations.
# Extend the 'distclean' rule.
emit += 'distclean-local: distclean-gnulib-libobjs\n'
emit += 'distclean-gnulib-libobjs:\n'
emit += '\t-rm -f @%s_LIBOBJDEPS@\n' % (macro_prefix)
# Extend the 'maintainer-clean' rule.
emit += 'maintainer-clean-local: distclean-gnulib-libobjs\n'
return emit
def tests_Makefile_am(self, destfile: str, modules: list[GLModule], moduletable: GLModuleTable,
makefiletable: GLMakefileTable, witness_macro: str, for_test: bool) -> str:
'''Emit the contents of the tests Makefile. Returns it as a string.
GLConfig: localpath, modules, libname, auxdir, makefile_name, libtool,
sourcebase, m4base, testsbase, macro_prefix, witness_c_macro,
single_configure, libtests.
destfile is a filename relative to destdir of Makefile being generated.
witness_macro is a string which represents witness_c_macro with the suffix.
modules is a list of GLModule instances.
moduletable is a GLModuleTable instance.
makefiletable is a GLMakefileTable instance.
actioncmd is a string variable, which represents the actioncmd; it can be
an empty string e.g. when user wants to generate files for GLTestDir.
for_test is a bool variable; it must be set to True if creating a package
for testing, False otherwise.'''
if type(destfile) is not str:
raise TypeError('destfile must be a string, not %s'
% type(destfile).__name__)
for module in modules:
if type(module) is not GLModule:
raise TypeError('each module must be a GLModule instance')
if type(makefiletable) is not GLMakefileTable:
raise TypeError('makefiletable must be a GLMakefileTable, not %s'
% type(makefiletable).__name__)
if type(witness_macro) is not str:
raise TypeError('witness_macro must be a string, not %s'
% type(witness_macro).__name__)
if type(for_test) is not bool:
raise TypeError('for_test must be a bool, not %s'
% type(for_test).__name__)
auxdir = self.config['auxdir']
sourcebase = self.config['sourcebase']
libname = self.config['libname']
m4base = self.config['m4base']
testsbase = self.config['testsbase']
gnu_make = self.config['gnu_make']
libtool = self.config['libtool']
macro_prefix = self.config['macro_prefix']
conddeps = self.config['conddeps']
witness_c_macro = self.config['witness_c_macro']
include_guard_prefix = self.config['include_guard_prefix']
module_indicator_prefix = self.config.getModuleIndicatorPrefix()
libtests = self.config['libtests']
single_configure = self.config['single_configure']
emit = ''
if libtool:
libext = 'la'
eliminate_LDFLAGS = False
else: # if not libtool
libext = 'a'
eliminate_LDFLAGS = True
if for_test:
# When creating a package for testing: Attempt to provoke failures,