]> git.ipfire.org Git - people/stevee/pakfire.git/blame - src/libpakfire/build.c
filelist: Fix check when skipping directories in post order
[people/stevee/pakfire.git] / src / libpakfire / build.c
CommitLineData
1a276007
MT
1/*#############################################################################
2# #
3# Pakfire - The IPFire package management system #
4# Copyright (C) 2021 Pakfire development team #
5# #
6# This program is free software: you can redistribute it and/or modify #
7# it under the terms of the GNU General Public License as published by #
8# the Free Software Foundation, either version 3 of the License, or #
9# (at your option) any later version. #
10# #
11# This program is distributed in the hope that it will be useful, #
12# but WITHOUT ANY WARRANTY; without even the implied warranty of #
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14# GNU General Public License for more details. #
15# #
16# You should have received a copy of the GNU General Public License #
17# along with this program. If not, see <http://www.gnu.org/licenses/>. #
18# #
19#############################################################################*/
20
21#include <errno.h>
22#include <stdlib.h>
23
24#include <pakfire/build.h>
25#include <pakfire/execute.h>
26#include <pakfire/dist.h>
ae7968a7 27#include <pakfire/file.h>
1a276007 28#include <pakfire/logging.h>
4c07774f 29#include <pakfire/package.h>
48c6f2e7 30#include <pakfire/packager.h>
1a276007
MT
31#include <pakfire/parser.h>
32#include <pakfire/private.h>
33#include <pakfire/types.h>
34#include <pakfire/util.h>
35
36static const char* stages[] = {
37 "prepare",
38 "build",
39 "test",
40 "install",
41 NULL,
42};
43
44#define TEMPLATE \
45 "#!/bin/bash --login\n" \
46 "\n" \
47 "set -e\n" \
48 "set -x\n" \
49 "\n" \
50 "%%{_%s}\n" \
51 "\n" \
52 "exit 0\n"
53
29f2151d 54static int pakfire_build_run_script(Pakfire pakfire, const char* filename, const char* args[],
8d0f3a35
MT
55 pakfire_execute_logging_callback logging_callback, void* data) {
56 char* script = NULL;
57 size_t size = 0;
58 char path[PATH_MAX];
59
60 DEBUG(pakfire, "Running build script '%s'...\n", filename);
61
62 // Make the source path
63 pakfire_path_join(path, PAKFIRE_SCRIPTS_DIR, filename);
64
65 // Open the source script
66 FILE* f = fopen(path, "r");
67 if (!f) {
68 ERROR(pakfire, "Could not open %s: %s\n", path, strerror(errno));
69 return 1;
70 }
71
72 // Read the script into memory
73 int r = pakfire_read_file_into_buffer(f, &script, &size);
74 if (r) {
75 ERROR(pakfire, "Could not read script into memory: %s\n", strerror(errno));
76 goto ERROR;
77 }
78
79 // Execute it
29f2151d 80 r = pakfire_execute_script(pakfire, script, size, args, NULL, 0, logging_callback, data);
8d0f3a35
MT
81 if (r) {
82 ERROR(pakfire, "Script '%s' failed with status %d\n", filename, r);
83 }
84
85ERROR:
86 if (script)
87 free(script);
88
89 return r;
90}
91
5b0b3dc2
MT
92static int find_dependency(char** haystack, const char* needle) {
93 if (!needle) {
94 errno = EINVAL;
95 return -1;
96 }
97
98 if (!haystack)
99 return 0;
100
101 for (char** element = haystack; *element; element++) {
102 if (strcmp(needle, *element) == 0)
103 return 1;
104 }
105
106 return 0;
107}
108
109static int pakfire_build_find_dependencies(Pakfire pakfire,
110 PakfirePackage pkg, PakfireFilelist filelist, const char* buildroot) {
111 char** provides = NULL;
112 char** requires = NULL;
113 char path[PATH_MAX];
114
115 // Allocate path to write the filelist to
116 int r = pakfire_make_path(pakfire, path, "tmp/.pakfire-filelist.XXXXXX");
117 if (r < 0)
118 return 1;
119
120 // Create a temporary file
121 FILE* f = pakfire_mktemp(path);
122 if (!f)
123 goto ERROR;
124
125 // Write filelist to the temporary file
126 r = pakfire_filelist_export(filelist, f);
127 fclose(f);
128 if (r) {
129 ERROR(pakfire, "Could not export filelist: %s\n", strerror(errno));
130 goto ERROR;
131 }
132
133 const char* root = pakfire_get_path(pakfire);
134
135 // Pass buildroot and the filelist as arguments
136 const char* args[] = {
137 buildroot, pakfire_path_relpath(root, path), NULL
138 };
139
140 // Find all provides
141 r = pakfire_build_run_script(pakfire, "find-provides", args,
142 pakfire_execute_capture_stdout_to_array, &provides);
143 if (r) {
144 ERROR(pakfire, "find-provides returned with error %d\n", r);
145 goto ERROR;
146 }
147
148 // Find all requires
149 r = pakfire_build_run_script(pakfire, "find-requires", args,
150 pakfire_execute_capture_stdout_to_array, &requires);
151 if (r) {
152 ERROR(pakfire, "find-requires returned with error %d\n", r);
153 goto ERROR;
154 }
155
156 // Add all provides to the package
157 if (provides) {
158 for (char** element = provides; *element; element++) {
159 DEBUG(pakfire, "Adding provides: %s\n", *element);
160 pakfire_package_add_provides(pkg, *element);
161 }
162 }
163
164 // Add all requires to the package
165 if (requires) {
166 for (char** element = requires; *element; element++) {
167 // Skip adding this requirement if also provided by this package
168 if (find_dependency(provides, *element))
169 continue;
170
171 DEBUG(pakfire, "Adding requires: %s\n", *element);
172 pakfire_package_add_requires(pkg, *element);
173 }
174 }
175
176 // Success
177 r = 0;
178
179ERROR:
180 if (provides) {
181 for (char** element = provides; *element; element++)
182 free(*element);
183 free(provides);
184 }
185 if (requires) {
186 for (char** element = requires; *element; element++)
187 free(*element);
188 free(requires);
189 }
190 unlink(path);
191
192 return r;
193}
194
73543ae3
MT
195static int append_to_array(const char*** array, const char* s) {
196 unsigned int length = 0;
197
198 // Determine the length of the existing array
199 if (*array) {
200 for (const char** element = *array; *element; element++)
201 length++;
202 }
203
204 // Allocate space
205 *array = reallocarray(*array, length + 2, sizeof(**array));
206 if (!*array)
207 return 1;
208
209 // Append s and terminate the array
210 (*array)[length] = s;
211 (*array)[length + 1] = NULL;
212
213 return 0;
214}
215
216static int pakfire_build_package_add_files(Pakfire pakfire, PakfireParser makefile,
5b0b3dc2
MT
217 const char* buildroot, const char* namespace, PakfirePackage pkg,
218 struct pakfire_packager* packager) {
73543ae3
MT
219 PakfireFilelist filelist = NULL;
220 char path[PATH_MAX];
221 int r = 1;
222
223 char** files = NULL;
224 const char** includes = NULL;
225 const char** excludes = NULL;
226
227 // Fetch filelist from makefile
228 files = pakfire_parser_get_split(makefile, namespace, "files", '\n');
229
230 // No files to package?
231 if (!files)
232 return 0;
233
73543ae3
MT
234 // Convert to absolute path
235 pakfire_make_path(pakfire, path, buildroot);
236
237 // Split into includes and excludes
238 for (char** file = files; *file; file++) {
239 if (**file == '!')
240 r = append_to_array(&excludes, *file);
241 else
242 r = append_to_array(&includes, *file);
243 if (r)
244 goto ERROR;
245 }
246
247 // Allocate a new filelist
248 r = pakfire_filelist_create(&filelist, pakfire);
249 if (r)
250 goto ERROR;
251
252 // Scan for files
253 r = pakfire_filelist_scan(filelist, path, includes, excludes);
254 if (r)
255 goto ERROR;
256
ae7968a7 257 const size_t length = pakfire_filelist_size(filelist);
73543ae3
MT
258 DEBUG(pakfire, "%zu file(s) found\n", length);
259
5b0b3dc2
MT
260 // Find dependencies
261 r = pakfire_build_find_dependencies(pakfire, pkg, filelist, buildroot);
262 if (r) {
263 ERROR(pakfire, "Finding dependencies failed: %s\n", strerror(errno));
264 goto ERROR;
265 }
266
ae7968a7
MT
267 // Add all files to the package
268 for (unsigned int i = 0; i < length; i++) {
269 PakfireFile file = pakfire_filelist_get(filelist, i);
270
271 // Add the file to the package
272 r = pakfire_packager_add(
273 packager,
274 pakfire_file_get_abspath(file),
275 pakfire_file_get_path(file)
276 );
277 if (r) {
278 pakfire_file_unref(file);
279 goto ERROR;
280 }
281
3fca5032
MT
282 // Remove the file after it was packaged
283 r = pakfire_file_cleanup(file);
284 if (r) {
285 pakfire_file_unref(file);
286 goto ERROR;
287 }
288
ae7968a7
MT
289 pakfire_file_unref(file);
290 }
73543ae3
MT
291
292ERROR:
293 if (filelist)
294 pakfire_filelist_unref(filelist);
295 if (files) {
296 for (char** file = files; *file; file++)
297 free(*file);
298 free(files);
299 }
300 if (includes)
301 free(includes);
302 if (excludes)
303 free(excludes);
73543ae3
MT
304
305 return r;
306}
307
48c6f2e7 308static int pakfire_build_package(Pakfire pakfire, PakfireParser makefile,
5b0b3dc2 309 const char* buildroot, const char* namespace, const char* target) {
4c07774f
MT
310 PakfireRepo repo = NULL;
311 PakfirePackage pkg = NULL;
48c6f2e7 312 struct pakfire_packager* packager = NULL;
73543ae3 313
a50bde9c
MT
314 int r = 1;
315
316 // Expand the handle into the package name
4c07774f 317 char* name = pakfire_parser_expand(makefile, "packages", namespace);
a50bde9c
MT
318 if (!name) {
319 ERROR(pakfire, "Could not get package name: %s\n", strerror(errno));
320 goto ERROR;
321 }
322
323 INFO(pakfire, "Building package '%s'...\n", name);
324
4c07774f
MT
325 // Fetch build architecture
326 const char* arch = pakfire_get_arch(pakfire);
327 if (!arch)
328 goto ERROR;
329
330 // Fetch the dummy repository
331 repo = pakfire_get_repo(pakfire, "@dummy");
332 if (!repo)
333 goto ERROR;
334
335 // Fetch package from makefile
336 r = pakfire_parser_create_package(makefile, &pkg, repo, namespace, arch);
337 if (r) {
338 ERROR(pakfire, "Could not create package from makefile: %s\n", strerror(errno));
339 goto ERROR;
340 }
341
48c6f2e7
MT
342 // Create a packager
343 r = pakfire_packager_create(&packager, pkg);
344 if (r)
345 goto ERROR;
346
73543ae3 347 // Add files
5b0b3dc2
MT
348 r = pakfire_build_package_add_files(pakfire, makefile, buildroot, namespace,
349 pkg, packager);
73543ae3
MT
350 if (r)
351 goto ERROR;
352
ae7968a7
MT
353 // Write the finished package
354 r = pakfire_packager_finish_to_directory(packager, target);
355 if (r) {
356 ERROR(pakfire, "pakfire_packager_finish() failed: %s\n", strerror(errno));
357 goto ERROR;
358 }
359
4c07774f 360#ifdef ENABLE_DEBUG
23e22751 361 char* dump = pakfire_package_dump(pkg, PAKFIRE_PKG_DUMP_LONG);
4c07774f
MT
362 if (dump) {
363 DEBUG(pakfire, "%s\n", dump);
364 free(dump);
365 }
366#endif
a50bde9c
MT
367
368 // Success
369 r = 0;
370
371ERROR:
48c6f2e7
MT
372 if (packager)
373 pakfire_packager_unref(packager);
4c07774f
MT
374 if (repo)
375 pakfire_repo_unref(repo);
376 if (pkg)
377 pakfire_package_unref(pkg);
a50bde9c
MT
378 if (name)
379 free(name);
380
381 return r;
382}
383
5b0b3dc2
MT
384static int pakfire_build_packages(Pakfire pakfire, PakfireParser makefile,
385 const char* buildroot, const char* target) {
a50bde9c
MT
386 DEBUG(pakfire, "Creating packages...");
387 int r = 1;
388
389 // Fetch a list all all packages
390 char** packages = pakfire_parser_list_namespaces(makefile, "packages.package:*");
391 if (!packages) {
392 ERROR(pakfire, "Could not find any packages: %s\n", strerror(errno));
393 goto ERROR;
394 }
395
396 unsigned int num_packages = 0;
397
398 // Count how many packages we have
399 for (char** package = packages; *package; package++)
400 num_packages++;
401
402 DEBUG(pakfire, "Found %d package(s)\n", num_packages);
403
404 // Build packages in reverse order
405 for (int i = num_packages - 1; i >= 0; i--) {
5b0b3dc2 406 r = pakfire_build_package(pakfire, makefile, buildroot, packages[i], target);
a50bde9c
MT
407 if (r)
408 goto ERROR;
409 }
410
411 // Success
412 r = 0;
413
414ERROR:
415 if (packages)
416 free(packages);
417
418 return r;
419}
420
7d999600
MT
421static int pakfire_build_stage(Pakfire pakfire, PakfireParser makefile, const char* stage,
422 pakfire_execute_logging_callback logging_callback, void* data) {
1a276007
MT
423 char template[1024];
424
425 // Prepare template for this stage
426 int r = pakfire_string_format(template, TEMPLATE, stage);
427 if (r < 0)
428 return r;
429
2ffd21f9
MT
430 // Fetch the environment
431 char** envp = pakfire_parser_make_environ(makefile);
432
1a276007
MT
433 // Create the build script
434 char* script = pakfire_parser_expand(makefile, "build", template);
435 if (!script) {
436 ERROR(pakfire, "Could not generate the build script for stage '%s': %s\n",
437 stage, strerror(errno));
438 goto ERROR;
439 }
440
441 INFO(pakfire, "Running build stage '%s'\n", stage);
442
2ffd21f9 443 r = pakfire_execute_script(pakfire, script, strlen(script), NULL, envp, 0,
7d999600 444 logging_callback, data);
1a276007
MT
445 if (r) {
446 ERROR(pakfire, "Build stage '%s' failed with status %d\n", stage, r);
447 }
448
449ERROR:
2ffd21f9
MT
450 if (envp) {
451 for (char** e = envp; *e; e++)
452 free(*e);
453 free(envp);
454 }
1a276007
MT
455 if (script)
456 free(script);
457
458 return r;
459}
460
39f45b30
MT
461static const char* post_build_scripts[] = {
462 "remove-static-libs",
1f62ffae 463 "check-symlinks",
022b794e 464 "check-unsafe-files",
0b5f0bbc 465 "check-libraries",
16043831 466 "check-rpaths",
99aee237 467 "check-buildroot",
7e1fec6f 468 "check-include",
dd864160 469 "check-hardening",
ae703321 470 "check-interpreters",
2504194a 471 "check-fhs",
39f45b30 472 "compress-man-pages",
ffb65de6 473 "strip",
39f45b30
MT
474 NULL,
475};
476
477static int pakfire_build_run_post_build_scripts(Pakfire pakfire, const char* buildroot,
478 pakfire_execute_logging_callback logging_callback, void* data) {
479 // Set default arguments for build scripts
480 const char* args[] = {
481 buildroot, NULL
482 };
483
484 // Run them one by one
485 for (const char** script = post_build_scripts; *script; script++) {
486 int r = pakfire_build_run_script(pakfire, *script, args, logging_callback, data);
487 if (r)
488 return r;
489 }
490
491 return 0;
492}
493
1a276007 494PAKFIRE_EXPORT int pakfire_build(Pakfire pakfire, const char* path,
7d999600
MT
495 const char* target, int flags,
496 pakfire_execute_logging_callback logging_callback, void* data) {
1a276007 497 PakfireParser makefile = NULL;
3d4011eb 498 char buildroot[PATH_MAX];
1a276007 499 struct pakfire_parser_error* error = NULL;
3d4011eb 500 int r;
1a276007 501
48c6f2e7
MT
502 // Check for valid input
503 if (!path) {
504 errno = EINVAL;
505 return 1;
506 }
507
508 // The default target is the local repository path
509 if (!target) {
510 PakfireRepo repo = pakfire_get_repo(pakfire, "local");
511 if (!repo) {
512 errno = EINVAL;
513 return 1;
514 }
515
516 target = pakfire_repo_get_path(repo);
517 pakfire_repo_unref(repo);
518 }
519
3d4011eb
MT
520 const char* root = pakfire_get_path(pakfire);
521
522 // Create BUILDROOT
523 pakfire_make_path(pakfire, buildroot, "/tmp/.buildroot.XXXXXX");
524 if (!pakfire_mkdtemp(buildroot))
525 return 1;
526
527 // Make relative BUILDROOT path
528 const char* buildroot_rel = pakfire_path_relpath(root, buildroot);
529 if (!buildroot_rel)
530 return 1;
531
1a276007 532 // Read makefile
3d4011eb 533 r = pakfire_read_makefile(&makefile, pakfire, path, &error);
1a276007
MT
534 if (r) {
535 if (error) {
536 ERROR(pakfire, "Could not parse makefile %s: %s\n", path,
537 pakfire_parser_error_get_message(error));
538 pakfire_parser_error_unref(error);
539 } else {
540 ERROR(pakfire, "Could not parse makefile %s: %s\n", path,
541 strerror(errno));
542 }
543
544 goto ERROR;
545 }
546
3d4011eb 547 // Set BUILDROOT
fea2e884 548 pakfire_parser_set(makefile, NULL, "BUILDROOT", buildroot_rel, 0);
3d4011eb 549
1a276007
MT
550 // Run through all build stages
551 for (const char** stage = stages; *stage; stage++) {
7d999600 552 int r = pakfire_build_stage(pakfire, makefile, *stage, logging_callback, data);
1a276007
MT
553 if (r) {
554 // Drop to a shell for debugging
555 if (flags & PAKFIRE_BUILD_INTERACTIVE)
556 pakfire_shell(pakfire);
557
558 goto ERROR;
559 }
560 }
561
39f45b30
MT
562 // Run post build scripts
563 r = pakfire_build_run_post_build_scripts(pakfire, buildroot_rel, logging_callback, data);
8d0f3a35
MT
564 if (r)
565 goto ERROR;
566
a50bde9c 567 // Create the packages
5b0b3dc2 568 r = pakfire_build_packages(pakfire, makefile, buildroot_rel, target);
a50bde9c
MT
569 if (r) {
570 ERROR(pakfire, "Could not create packages: %s\n", strerror(errno));
571 goto ERROR;
572 }
1a276007
MT
573
574ERROR:
575 if (makefile)
576 pakfire_parser_unref(makefile);
577
3d4011eb
MT
578 // Remove buildroot
579 pakfire_rmtree(buildroot, 0);
580
1a276007
MT
581 return r;
582}
583
584PAKFIRE_EXPORT int pakfire_shell(Pakfire pakfire) {
585 const char* argv[] = {
586 "/bin/bash", "--login", NULL,
587 };
588
589 const int flags =
590 PAKFIRE_EXECUTE_INTERACTIVE | PAKFIRE_EXECUTE_ENABLE_NETWORK;
591
592 return pakfire_execute(pakfire, argv, NULL, flags, NULL, NULL);
593}