您的位置:首页 > Web前端

第三章 编写Makefile

2009-12-08 20:45 204 查看
Chapter 3: Writing Make?les

3 Writing Make?les

11

The information that tells make how to recompile a system comes from reading a data base
called the make?le.

3.1 What Make?les Contain
Make?les contain ?ve kinds of things: explicit rules, implicit rules, variable de?nitions,
directives, and comments. Rules, variables, and directives are described at length in later
chapters.
• An explicit rule says when and how to remake one or more ?les, called the rule’s targets.
It lists the other ?les that the targets depend on, called the prerequisites of the target,
and may also give commands to use to create or update the targets. See Chapter 4
[Writing Rules], page 23.
• An implicit rule says when and how to remake a class of ?les based on their names. It
describes how a target may depend on a ?le with a name similar to the target and gives
commands to create or update such a target. See Chapter 10 [Using Implicit Rules],
page 101.
• A variable de?nition is a line that speci?es a text string value for a variable that
can be substituted into the text later. The simple make?le example shows a variable
de?nition for objects as a list of all object ?les (see Section 2.4 [Variables Make
Make?les Simpler], page 6).
• A directive is a command for make to do something special while reading the make?le.
These include:
• Reading another make?le (see Section 3.3 [Including Other Make?les], page 12).
• Deciding (based on the values of variables) whether to use or ignore a part of the
make?le (see Chapter 7 [Conditional Parts of Make?les], page 71).
• De?ning a variable from a verbatim string containing multiple lines (see Section 6.8
[De?ning Variables Verbatim], page 66).
• ‘#’ in a line of a make?le starts a comment. It and the rest of the line are ignored,
except that a trailing backslash not escaped by another backslash will continue the
comment across multiple lines. A line containing just a comment (with perhaps spaces
before it) is e?ectively blank, and is ignored. If you want a literal #, escape it with a
backslash (e.g., /#). Comments may appear on any line in the make?le, although they
are treated specially in certain situations.
Within a command script (if the line begins with a TAB character) the entire line
is passed to the shell, just as with any other line that begins with a TAB. The shell
decides how to interpret the text: whether or not this is a comme
24000
nt is up to the shell.
Within a define directive, comments are not ignored during the de?nition of the
variable, but rather kept intact in the value of the variable. When the variable is
expanded they will either be treated as make comments or as command script text,
depending on the context in which the variable is evaluated.

12

3.2 What Name to Give Your Make?le

GNU make

By default, when make looks for the make?le, it tries the following names, in order:
‘GNUmakefile’, ‘makefile’ and ‘Makefile’.
Normally you should call your make?le either ‘makefile’ or ‘Makefile’. (We recommend
‘Makefile’ because it appears prominently near the beginning of a directory listing, right
near other important ?les such as ‘README’.) The ?rst name checked, ‘GNUmakefile’, is not
recommended for most make?les. You should use this name if you have a make?le that is
speci?c to GNU make, and will not be understood by other versions of make. Other make
programs look for ‘makefile’ and ‘Makefile’, but not ‘GNUmakefile’.
If make ?nds none of these names, it does not use any make?le. Then you must specify
a goal with a command argument, and make will attempt to ?gure out how to remake it
using only its built-in implicit rules. See Chapter 10 [Using Implicit Rules], page 101.
If you want to use a nonstandard name for your make?le, you can specify the make?le
name with the ‘-f’ or ‘--file’ option. The arguments ‘-f name ’ or ‘--file=name ’ tell make
to read the ?le name as the make?le. If you use more than one ‘-f’ or ‘--file’ option,
you can specify several make?les. All the make?les are e?ectively concatenated in the order
speci?ed. The default make?le names ‘GNUmakefile’, ‘makefile’ and ‘Makefile’ are not
checked automatically if you specify ‘-f’ or ‘--file’.

3.3 Including Other Make?les
The include directive tells make to suspend reading the current make?le and read one or
more other make?les before continuing. The directive is a line in the make?le that looks
like this:
include filenames ...
?lenames can contain shell ?le name patterns. If ?lenames is empty, nothing is included
and no error is printed.
Extra spaces are allowed and ignored at the beginning of the line, but a tab is not
allowed. (If the line begins with a tab, it will be considered a command line.) Whitespace
is required between include and the ?le names, and between ?le names; extra whitespace
is ignored there and at the end of the directive. A comment starting with ‘#’ is allowed at
the end of the line. If the ?le names contain any variable or function references, they are
expanded. See Chapter 6 [How to Use Variables], page 57.
For example, if you have three ‘.mk’ ?les, ‘a.mk’, ‘b.mk’, and ‘c.mk’, and $(bar) expands
to bish bash, then the following expression
include foo *.mk $(bar)
is equivalent to
include foo a.mk b.mk c.mk bish bash
When make processes an include directive, it suspends reading of the containing make?le
and reads from each listed ?le in turn. When that is ?nished, make resumes reading the
make?le in which the directive appears.
One occasion for using include directives is when several programs, handled by indi-
vidual make?les in various directories, need to use a common set of variable de?nitions (see

Chapter 3: Writing Make?les

13

Section 6.5 [Setting Variables], page 63) or pattern rules (see Section 10.5 [De?ning and
Rede?ning Pattern Rules], page 108).
Another such occasion is when you want to generate prerequisites from source ?les
automatically; the prerequisites can be put in a ?le that is included by the main make?le.
This practice is generally cleaner than that of somehow appending the prerequisites to the
end of the main make?le as has been traditionally done with other versions of make. See
Section 4.13 [Automatic Prerequisites], page 40.
If the speci?ed name does not start with a slash, and the ?le is not found in the
current directory, several other directories are searched. First, any directories you have
speci?ed with the ‘-I’ or ‘--include-dir’ option are searched (see Section 9.7 [Sum-
mary of Options], page 96). Then the following directories (if they exist) are searched,
in this order: ‘prefix /include’ (normally ‘/usr/local/include’1) ‘/usr/gnu/include’,
‘/usr/local/include’, ‘/usr/include’.
If an included make?le cannot be found in any of these directories, a warning message
is generated, but it is not an immediately fatal error; processing of the make?le containing
the include continues. Once it has ?nished reading make?les, make will try to remake any
that are out of date or don’t exist. See Section 3.7 [How Make?les Are Remade], page 16.
Only after it has tried to ?nd a way to remake a make?le and failed, will make diagnose the
missing make?le as a fatal error.
If you want make to simply ignore a make?le which does not exist and cannot be remade,
with no error message, use the -include directive instead of include, like this:
-include filenames ...
This acts like include in every way except that there is no error (not even a warning) if
any of the ?lenames do not exist. For compatibility with some other make implementations,
sinclude is another name for -include.

3.4 The Variable MAKEFILES
If the environment variable MAKEFILES is de?ned, make considers its value as a list of
names (separated by whitespace) of additional make?les to be read before the others. This
works much like the include directive: various directories are searched for those ?les (see
Section 3.3 [Including Other Make?les], page 12). In addition, the default goal is never
taken from one of these make?les and it is not an error if the ?les listed in MAKEFILES are
not found.
The main use of MAKEFILES is in communication between recursive invocations of make
(see Section 5.7 [Recursive Use of make], page 50). It usually is not desirable to set the
environment variable before a top-level invocation of make, because it is usually better not
to mess with a make?le from outside. However, if you are running make without a speci?c
make?le, a make?le in MAKEFILES can do useful things to help the built-in implicit rules
work better, such as de?ning search paths (see Section 4.4 [Directory Search], page 26).
Some users are tempted to set MAKEFILES in the environment automatically on login,
and program make?les to expect this to be done. This is a very bad idea, because such
make?les will fail to work if run by anyone else. It is much better to write explicit include
directives in the make?les. See Section 3.3 [Including Other Make?les], page 12.
1 GNU Make compiled for MS-DOS and MS-Windows behaves as if pre?x has been de?ned to be the root
of the DJGPP tree hierarchy.

14

3.5 The Variable MAKEFILE_LIST

GNU make

As make reads various make?les, including any obtained from the MAKEFILES variable, the
command line, the default ?les, or from include directives, their names will be automat-
ically appended to the MAKEFILE_LIST variable. They are added right before make begins
to parse them.

This means that if the ?rst thing a make?le does is examine the last word in this variable,
it will be the name of the current make?le. Once the current make?le has used include,
however, the last word will be the just-included make?le.

If a make?le named Makefile has this content:

name1 := $(lastword $(MAKEFILE_LIST))

include inc.mk

name2 := $(lastword $(MAKEFILE_LIST))

all:
@echo name1 = $(name1)
@echo name2 = $(name2)

then you would expect to see this output:

name1 = Makefile
name2 = inc.mk

See Section 8.2 [Text Functions], page 78, for more information on the word and words
functions used above. See Section 6.2 [Flavors], page 58, for more information on simply-
expanded (:=) variable de?nitions.

3.6 Other Special Variables
GNU make also supports other special variables. Unless otherwise documented here, these
values lose their special properties if they are set by a make?le or on the command line.

.DEFAULT_GOAL
Sets the default goal to be used if no targets were speci?ed on the command line
(see Section 9.2 [Arguments to Specify the Goals], page 91). The .DEFAULT_
GOAL variable allows you to discover the current default goal, restart the default
goal selection algorithm by clearing its value, or to explicitly set the default goal.
The following example illustrates these cases:

Chapter 3: Writing Make?les

# Query the default goal.
ifeq ($(.DEFAULT_GOAL),)
$(warning no default goal is set)
endif

.PHONY: foo
foo: ; @echo $@

$(warning default goal is $(.DEFAULT_GOAL))

# Reset the default goal.
.DEFAULT_GOAL :=

.PHONY: bar
bar: ; @echo $@

$(warning default goal is $(.DEFAULT_GOAL))

# Set our own.
.DEFAULT_GOAL := foo
This make?le prints:
no default goal is set
default goal is foo
default goal is bar
foo

15

Note that assigning more than one target name to .DEFAULT_GOAL is illegal and
will result in an error.
MAKE_RESTARTS
This variable is set only if this instance of make has restarted (see Section 3.7
[How Make?les Are Remade], page 16): it will contain the number of times this
instance has restarted. Note this is not the same as recursion (counted by the
MAKELEVEL variable). You should not set, modify, or export this variable.
.VARIABLES
Expands to a list of the names of all global variables de?ned so far. This
includes variables which have empty values, as well as built-in variables (see
Section 10.3 [Variables Used by Implicit Rules], page 105), but does not include
any variables which are only de?ned in a target-speci?c context. Note that any
value you assign to this variable will be ignored; it will always return its special
value.
.FEATURES
Expands to a list of special features supported by this version of make. Possible
values include:
‘archives’
Supports ar (archive) ?les using special ?lename syntax. See Chap-
ter 11 [Usingmaketo Update Archive Files], page 119.

16

‘check-symlink’

GNU make

Supports the -L (--check-symlink-times) ?ag. See Section 9.7
[Summary of Options], page 96.
‘else-if’ Supports “else if” non-nested conditionals. See Section 7.2 [Syntax
of Conditionals], page 72.
‘jobserver’
Supports “job server” enhanced parallel builds. See Section 5.4
[Parallel Execution], page 47.
‘second-expansion’
Supports secondary expansion of prerequisite lists.
‘order-only’
Supports order-only prerequisites. See Section 4.2 [Types of Pre-
requisites], page 24.
‘target-specific’
Supports target-speci?c and pattern-speci?c variable assignments.
See Section 6.10 [Target-speci?c Variable Values], page 67.

.INCLUDE_DIRS
Expands to a list of directories that make searches for included make?les (see
Section 3.3 [Including Other Make?les], page 12).

3.7 How Make?les Are Remade
Sometimes make?les can be remade from other ?les, such as RCS or SCCS ?les. If a make?le
can be remade from other ?les, you probably want make to get an up-to-date version of the
make?le to read in.
To this end, after reading in all make?les, make will consider each as a goal target and
attempt to update it. If a make?le has a rule which says how to update it (found either
in that very make?le or in another one) or if an implicit rule applies to it (see Chapter 10
[Using Implicit Rules], page 101), it will be updated if necessary. After all make?les have
been checked, if any have actually been changed, make starts with a clean slate and reads
all the make?les over again. (It will also attempt to update each of them over again, but
normally this will not change them again, since they are already up to date.)
If you know that one or more of your make?les cannot be remade and you want to keep
make from performing an implicit rule search on them, perhaps for e?ciency reasons, you
can use any normal method of preventing implicit rule lookup to do so. For example, you
can write an explicit rule with the make?le as the target, and an empty command string
(see Section 5.9 [Using Empty Commands], page 56).
If the make?les specify a double-colon rule to remake a ?le with commands but no
prerequisites, that ?le will always be remade (see Section 4.12 [Double-Colon], page 39).
In the case of make?les, a make?le that has a double-colon rule with commands but no
prerequisites will be remade every time make is run, and then again after make starts over
and reads the make?les in again. This would cause an in?nite loop: make would constantly
remake the make?le, and never do anything else. So, to avoid this, make will not attempt

Chapter 3: Writing Make?les

17

to remake make?les which are speci?ed as targets of a double-colon rule with commands
but no prerequisites.
If you do not specify any make?les to be read with ‘-f’ or ‘--file’ options, make will try
the default make?le names; see Section 3.2 [What Name to Give Your Make?le], page 12.
Unlike make?les explicitly requested with ‘-f’ or ‘--file’ options, make is not certain that
these make?les should exist. However, if a default make?le does not exist but can be created
by running make rules, you probably want the rules to be run so that the make?le can be
used.
Therefore, if none of the default make?les exists, make will try to make each of them in
the same order in which they are searched for (see Section 3.2 [What Name to Give Your
Make?le], page 12) until it succeeds in making one, or it runs out of names to try. Note
that it is not an error if make cannot ?nd or make any make?le; a make?le is not always
necessary.
When you use the ‘-t’ or ‘--touch’ option (see Section 9.3 [Instead of Executing the
Commands], page 93), you would not want to use an out-of-date make?le to decide which
targets to touch. So the ‘-t’ option has no e?ect on updating make?les; they are really up-
dated even if ‘-t’ is speci?ed. Likewise, ‘-q’ (or ‘--question’) and ‘-n’ (or ‘--just-print’)
do not prevent updating of make?les, because an out-of-date make?le would result in the
wrong output for other targets. Thus, ‘make -f mfile -n foo’ will update ‘mfile’, read
it in, and then print the commands to update ‘foo’ and its prerequisites without running
them. The commands printed for ‘foo’ will be those speci?ed in the updated contents of
‘mfile’.
However, on occasion you might actually wish to prevent updating of even the make?les.
You can do this by specifying the make?les as goals in the command line as well as specifying
them as make?les. When the make?le name is speci?ed explicitly as a goal, the options ‘-t’
and so on do apply to them.
Thus, ‘make -f mfile -n mfile foo’ would read the make?le ‘mfile’, print the com-
mands needed to update it without actually running them, and then print the commands
needed to update ‘foo’ without running them. The commands for ‘foo’ will be those
speci?ed by the existing contents of ‘mfile’.

3.8 Overriding Part of Another Make?le
Sometimes it is useful to have a make?le that is mostly just like another make?le. You
can often use the ‘include’ directive to include one in the other, and add more targets or
variable de?nitions. However, if the two make?les give di?erent commands for the same
target, make will not let you just do this. But there is another way.
In the containing make?le (the one that wants to include the other), you can use a
match-anything pattern rule to say that to remake any target that cannot be made from
the information in the containing make?le, make should look in another make?le. See
Section 10.5 [Pattern Rules], page 108, for more information on pattern rules.
For example, if you have a make?le called ‘Makefile’ that says how to make the target
‘foo’ (and other targets), you can write a make?le called ‘GNUmakefile’ that contains:
foo:
frobnicate > foo

18

%: force
@$(MAKE) -f Makefile $@
force: ;

GNU make

If you say ‘make foo’, make will ?nd ‘GNUmakefile’, read it, and see that to make ‘foo’,
it needs to run the command ‘frobnicate > foo’. If you say ‘make bar’, make will ?nd no
way to make ‘bar’ in ‘GNUmakefile’, so it will use the commands from the pattern rule:
‘make -f Makefile bar’. If ‘Makefile’ provides a rule for updating ‘bar’, make will apply
the rule. And likewise for any other target that ‘GNUmakefile’ does not say how to make.
The way this works is that the pattern rule has a pattern of just ‘%’, so it matches any
target whatever. The rule speci?es a prerequisite ‘force’, to guarantee that the commands
will be run even if the target ?le already exists. We give ‘force’ target empty commands
to prevent make from searching for an implicit rule to build it—otherwise it would apply
the same match-anything rule to ‘force’ itself and create a prerequisite loop!

3.9 How make Reads a Make?le
GNU make does its work in two distinct phases. During the ?rst phase it reads all the make-
?les, included make?les, etc. and internalizes all the variables and their values, implicit and
explicit rules, and constructs a dependency graph of all the targets and their prerequisites.
During the second phase, make uses these internal structures to determine what targets will
need to be rebuilt and to invoke the rules necessary to do so.
It’s important to understand this two-phase approach because it has a direct impact
on how variable and function expansion happens; this is often a source of some confusion
when writing make?les. Here we will present a summary of the phases in which expansion
happens for di?erent constructs within the make?le. We say that expansion is immediate if
it happens during the ?rst phase: in this case make will expand any variables or functions
in that section of a construct as the make?le is parsed. We say that expansion is deferred if
expansion is not performed immediately. Expansion of deferred construct is not performed
until either the construct appears later in an immediate context, or until the second phase.
You may not be familiar with some of these constructs yet. You can reference this section
as you become familiar with them, in later chapters.

Variable Assignment
Variable de?nitions are parsed as follows:
immediate = deferred
immediate ?= deferred
immediate := immediate
immediate += deferred or immediate

define immediate
deferred
endef
For the append operator, ‘+=’, the right-hand side is considered immediate if the variable
was previously set as a simple variable (‘:=’), and deferred otherwise.

Chapter 3: Writing Make?les

Conditional Statements

19

All instances of conditional syntax are parsed immediately, in their entirety; this includes
the ifdef, ifeq, ifndef, and ifneq forms. Of course this means that automatic variables
cannot be used in conditional statements, as automatic variables are not set until the
command script for that rule is invoked. If you need to use automatic variables in a
conditional you must use shell conditional syntax, in your command script proper, for these
tests, not make conditionals.

Rule De?nition
A rule is always expanded the same way, regardless of the form:
immediate : immediate ; deferred
deferred
That is, the target and prerequisite sections are expanded immediately, and the com-
mands used to construct the target are always deferred. This general rule is true for explicit
rules, pattern rules, su?x rules, static pattern rules, and simple prerequisite de?nitions.

3.10 Secondary Expansion
In the previous section we learned that GNU make works in two distinct phases: a read-in
phase and a target-update phase (see Section 3.9 [Howmake Reads a Make?le], page 18).
GNU make also has the ability to enable a second expansion of the prerequisites (only) for
some or all targets de?ned in the make?le. In order for this second expansion to occur,
the special target .SECONDEXPANSION must be de?ned before the ?rst prerequisite list that
makes use of this feature.
If that special target is de?ned then in between the two phases mentioned above, right
at the end of the read-in phase, all the prerequisites of the targets de?ned after the special
target are expanded a second time. In most circumstances this secondary expansion will
have no e?ect, since all variable and function references will have been expanded during the
initial parsing of the make?les. In order to take advantage of the secondary expansion phase
of the parser, then, it’s necessary to escape the variable or function reference in the make?le.
In this case the ?rst expansion merely un-escapes the reference but doesn’t expand it, and
expansion is left to the secondary expansion phase. For example, consider this make?le:
.SECONDEXPANSION:
ONEVAR = onefile
TWOVAR = twofile
myfile: $(ONEVAR) $$(TWOVAR)
After the ?rst expansion phase the prerequisites list of the ‘myfile’ target will be
onefile and $(TWOVAR); the ?rst (unescaped) variable reference to ONEVAR is expanded,
while the second (escaped) variable reference is simply unescaped, without being recognized
as a variable reference. Now during the secondary expansion the ?rst word is expanded again
but since it contains no variable or function references it remains the static value ‘onefile’,
while the second word is now a normal reference to the variable TWOVAR, which is ex-
panded to the value ‘twofile’. The ?nal result is that there are two prerequisites, ‘onefile’
and ‘twofile’.

20

GNU make

Obviously, this is not a very interesting case since the same result could more easily have

been achieved simply by having both variables appear, unescaped, in the prerequisites list.
One di?erence becomes apparent if the variables are reset; consider this example:
.SECONDEXPANSION:
AVAR = top
onefile: $(AVAR)
twofile: $$(AVAR)
AVAR = bottom
Here the prerequisite of ‘onefile’ will be expanded immediately, and resolve to the
value ‘top’, while the prerequisite of ‘twofile’ will not be full expanded until the secondary
expansion and yield a value of ‘bottom’.
This is marginally more exciting, but the true power of this feature only becomes ap-
parent when you discover that secondary expansions always take place within the scope of
the automatic variables for that target. This means that you can use variables such as $@,
$*, etc. during the second expansion and they will have their expected values, just as in
the command script. All you have to do is defer the expansion by escaping the $. Also,
secondary expansion occurs for both explicit and implicit (pattern) rules. Knowing this,
the possible uses for this feature increase dramatically. For example:
.SECONDEXPANSION:
main_OBJS := main.o try.o test.o
lib_OBJS := lib.o api.o

main lib: $$($$@_OBJS)
Here, after the initial expansion the prerequisites of both the ‘main’ and ‘lib’ targets
will be $($@_OBJS). During the secondary expansion, the $@ variable is set to the name of
the target and so the expansion for the ‘main’ target will yield $(main_OBJS), or main.o
try.o test.o, while the secondary expansion for the ‘lib’ target will yield $(lib_OBJS),
or lib.o api.o.
You can also mix functions here, as long as they are properly escaped:
main_SRCS := main.c try.c test.c
lib_SRCS := lib.c api.c

.SECONDEXPANSION:
main lib: $$(patsubst %.c,%.o,$$($$@_SRCS))
This version allows users to specify source ?les rather than object ?les, but gives the
same resulting prerequisites list as the previous example.
Evaluation of automatic variables during the secondary expansion phase, especially of
the target name variable $$@, behaves similarly to evaluation within command scripts.
However, there are some subtle di?erences and “corner cases” which come into play for
the di?erent types of rule de?nitions that make understands. The subtleties of using the
di?erent automatic variables are described below.

Secondary Expansion of Explicit Rules
During the secondary expansion of explicit rules, $$@ and $$% evaluate, respectively, to the
?le name of the target and, when the target is an archive member, the target member name.

Chapter 3: Writing Make?les

21

The $$< variable evaluates to the ?rst prerequisite in the ?rst rule for this target. $$^ and
$$+ evaluate to the list of all prerequisites of rules that have already appeared for the same
target ($$+ with repetitions and $$^ without). The following example will help illustrate
these behaviors:
.SECONDEXPANSION:

foo: foo.1 bar.1 $$< $$^ $$+

foo: foo.2 bar.2 $$< $$^ $$+

foo: foo.3 bar.3 $$< $$^ $$+

# line #1

# line #2

# line #3

In the ?rst prerequisite list, all three variables ($$<, $$^, and $$+) expand to the empty
string. In the second, they will have values foo.1, foo.1 bar.1, and foo.1 bar.1 respec-
tively. In the third they will have values foo.1, foo.1 bar.1 foo.2 bar.2, and foo.1
bar.1 foo.2 bar.2 respectively.
Rules undergo secondary expansion in make?le order, except that the rule with the
command script is always evaluated last.
The variables $$? and $$* are not available and expand to the empty string.

Secondary Expansion of Static Pattern Rules
Rules for secondary expansion of static pattern rules are identical to those for explicit rules,
above, with one exception: for static pattern rules the $$* variable is set to the pattern
stem. As with explicit rules, $$? is not available and expands to the empty string.

Secondary Expansion of Implicit Rules
As make searches for an implicit rule, it substitutes the stem and then performs secondary
expansion for every rule with a matching target pattern. The value of the automatic vari-
ables is derived in the same fashion as for static pattern rules. As an example:
.SECONDEXPANSION:

foo: bar

foo foz: fo%: bo%

%oo: $$< $$^ $$+ $$*
When the implicit rule is tried for target ‘foo’, $$< expands to ‘bar’, $$^ expands to
‘bar boo’, $$+ also expands to ‘bar boo’, and $$* expands to ‘f’.
Note that the directory pre?x (D), as described in Section 10.8 [Implicit Rule Search
Algorithm], page 116, is appended (after expansion) to all the patterns in the prerequisites
list. As an example:
.SECONDEXPANSION:

/tmp/foo.o:

%.o: $$(addsuffix /%.c,foo bar) foo.h

22

GNU make

The prerequisite list after the secondary expansion and directory pre?x reconstruction

will be ‘/tmp/foo/foo.c /tmp/var/bar/foo.c foo.h’. If you are not interested in this
reconstruction, you can use $$* instead of % in the prerequisites list.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息