]>
Commit | Line | Data |
---|---|---|
1 | ################################################################################ | |
2 | # Non-recursive make build system # | |
3 | # ------------------------------- # | |
4 | # Copyright (C) 2012 Andrzej Ostruszka <andrzej.ostruszka@gmail.com> # | |
5 | # # | |
6 | # URL: http://github.com/aostruszka/nonrec-make # | |
7 | # (or older: http://nonrec-make.googlecode.com/) # | |
8 | # # | |
9 | # Permission is hereby granted, free of charge, to any person obtaining a copy # | |
10 | # of this software and associated documentation files (the "Software"), to # | |
11 | # deal in the Software without restriction, including without limitation the # | |
12 | # rights to use, copy, modify, merge, publish, distribute, sublicense, # | |
13 | # and/or sell copies of the Software, and to permit persons to whom the # | |
14 | # Software is furnished to do so, subject to the following conditions: # | |
15 | # # | |
16 | # The above copyright notice and this permission notice shall be included in # | |
17 | # all copies or substantial portions of the Software. # | |
18 | # # | |
19 | # Except as contained in this notice, the name(s) of the above copyright # | |
20 | # holders shall not be used in advertising or otherwise to promote the sale, # | |
21 | # use or other dealings in this Software without prior written authorization. # | |
22 | # # | |
23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # | |
24 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # | |
25 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # | |
26 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # | |
27 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # | |
28 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # | |
29 | # IN THE SOFTWARE. # | |
30 | ################################################################################ | |
31 | ||
32 | NOTE: This readme _might_ not be up to date. For up to date information | |
33 | see the above URL (and accompanying wiki pages). | |
34 | ||
35 | This is my attempt to implement a non-recursive make build system. For | |
36 | the motivation Google for the paper "Recursive make consider harmful" by | |
37 | Peter Miller. | |
38 | ||
39 | I've seen couple of other proposals and decided to have something that | |
40 | will be a blend of nice ideas I have seen plus some improvements. If | |
41 | you actually use this I'd like to hear from you :) - is it useful, does | |
42 | it perform well, do you have any suggestions for the improvements ... | |
43 | and so on. This implementation is based on GNU make and its new | |
44 | features introduced in 3.80. But don't use that version - these | |
45 | features had bugs in that version. Use version 3.81 where everything | |
46 | works OK. | |
47 | ||
48 | Before you keep on reading though, just take a look at the structure of | |
49 | the Rules.mk files (Rules.top has exactly the same structure as Rules.mk | |
50 | - it just has another name to ease location of the top level project | |
51 | directory from its subfolders). | |
52 | I've got a feeling that it is much easier to understand how the system | |
53 | looks from the user perspective just by looking at the example than | |
54 | reading its explanation :D. | |
55 | ||
56 | OK, now that you have a feeling how the Rules.mk look like let me walk | |
57 | you through an example (ex1 in the repository). Consider the project | |
58 | that has some source files at the top directory and is depending on two | |
59 | libraries in Dir_1 and Dir_2 and another one in Dir_3. The libraries | |
60 | themselves are partitioned between several subdirectories and Dir_2 has | |
61 | some examples in a separate subfolder (do not pay attention to all *.c | |
62 | files). | |
63 | ||
64 | ex1/ | |
65 | Makefile | |
66 | Rules.top <- Just a symlink to Rules.mk to mark where the top level is | |
67 | Rules.mk | |
68 | main.c | |
69 | top_a.c | |
70 | top_b.c | |
71 | cli.c | |
72 | cli_dep.c | |
73 | mk/* <- This is where the mk files from this build system are | |
74 | Dir_1/ | |
75 | Makefile | |
76 | Rules.mk | |
77 | dir_1_file1.c | |
78 | dir_1_file2.c | |
79 | dir_1_file3.c | |
80 | Dir_1a/ | |
81 | Makefile | |
82 | Rules.mk | |
83 | dir_1a_file1.c | |
84 | dir_1a_file2.c | |
85 | dir_1a_file3.c | |
86 | Dir_1b/ | |
87 | Makefile | |
88 | Rules.mk | |
89 | src/ | |
90 | dir_1b_file1.c | |
91 | dir_1b_file2.c | |
92 | Dir_2/ | |
93 | Makefile | |
94 | Rules.mk | |
95 | dir_2_file1.c | |
96 | dir_2_file2.c | |
97 | Dir_2a/ | |
98 | Makefile | |
99 | Rules.mk | |
100 | dir_2a_file1.c | |
101 | dir_2a_file2.c | |
102 | dir_2a_file3.c | |
103 | Dir_2b/ | |
104 | Makefile | |
105 | Rules.mk | |
106 | dir_2b_file1.c | |
107 | dir_2b_file2.c | |
108 | dir_2b_file3.c | |
109 | Dir_ex/ | |
110 | Makefile | |
111 | Rules.mk | |
112 | ex.c | |
113 | Dir_3/ | |
114 | Makefile | |
115 | Rules.mk | |
116 | dir_3_file1.c | |
117 | dir_3_file2.c | |
118 | ||
119 | There's one top level make file (Rules.top) which eventually includes | |
120 | all the makefiles. In addition in each directory there is Makefile | |
121 | (which can be a link to the one in the top level directory) which | |
122 | searches for the Rules.top includes it with the default goal | |
123 | changed to rebuild only targets for the current directory. This allows | |
124 | you to run make from each subdirectory and update only part of the | |
125 | targets (in contrary to other implementation which usually require | |
126 | you to run it at the top level and make full build each time). | |
127 | ||
128 | This build system was designed to have very simple structure of the | |
129 | "user makefiles". The user just has to set the Rules.mk files in each | |
130 | directory and some general configuration options. All the "magic" is | |
131 | hidden in header.mk, footer.mk and skel.mk which don't have to be | |
132 | modified [1]. | |
133 | ||
134 | The structure of the Rules.mk is following (this is from top level | |
135 | Rules.top which has the same format as Rules.mk - and in fact it is | |
136 | suggested that it should be a symlink to normal Rules.mk file since it | |
137 | will allow for this project to act as a subproject of some super project | |
138 | treating your whole project tree as a subdirectory[2]): | |
139 | ||
140 | -8<---Rules.top--------------- | |
141 | 1: TARGETS = app.exe cli.exe | |
142 | 2: SUBDIRS = Dir_1 Dir_2 | |
143 | 3: | |
144 | 4: app.exe_DEPS = top_a.o top_b.o main.o $(SUBDIRS_TGTS) | |
145 | 5: app.exe_LIBS = -lm | |
146 | 6: # Let's use DEFAULT_MAKECMD for app.exe | |
147 | 7: | |
148 | 8: cli.exe_DEPS = cli.o cli_dep.o | |
149 | 9: cli.exe_CMD = $(LINK.c) $^ $(LDLIBS) -o $@ | |
150 | -8<--------------------------- | |
151 | ||
152 | Line 1 - this directory has two targets that should be built. | |
153 | Line 2 - this directory has two subdirectories that should be scanned | |
154 | Line 4 - app.exe depends on ... (SUBDIRS_TGTS is a variable that | |
155 | contains all the targets from the subdirectories mentioned at | |
156 | line 4) | |
157 | Line 5 - app.exe should be linked with math library | |
158 | Line 6 - app.exe will be built with default "rule" | |
159 | Line 8 - cli.exe depends on ... and | |
160 | Line 9 - use the following command to build it | |
161 | ||
162 | You can specify the targets for current directory in two ways: | |
163 | 1. Give them in TARGETS. Each target can have it's own *_DEPS, *_LIBS | |
164 | and *_CMD which give the dependencies, additional libs needed and | |
165 | a command to run which will update the target. They are explained | |
166 | a bit below. | |
167 | 2. The targets are simply objects - or in more general files that | |
168 | match patterns in AUTO_TGTS, and have appropriate rules in 'skeleton'. | |
169 | In that case you can list them in OBJS or SRCS like e.g. in Rules.mk | |
170 | from Dir_1a | |
171 | ||
172 | -8<---Dir_2/Dir_2a/Rules.mk--- | |
173 | 1: SRCS := dir_2a_file1.c dir_2a_file2.c dir_2a_file3.c | |
174 | -8<--------------------------- | |
175 | ||
176 | There are "reserved" variables that you should not modify. Most notably: | |
177 | - $(d) is the directory of the current Rules.mk [see note 2] | |
178 | - $(TOP) is the top level directory of the project tree | |
179 | - $(MK) is the directory where the included *.mk makefiles are | |
180 | For the full list you have to take a look at the makefiles in mk | |
181 | directory (e.g. in the skel.mk there are macros 'include_subdir_rules', | |
182 | 'save_vars', 'tgt_rule' and 'skeleton' which you should not change [1]). | |
183 | ||
184 | Going back to the Rules.mk. Normally wildcards in variable assignments | |
185 | are not expanded in make but this make system detects wildcards in SRCS | |
186 | and expands them (both in directory of the Rules.mk and its SRCS_VPATH | |
187 | subdirectories - see below what SRCS_VPATH is used for). Thus you can | |
188 | simply say in Rules.mk: | |
189 | ||
190 | SRCS := *.c | |
191 | ||
192 | If you have directory with large number of files where simple glob is | |
193 | what you want to use in SRCS but there are some files that you'd like to | |
194 | exclude just list them in SRCS_EXCLUDES :) - this is a list of makefile | |
195 | patterns e.g. | |
196 | ||
197 | SRCS_EXCLUDES := extra% test% | |
198 | ||
199 | Of course you can use the built in make wildcards but you should do that | |
200 | as follows: | |
201 | ||
202 | SRCS := $(notdir $(wildcard $(d)/*.c)) | |
203 | ||
204 | Keep in mind that the directory where make is invoked is usually different | |
205 | from where given Rules.mk is located. | |
206 | ||
207 | When supplying the value for *_DEPS you can refer to the object from the | |
208 | same directory with no directory prefix. To be specific all | |
209 | dependencies that are not absolute paths will be treated as ones from | |
210 | the $(OBJDIR) subdirectory of current directory (OBJDIR and OBJPATH are | |
211 | "discussed" below). You can use SUBDIRS_TGTS variable which will list | |
212 | all targets in subdirectories. You can also name them explicitly like | |
213 | $(TARGETS_$(d)/subdir) and so on - see e.g. the Rules.mk in Dir_2 | |
214 | directory where Dir_ex is mentioned as a subdirectory but is excluded | |
215 | from the *_DEPS (this allows you to create folders that "inherit" all | |
216 | the setting from the project build system but are not meant to be a part | |
217 | of the project itself - like examples). For instance: | |
218 | ||
219 | dir1_lib.a_DEPS = dir_1_file1.o dir_1_file2.o dir_1_file3.o $(SUBDIRS_TGTS) | |
220 | ||
221 | tells that dir1_lib.a (which will be created in Dir_1/$(OBJDIR)) depends | |
222 | on several object files from the same directory and the targets from all | |
223 | subdirectories. | |
224 | ||
225 | One last thing about the TARGETS/OBJS. By default source files for the | |
226 | objects are searched in the directory where Rules.mk is, but if you want | |
227 | to have source files in a subdirectory (say 'src') you can do that via | |
228 | SRCS_VPATH variable (see skel.mk). E.g.: | |
229 | ||
230 | SRCS_VPATH := src1 src2 | |
231 | ||
232 | will cause make to first look at the directory where Rules.mk is present | |
233 | and then in its src1 and src2 subdirectories. | |
234 | ||
235 | *_LIBS are appended to the LDLIBS variable when updating the target. | |
236 | This variable is used by several make built in rules but if you create | |
237 | your own rule or MAKECMD.* (see next paragraph) you can refer to it (see | |
238 | the function 'save_vars'). | |
239 | ||
240 | When *_CMD is not present and the target does not match any pattern in | |
241 | AUTO_TGTS then either MAKECMD.suff (where .suff is the suffix of the | |
242 | target) or DEFAULT_MAKECMD is used - take a look into skel.mk. | |
243 | ||
244 | If you want to setup special flags for compilation you can do that via | |
245 | "directory specific variables". As an example here's what I did for | |
246 | compilation of C files. There's a built in rule in make which uses | |
247 | COMPILE.c variable for making %.o out of %.c so I added $(DIR_CFLAGS) to | |
248 | its default value and DIR_CFLAGS is defined in skel.mk as: | |
249 | ||
250 | DIR_INCLUDES = $(addprefix -I,$(INCLUDES_$(<D))) | |
251 | DIR_CFLAGS = $(CFLAGS_$(<D)) $(DIR_INCLUDES) | |
252 | ||
253 | So it extracts the directory part of the first prerequisite in the rule | |
254 | (that is %.c file - check the section 'automatic variables' in make | |
255 | manual for the meaning of $(<) and $(<D)) and refer to variables named | |
256 | CFLAGS_the_directory_part and INCLUDES_the_directory_part. | |
257 | Thus if you wanted to add special includes for files in Dir_1/Dir_1b you | |
258 | could add: | |
259 | ||
260 | INCLUDES_$(d) := $(TOP)/some/special/include_dir | |
261 | ||
262 | into its Rules.mk and all files in this directory will be compiled with | |
263 | -I$(TOP)/some... switch. The same goes for CFLAGS and CXXFLAGS. | |
264 | ||
265 | The same goes for the linker flags - quoting from skel.mk: | |
266 | ||
267 | LDFLAGS = $(addprefix -L,$(LIBDIRS_$(subst /$(OBJDIR),,$(@D)))) | |
268 | LDLIBS = $(LIBS_$(@)) | |
269 | ||
270 | The above means that if targets in given directory need to be linked | |
271 | with special -L switches you can provide them via LIBDIRS_$(d) | |
272 | variables. If there are some global -L switches just append them in | |
273 | skel.mk. The second line above shows how *_LIBS variable that you can | |
274 | give for specific target gets added to the LDLIBS (there's 'save_vars' | |
275 | in between if you're curious :)). | |
276 | ||
277 | You can of course use target specific variables that GNU make supports | |
278 | so you have more control (if you don't know what target specific | |
279 | variables are take a look into manual). Say you want to compile | |
280 | dir_1b_file2.c with an additional flag but all other files in | |
281 | Dir_1/Dir_1b directory should not have this flag turned on. All you | |
282 | need to do is to add this line into Rules.mk in Dir_1b directory. | |
283 | ||
284 | $(OBJPATH)/dir_1b_file2.o : CFLAGS += -ansi | |
285 | ||
286 | OBJPATH is a variable that contains the full directory where the | |
287 | resulting object file will be placed. While we are at this, by default | |
288 | all targets are compiled into OBJDIR (defined in skel.mk as 'obj') | |
289 | subdirectory of the directory where Rules.mk is present. You can use | |
290 | this OBJDIR variable (and perhaps some conditional statements) to setup | |
291 | the output path according to your current compilation mode. E.g. | |
292 | obj/debug for objects with debugging information or obj/ppc_7xx for | |
293 | cross compilation to the given Power PC family and so on. There's | |
294 | predefined (in config* files) HOST_ARCH variable that you can use for | |
295 | this (e.g. set OBJDIR := obj/$(HOST_ARCH) in skel.mk). | |
296 | ||
297 | Finally let me explain what targets are defined and the way you can run | |
298 | them from command line. By default (that is if you have not modified | |
299 | anything :)) there are several targets that are "global". It is 'all', | |
300 | 'clean_all' and 'dist_clean'. If you specify them in the command line | |
301 | they will respectively rebuild whole tree, clean everything and clean | |
302 | everything together with the removal of OBJDIRs - no matter from which | |
303 | directory you started make. BTW if there's something that you want to | |
304 | clean up and it's not in the OBJDIR - e.g. you've got file lexer.l out | |
305 | of which lexer.c and lexer.h is generated and you want them to be | |
306 | removed - you can specify this in the variable CLEAN (this is relative | |
307 | to the directory where Rules.mk is). | |
308 | In addition to those each dir has "it's own" targets. These are: | |
309 | ||
310 | 1) dir_<directory_path> | |
311 | which builds all targets in given directory plus its dependencies | |
312 | 2) tree_<directory_path> | |
313 | which builds all targets in subtree starting at directory given | |
314 | 3) clean_<directory_path> | |
315 | which removes all "products" from the $(OBJDIR) subdirectory of | |
316 | current directory | |
317 | 4) clean_tree_<directory_path> | |
318 | which does clean_<directory_path> and the same for each its | |
319 | subdirectory | |
320 | ||
321 | For your convenience there are couple "aliases" defined (see Makefile). | |
322 | When no target is given on command line it defaults to dir_$(pwd). | |
323 | If you give 'clean' as a target that will result in execution of target | |
324 | clean_$(pwd) and the same for 'clean_tree'. E.g. say you're in Dir_1. | |
325 | Then: | |
326 | ||
327 | * 'make' (same as 'make dir_$(pwd)') | |
328 | builds all targets in the Dir_1 which in our example is | |
329 | Dir_1/obj/dir1_lib.a - of course any of its dependencies that are not | |
330 | up to date are updated also. This rule has one exception - if your | |
331 | Rules.mk has no targets and only SUBDIRS (e.g. you have grouped | |
332 | several subdirectories in one directory) then simple 'make' in this | |
333 | directory - instead of doing nothing - will build targets of all its | |
334 | subdirectories. | |
335 | * 'make tree' (same as 'make tree_$(pwd)') | |
336 | rebuilds everything in given subtree | |
337 | * 'make clean' (same as 'make clean_$(pwd)') | |
338 | removes everything from Dir_1/obj/ | |
339 | * 'make clean_tree (same as 'make clean_tree_$(pwd)') | |
340 | cleans Dir_1/obj and Dir_1/Dir_1[abc]/obj | |
341 | ||
342 | You can obviously provide the path by yourself - it does not have to | |
343 | be $(pwd) - and as usual you can build particular object files too e.g. | |
344 | 'make $(pwd)/obj/dir_1_file1.o' | |
345 | ||
346 | And that would be it. Gee, so much writing for something that is rather | |
347 | simple to use - go ahead and take a look again at these Rules.mk in | |
348 | various directories. Setting up you project should be simple by now :). | |
349 | ||
350 | Have fun! | |
351 | ||
352 | Andrzej Ostruszka | |
353 | ||
354 | [1] Unless this build system does not do what you wanted :-P. In that | |
355 | case you probably need to spiff it up. So you'll need to | |
356 | digest it first and note [3] is my hand at it :). | |
357 | ||
358 | [2] There is one limitation that you should be aware. | |
359 | Prior to commit 070f681 you should not have in your project two "target | |
360 | specific" LIBS, LDFLAGS or CMD variables (that is those that are used | |
361 | during second phase of make execution) that have the same names! For | |
362 | example in one part of your tree you're generating target abc which has | |
363 | it's abc_CMD and in the other part another target that has the same name | |
364 | and also it's own command. In such case the last assignment to abc_CMD | |
365 | will hold and it will be used for both targets. The same goes for LIBS | |
366 | and LDFLAGS. | |
367 | ||
368 | And this also applied to situation when you would like to use two | |
369 | subprojects in one larger project. There should be no clash between | |
370 | variables from these subprojects. | |
371 | ||
372 | Starting with commit 070f681 you can have in your (sub)projects two | |
373 | LIBS, LDFLAGS or CMD variables. However there is a catch here! Since | |
374 | these variables are not mandatory (you don't have to provide them for | |
375 | each target) in order to differentiate between case where abc_CMD was | |
376 | actually given for this instance of abc target from the situation where | |
377 | abc_CMD is still visible from previous instance of abc target those | |
378 | abc_(LIBS|LDFLAGS|CMD) variables are cleared once their value is | |
379 | used/remembered (see save_vars macro in skel.mk). That means referring | |
380 | to these variables outside Rules.mk where they were assigned will not | |
381 | work (and even in the same Rules.mk they will work only in case of | |
382 | simply expanded variables - not recursive). If you have such need I'd | |
383 | advice to introduce your own variable and use this variable in all | |
384 | places, e.g.: | |
385 | ||
386 | MATH_LIBS := -lgsl -lgslcblas -lm | |
387 | ... | |
388 | TARGETS := abc | |
389 | ||
390 | abc_DEPS = ... | |
391 | abc_LIBS = $(MATH_LIBS) | |
392 | ... | |
393 | ||
394 | [3] You should know that make works in two phases - first it scans the | |
395 | makefiles and then it begins their execution (see discussion of | |
396 | 'immediate' and 'deferred' in section 'Reading Makefiles' of make | |
397 | manual). This implies that the $(d) variable is not valid during | |
398 | execution of the commands used for target updates. If you need to refer | |
399 | to the directory of the target or prerequisite you should rely on | |
400 | automatic variables (@, <, ...) and built in functions (dir, notdir, | |
401 | ...). | |
402 | ||
403 | Every Rules.mk (or Rules.top) need to be included in cooperation with | |
404 | header.mk and footer.mk. This is now done automatically but in older | |
405 | versions this was not so and user had to include them manually. | |
406 | The main purpose of header is to clear all variables that you can use | |
407 | inside Rules.mk so that values set in one rules file does not propagate | |
408 | to other. In addition at the top level it sets the $(d) variable (which | |
409 | stands for the directory where the currently included Rules.mk is) and | |
410 | includes the skel.mk so the top level Rules.top can have exactly the | |
411 | same structure as other Rules.mk. | |
412 | ||
413 | The skel.mk is a skeleton which defines variables used by make. In | |
414 | addition it includes: | |
415 | - config.mk - this is a file with the "configuration" for your project | |
416 | - def_rules.mk - where to put general rules (e.g. pattern rules). E.g. | |
417 | if you want to add a rule that builds %.o out of %.m4 (by running m4 | |
418 | preprocessor before passing the contents of file to CC) just put it in | |
419 | here. | |
420 | ||
421 | skel.mk also defines some functions like save_vars and tgt_rule | |
422 | which are called in the footer.mk. Take a look into make manual for the | |
423 | way the functions are defined and called if you're not familiar with it. | |
424 | ||
425 | include_subdir_rules: This is where I keep track of directory of | |
426 | currently included makefile and include the Rules.mk from the | |
427 | subdirectories. This function is called in footer.mk in foreach | |
428 | loop with a subdirectory name as an argument. | |
429 | ||
430 | save_vars: This one is very simple it just saves the target specific | |
431 | variables under their "full path" variables. E.g. | |
432 | dir1_lib.a_DEPS will get saved as DEPS_$(TOP)/Dir_1/obj/dir1_lib.a | |
433 | This has two purposes. First it allows you to have targets with | |
434 | the same names in different directories (not that I recommend | |
435 | this :)) and allows for easier definition of tgt_rules :-P | |
436 | ||
437 | tgt_rule: This is where the rule for given target is created. First | |
438 | I convert all relative dependencies to the absolute ones then | |
439 | I include all dependency files (by default they are created as | |
440 | side effects during compilation - if your compiler does not | |
441 | allow you to do that just make simple shell script that will do | |
442 | this). I also append appropriate libs and then issue the rule | |
443 | for this target. By using the short circuiting 'or' command | |
444 | I give priority to the CMD over MAKECMD.suffix which itself has | |
445 | priority over DEFAULT_MAKECMD. | |
446 | ||
447 | skeleton: This is just a skeleton for compilation of files in given | |
448 | directory. If you want to add some rule here then also add | |
449 | appropriate pattern to the AUTO_TGTS that will filter out these | |
450 | targets and prevent generation of the specific rules via | |
451 | tgt_rule function. | |
452 | ||
453 | The footer.mk is where Rules.mk gets translated into the proper | |
454 | makefile. There's not much to explain, just take a look in there. | |
455 | First I memorize the targets for given directory (either from OBJS/SRCS | |
456 | or from TARGETS) with appropriate directory prefix. If there's need to | |
457 | save the target specific *_(CMD|DEP|LIB) I do it. Then I include all | |
458 | the Rules.mk from the subdirectories. Then I define the targets for | |
459 | given directory either explicitly (like clean_all or clean_$(d)) or by | |
460 | evaluation of the 'skeleton' mentioned above or by iterating through all | |
461 | targets which do not match AUTO_TGTS and evaluating what tgt_rule | |
462 | function for this target has returned. | |
463 | ||
464 | In case you want to play with these settings make sure you understand | |
465 | how make works, what are it's phases, when and how the variables are | |
466 | expanded and so on. It will save you a lot of time :). | |
467 | ||
468 | Best regards | |
469 | Andrzej |