1 /*#############################################################################
3 # Pakfire - The IPFire package management system #
4 # Copyright (C) 2021 Pakfire development team #
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. #
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. #
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/>. #
19 #############################################################################*/
24 #include <sys/mount.h>
26 #include <uuid/uuid.h>
28 #include <pakfire/build.h>
29 #include <pakfire/execute.h>
30 #include <pakfire/dist.h>
31 #include <pakfire/file.h>
32 #include <pakfire/logging.h>
33 #include <pakfire/package.h>
34 #include <pakfire/packager.h>
35 #include <pakfire/parser.h>
36 #include <pakfire/private.h>
37 #include <pakfire/repo.h>
38 #include <pakfire/scriptlet.h>
39 #include <pakfire/snapshot.h>
40 #include <pakfire/types.h>
41 #include <pakfire/util.h>
43 #define CCACHE_DIR "/var/cache/ccache"
45 static const char* stages
[] = {
54 "#!/bin/bash --login\n" \
63 static int pakfire_build_install_packages(Pakfire pakfire
, int* snapshot_needs_update
) {
64 char** packages
= NULL
;
67 struct pakfire_config
* config
= pakfire_get_config(pakfire
);
69 // Fetch build environment
70 const char* requires
= pakfire_config_get(config
, "build", "requires", NULL
);
72 ERROR(pakfire
, "No build requirements have been defined\n");
76 // Split requirements into packages
77 packages
= pakfire_split_string(requires
, ',');
84 r
= pakfire_install(pakfire
, 0, (const char**)packages
, NULL
, 0, &changed
);
86 ERROR(pakfire
, "Could not install build dependencies\n");
90 // Mark snapshot as changed if new packages were installed
92 *snapshot_needs_update
= 1;
95 r
= pakfire_sync(pakfire
, 0, 0, &changed
);
97 ERROR(pakfire
, "Could not update packages: %m\n");
101 // Has anything changed?
103 *snapshot_needs_update
= 1;
110 for (char** package
= packages
; *package
; package
++)
115 pakfire_config_unref(config
);
120 int pakfire_build_setup(Pakfire pakfire
) {
125 if (!pakfire_has_flag(pakfire
, PAKFIRE_FLAGS_DISABLE_CCACHE
)) {
126 r
= pakfire_make_cache_path(pakfire
, path
, "%s", "ccache");
130 // Ensure path exists
131 r
= pakfire_mkdir(path
, 0);
132 if (r
&& errno
!= EEXIST
) {
133 ERROR(pakfire
, "Could not create ccache directory %s: %m\n", path
);
137 r
= pakfire_bind(pakfire
, path
, CCACHE_DIR
, MS_NOSUID
|MS_NOEXEC
|MS_NODEV
);
139 ERROR(pakfire
, "Could not mount ccache: %m\n");
145 if (!pakfire_has_flag(pakfire
, PAKFIRE_FLAGS_DISABLE_SNAPSHOT
)) {
146 r
= pakfire_make_cache_path(pakfire
, path
, "%s", "snapshot.tar.zst");
151 FILE* f
= fopen(path
, "r");
153 // Try restoring the snapshot
155 r
= pakfire_snapshot_restore(pakfire
, f
);
162 // Tells us whether we need to (re-)create the snapshot
163 int snapshot_needs_update
= 0;
165 // Install or update any build dependencies
166 r
= pakfire_build_install_packages(pakfire
, &snapshot_needs_update
);
170 if (snapshot_needs_update
) {
171 // Open snapshot file for writing
172 f
= fopen(path
, "w");
174 ERROR(pakfire
, "Could not open snapshot file for writing: %m\n");
178 // Create a new snapshot
179 r
= pakfire_snapshot_create(pakfire
, f
);
190 static int pakfire_build_run_script(Pakfire pakfire
, const char* filename
, const char* args
[],
191 pakfire_execute_logging_callback logging_callback
, void* data
) {
196 DEBUG(pakfire
, "Running build script '%s'...\n", filename
);
198 // Make the source path
199 pakfire_path_join(path
, PAKFIRE_SCRIPTS_DIR
, filename
);
201 // Open the source script
202 FILE* f
= fopen(path
, "r");
204 ERROR(pakfire
, "Could not open %s: %m\n", path
);
208 // Read the script into memory
209 int r
= pakfire_read_file_into_buffer(f
, &script
, &size
);
211 ERROR(pakfire
, "Could not read script into memory: %m\n");
216 r
= pakfire_execute_script(pakfire
, script
, size
, args
, NULL
, 0, logging_callback
, data
);
218 ERROR(pakfire
, "Script '%s' failed with status %d\n", filename
, r
);
228 static int find_dependency(char** haystack
, const char* needle
) {
237 for (char** element
= haystack
; *element
; element
++) {
238 if (strcmp(needle
, *element
) == 0)
245 static int pakfire_build_find_dependencies(Pakfire pakfire
,
246 struct pakfire_package
* pkg
, struct pakfire_filelist
* filelist
, const char* buildroot
) {
247 char** provides
= NULL
;
248 char** requires
= NULL
;
251 // Allocate path to write the filelist to
252 int r
= pakfire_make_path(pakfire
, path
, "tmp/.pakfire-filelist.XXXXXX");
256 // Create a temporary file
257 FILE* f
= pakfire_mktemp(path
);
261 // Write filelist to the temporary file
262 r
= pakfire_filelist_export(filelist
, f
);
265 ERROR(pakfire
, "Could not export filelist: %m\n");
269 const char* root
= pakfire_get_path(pakfire
);
271 // Pass buildroot and the filelist as arguments
272 const char* args
[] = {
273 buildroot
, pakfire_path_relpath(root
, path
), NULL
277 r
= pakfire_build_run_script(pakfire
, "find-provides", args
,
278 pakfire_execute_capture_stdout_to_array
, &provides
);
280 ERROR(pakfire
, "find-provides returned with error %d\n", r
);
285 r
= pakfire_build_run_script(pakfire
, "find-requires", args
,
286 pakfire_execute_capture_stdout_to_array
, &requires
);
288 ERROR(pakfire
, "find-requires returned with error %d\n", r
);
292 // Add all provides to the package
294 for (char** element
= provides
; *element
; element
++) {
295 DEBUG(pakfire
, "Adding provides: %s\n", *element
);
296 pakfire_package_add_provides(pkg
, *element
);
300 // Add all requires to the package
302 for (char** element
= requires
; *element
; element
++) {
303 // Skip adding this requirement if also provided by this package
304 if (find_dependency(provides
, *element
))
307 DEBUG(pakfire
, "Adding requires: %s\n", *element
);
308 pakfire_package_add_requires(pkg
, *element
);
317 for (char** element
= provides
; *element
; element
++)
322 for (char** element
= requires
; *element
; element
++)
331 static int append_to_array(const char*** array
, const char* s
) {
332 unsigned int length
= 0;
334 // Determine the length of the existing array
336 for (const char** element
= *array
; *element
; element
++)
341 *array
= reallocarray(*array
, length
+ 2, sizeof(**array
));
345 // Append s and terminate the array
346 (*array
)[length
] = s
;
347 (*array
)[length
+ 1] = NULL
;
352 static int pakfire_build_package_add_files(Pakfire pakfire
, struct pakfire_parser
* makefile
,
353 const char* buildroot
, const char* namespace, struct pakfire_package
* pkg
,
354 struct pakfire_packager
* packager
) {
355 struct pakfire_filelist
* filelist
= NULL
;
360 const char** includes
= NULL
;
361 const char** excludes
= NULL
;
363 // Fetch filelist from makefile
364 files
= pakfire_parser_get_split(makefile
, namespace, "files", '\n');
366 // No files to package?
370 // Convert to absolute path
371 pakfire_make_path(pakfire
, path
, buildroot
);
373 // Split into includes and excludes
374 for (char** file
= files
; *file
; file
++) {
376 r
= append_to_array(&excludes
, *file
);
378 r
= append_to_array(&includes
, *file
);
383 // Allocate a new filelist
384 r
= pakfire_filelist_create(&filelist
, pakfire
);
389 r
= pakfire_filelist_scan(filelist
, path
, includes
, excludes
);
393 const size_t length
= pakfire_filelist_size(filelist
);
394 DEBUG(pakfire
, "%zu file(s) found\n", length
);
396 // Nothing to do if the filelist is empty
401 r
= pakfire_build_find_dependencies(pakfire
, pkg
, filelist
, buildroot
);
403 ERROR(pakfire
, "Finding dependencies failed: %m\n");
407 // Add all files to the package
408 for (unsigned int i
= 0; i
< length
; i
++) {
409 struct pakfire_file
* file
= pakfire_filelist_get(filelist
, i
);
411 // Add the file to the package
412 r
= pakfire_packager_add(
414 pakfire_file_get_abspath(file
),
415 pakfire_file_get_path(file
)
418 pakfire_file_unref(file
);
422 // Remove the file after it was packaged
423 r
= pakfire_file_cleanup(file
);
425 pakfire_file_unref(file
);
429 pakfire_file_unref(file
);
434 pakfire_filelist_unref(filelist
);
436 for (char** file
= files
; *file
; file
++)
448 static int pakfire_build_add_scriptlet_requires(Pakfire pakfire
, struct pakfire_package
* pkg
,
449 struct pakfire_scriptlet
* scriptlet
) {
450 char** prerequires
= NULL
;
455 const char* root
= pakfire_get_path(pakfire
);
458 r
= pakfire_make_path(pakfire
, path
, "tmp/.pakfire-scriptlet.XXXXXX");
462 // Fetch scriptlet payload
463 const char* data
= pakfire_scriptlet_get_data(scriptlet
, &size
);
465 // Create a temporary file
466 FILE* f
= pakfire_mktemp(path
);
471 ssize_t bytes_written
= fwrite(data
, 1, size
, f
);
472 if (bytes_written
< 0) {
473 ERROR(pakfire
, "Could not write to %s: %m\n", path
);
482 const char* args
[] = {
483 pakfire_path_relpath(root
, path
),
487 // Find all pre-requires
488 r
= pakfire_build_run_script(pakfire
, "find-prerequires", args
,
489 pakfire_execute_capture_stdout_to_array
, &prerequires
);
493 // Add all pre-requires to the package
495 for (char** element
= prerequires
; *element
; element
++) {
496 DEBUG(pakfire
, "Adding pre-requires: %s\n", *element
);
497 pakfire_package_add_prerequires(pkg
, *element
);
505 for (char** element
= prerequires
; *element
; element
++)
512 static int pakfire_build_package_add_scriptlet(Pakfire pakfire
, struct pakfire_package
* pkg
,
513 struct pakfire_packager
* packager
, const char* type
, const char* data
) {
514 struct pakfire_scriptlet
* scriptlet
= NULL
;
518 // Wrap scriptlet into a shell script
519 r
= asprintf(&shell
, "#!/bin/sh\n\nset -e\n\n%s\n\nexit 0\n", data
);
523 // Create a scriptlet
524 r
= pakfire_scriptlet_create(&scriptlet
, pakfire
, type
, shell
, 0);
528 // Add it to the package
529 r
= pakfire_packager_add_scriptlet(packager
, scriptlet
);
531 ERROR(pakfire
, "Could not add scriptlet %s\n", type
);
535 // Add scriptlet requirements
536 r
= pakfire_build_add_scriptlet_requires(pakfire
, pkg
, scriptlet
);
538 ERROR(pakfire
, "Could not add scriptlet requirements: %m\n");
547 pakfire_scriptlet_unref(scriptlet
);
554 static int pakfire_build_package_add_scriptlets(Pakfire pakfire
, struct pakfire_parser
* makefile
,
555 const char* namespace, struct pakfire_package
* pkg
, struct pakfire_packager
* packager
) {
559 for (const char** type
= pakfire_scriptlet_types
; *type
; type
++) {
560 r
= pakfire_string_format(name
, "scriptlet:%s", *type
);
564 // Fetch the scriptlet
565 char* data
= pakfire_parser_get(makefile
, namespace, name
);
569 // Add it to the package
570 r
= pakfire_build_package_add_scriptlet(pakfire
, pkg
, packager
, *type
, data
);
582 static int pakfire_build_package(Pakfire pakfire
, struct pakfire_parser
* makefile
,
583 uuid_t
* build_id
, const char* buildroot
, const char* namespace, const char* target
) {
584 struct pakfire_repo
* repo
= NULL
;
585 struct pakfire_package
* pkg
= NULL
;
586 struct pakfire_packager
* packager
= NULL
;
590 // Expand the handle into the package name
591 char* name
= pakfire_parser_expand(makefile
, "packages", namespace);
593 ERROR(pakfire
, "Could not get package name: %m\n");
597 INFO(pakfire
, "Building package '%s'...\n", name
);
599 // Fetch build architecture
600 const char* arch
= pakfire_get_arch(pakfire
);
604 // Fetch the dummy repository
605 repo
= pakfire_get_repo(pakfire
, "@dummy");
609 // Fetch package from makefile
610 r
= pakfire_parser_create_package(makefile
, &pkg
, repo
, namespace, arch
);
612 ERROR(pakfire
, "Could not create package from makefile: %m\n");
617 pakfire_package_set_build_id_from_uuid(pkg
, build_id
);
620 r
= pakfire_packager_create(&packager
, pkg
);
625 r
= pakfire_build_package_add_files(pakfire
, makefile
, buildroot
, namespace,
631 r
= pakfire_build_package_add_scriptlets(pakfire
, makefile
, namespace, pkg
, packager
);
635 // Write the finished package
636 r
= pakfire_packager_finish_to_directory(packager
, target
);
638 ERROR(pakfire
, "pakfire_packager_finish() failed: %m\n");
643 char* dump
= pakfire_package_dump(pkg
, PAKFIRE_PKG_DUMP_LONG
);
645 DEBUG(pakfire
, "%s\n", dump
);
655 pakfire_packager_unref(packager
);
657 pakfire_repo_unref(repo
);
659 pakfire_package_unref(pkg
);
666 static int pakfire_build_packages(Pakfire pakfire
, struct pakfire_parser
* makefile
,
667 uuid_t
* build_id
, const char* buildroot
, const char* target
) {
668 DEBUG(pakfire
, "Creating packages...");
671 // Fetch a list all all packages
672 char** packages
= pakfire_parser_list_namespaces(makefile
, "packages.package:*");
674 ERROR(pakfire
, "Could not find any packages: %m\n");
678 unsigned int num_packages
= 0;
680 // Count how many packages we have
681 for (char** package
= packages
; *package
; package
++)
684 DEBUG(pakfire
, "Found %d package(s)\n", num_packages
);
686 // Build packages in reverse order
687 for (int i
= num_packages
- 1; i
>= 0; i
--) {
688 r
= pakfire_build_package(pakfire
, makefile
, build_id
, buildroot
, packages
[i
], target
);
703 static int pakfire_build_stage(Pakfire pakfire
, struct pakfire_parser
* makefile
, const char* stage
,
704 pakfire_execute_logging_callback logging_callback
, void* data
) {
707 // Prepare template for this stage
708 int r
= pakfire_string_format(template, TEMPLATE
, stage
);
712 // Fetch the environment
713 char** envp
= pakfire_parser_make_environ(makefile
);
715 // Create the build script
716 char* script
= pakfire_parser_expand(makefile
, "build", template);
718 ERROR(pakfire
, "Could not generate the build script for stage '%s': %m\n", stage
);
722 INFO(pakfire
, "Running build stage '%s'\n", stage
);
724 r
= pakfire_execute_script(pakfire
, script
, strlen(script
), NULL
, envp
, 0,
725 logging_callback
, data
);
727 ERROR(pakfire
, "Build stage '%s' failed with status %d\n", stage
, r
);
732 for (char** e
= envp
; *e
; e
++)
742 static const char* post_build_scripts
[] = {
743 "remove-static-libs",
745 "check-unsafe-files",
751 "check-interpreters",
753 "compress-man-pages",
758 static int pakfire_build_run_post_build_scripts(Pakfire pakfire
, const char* buildroot
,
759 pakfire_execute_logging_callback logging_callback
, void* data
) {
760 // Set default arguments for build scripts
761 const char* args
[] = {
765 // Run them one by one
766 for (const char** script
= post_build_scripts
; *script
; script
++) {
767 int r
= pakfire_build_run_script(pakfire
, *script
, args
, logging_callback
, data
);
775 static int pakfire_build_makefile(Pakfire pakfire
, const char* path
, const char* target
,
776 uuid_t
* build_id
, int flags
,
777 pakfire_execute_logging_callback logging_callback
, void* data
) {
778 struct pakfire_parser
* makefile
= NULL
;
779 char buildroot
[PATH_MAX
];
780 struct pakfire_parser_error
* error
= NULL
;
783 const char* root
= pakfire_get_path(pakfire
);
786 pakfire_make_path(pakfire
, buildroot
, "/tmp/.buildroot.XXXXXX");
787 if (!pakfire_mkdtemp(buildroot
))
790 // Make relative BUILDROOT path
791 const char* buildroot_rel
= pakfire_path_relpath(root
, buildroot
);
796 r
= pakfire_read_makefile(&makefile
, pakfire
, path
, &error
);
799 ERROR(pakfire
, "Could not parse makefile %s: %s\n", path
,
800 pakfire_parser_error_get_message(error
));
801 pakfire_parser_error_unref(error
);
803 ERROR(pakfire
, "Could not parse makefile %s: %m\n", path
);
810 pakfire_parser_set(makefile
, NULL
, "BUILDROOT", buildroot_rel
, 0);
812 // Run through all build stages
813 for (const char** stage
= stages
; *stage
; stage
++) {
814 int r
= pakfire_build_stage(pakfire
, makefile
, *stage
, logging_callback
, data
);
816 // Drop to a shell for debugging
817 if (flags
& PAKFIRE_BUILD_INTERACTIVE
)
818 pakfire_shell(pakfire
);
824 // Run post build scripts
825 r
= pakfire_build_run_post_build_scripts(pakfire
, buildroot_rel
, logging_callback
, data
);
829 // Create the packages
830 r
= pakfire_build_packages(pakfire
, makefile
, build_id
, buildroot_rel
, target
);
832 ERROR(pakfire
, "Could not create packages: %m\n");
838 pakfire_parser_unref(makefile
);
841 pakfire_rmtree(buildroot
, 0);
846 PAKFIRE_EXPORT
int pakfire_build(Pakfire pakfire
, const char* path
,
847 const char* target
, const char* id
, int flags
,
848 pakfire_execute_logging_callback logging_callback
, void* data
) {
849 char makefiles
[PATH_MAX
];
855 // Check for valid input
861 // Try parsing the Build ID
863 r
= uuid_parse(id
, build_id
);
867 // Otherwise initialize the Build ID with something random
869 uuid_generate_random(build_id
);
872 // The default target is the local repository path
874 struct pakfire_repo
* repo
= pakfire_get_repo(pakfire
, "@local");
876 target
= pakfire_repo_get_path(repo
);
877 pakfire_repo_unref(repo
);
879 // If the repository could not be found, just write to the cwd
881 target
= getcwd(cwd
, sizeof(cwd
));
885 const char* packages
[] = {
889 // Install the package into the build environment
890 r
= pakfire_install(pakfire
, 0, packages
, NULL
, 0, NULL
);
892 ERROR(pakfire
, "Could not install %s\n", path
);
896 // Where are the makefiles located?
897 r
= pakfire_make_path(pakfire
, makefiles
, "/usr/src/packages/*/*.nm");
901 // Find all makefiles
902 r
= glob(makefiles
, 0, NULL
, &buffer
);
904 ERROR(pakfire
, "glob() on %s failed: %m\n", makefiles
);
909 // Iterate over all makefiles
910 for (unsigned int i
= 0; i
< buffer
.gl_pathc
; i
++) {
911 r
= pakfire_build_makefile(pakfire
, buffer
.gl_pathv
[i
], target
, &build_id
, flags
,
912 logging_callback
, data
);
914 ERROR(pakfire
, "Could not build %s: %m\n", buffer
.gl_pathv
[i
]);
924 PAKFIRE_EXPORT
int pakfire_shell(Pakfire pakfire
) {
925 const char* argv
[] = {
926 "/bin/bash", "--login", NULL
,
930 PAKFIRE_EXECUTE_INTERACTIVE
| PAKFIRE_EXECUTE_ENABLE_NETWORK
;
932 return pakfire_execute(pakfire
, argv
, NULL
, flags
, NULL
, NULL
);