/* * Copyright (c) 2013-2016, 2020, 2022-2023 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * tix-build.c * Compile a source tix into a tix suitable for installation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" struct buildvar { const char* variable; const char* value; bool program; }; static struct buildvar buildvars[] = { { "AR", "ar", true }, { "AS", "as", true }, { "CC", "gcc", true }, { "CFLAGS", "-Os", false }, { "CPP", "gcc -E", true }, { "CPP", "", false }, { "CXXFILT", "c++filt", true }, { "CXX", "g++", true }, { "CXXFLAGS", "-Os", false }, { "LD", "ld", true }, { "LDFLAGS", "", false }, { "NM", "nm", true }, { "OBJCOPY", "objcopy", true }, { "OBJDUMP", "objdump", true }, { "PKG_CONFIG", "pkg-config", true }, { "RANLIB", "ranlib", true }, { "READELF", "readelf", true }, { "STRIP", "strip", true }, }; #define BUILDVARS_LENGTH (sizeof(buildvars) / sizeof(buildvars[0])) enum build_step { BUILD_STEP_NO_SUCH_STEP, BUILD_STEP_START, BUILD_STEP_PRE_CLEAN, BUILD_STEP_CONFIGURE, BUILD_STEP_BUILD, BUILD_STEP_INSTALL, BUILD_STEP_POST_INSTALL, BUILD_STEP_POST_CLEAN, BUILD_STEP_PACKAGE, BUILD_STEP_END, }; static bool should_do_build_step(enum build_step step, enum build_step start, enum build_step end) { return start <= step && step <= end; } #define SHOULD_DO_BUILD_STEP(step, minfo) \ should_do_build_step((step), (minfo)->start_step, (minfo)->end_step) static enum build_step step_of_step_name(const char* step_name) { if ( !strcmp(step_name, "start") ) return BUILD_STEP_START; if ( !strcmp(step_name, "clean") ) return BUILD_STEP_PRE_CLEAN; if ( !strcmp(step_name, "pre-clean") ) return BUILD_STEP_PRE_CLEAN; if ( !strcmp(step_name, "configure") ) return BUILD_STEP_CONFIGURE; if ( !strcmp(step_name, "build") ) return BUILD_STEP_BUILD; if ( !strcmp(step_name, "install") ) return BUILD_STEP_INSTALL; if ( !strcmp(step_name, "post-install") ) return BUILD_STEP_POST_INSTALL; if ( !strcmp(step_name, "post-clean") ) return BUILD_STEP_POST_CLEAN; if ( !strcmp(step_name, "package") ) return BUILD_STEP_PACKAGE; if ( !strcmp(step_name, "end") ) return BUILD_STEP_END; return BUILD_STEP_NO_SUCH_STEP; } struct metainfo { char* build; char* build_dir; char* destination; int generation; char* host; char* make; char* makeflags; char* package_dir; char* package_info_path; char* package_name; char* prefix; char* exec_prefix; char* sysroot; char* tar; char* target; char* tmp; string_array_t package_info; enum build_step start_step; enum build_step end_step; bool bootstrapping; bool cross; // TODO: After releasing Sortix 1.1, remove tixbuildinfo support. bool tixbuildinfo; }; static const char* metainfo_get_def(const struct metainfo* minfo, const char* key, const char* old_key, const char* def) { return dictionary_get_def((string_array_t*) &minfo->package_info, !minfo->tixbuildinfo ? key : old_key, def); } static const char* metainfo_get(const struct metainfo* minfo, const char* key, const char* old_key) { return metainfo_get_def(minfo, key, old_key, NULL); } static const char* metainfo_verify(struct metainfo* minfo, const char* key, const char* old_key) { const char* ret = metainfo_get(minfo, key, old_key); if ( !ret ) errx(1, "error: `%s': no `%s' variable declared", minfo->package_info_path, !minfo->tixbuildinfo ? key : old_key); return ret; } static bool has_in_path(const char* program) { pid_t child_pid = fork(); if ( child_pid < 0 ) err(1, "fork: which %s", program); if ( child_pid ) { int exitstatus; waitpid(child_pid, &exitstatus, 0); return WIFEXITED(exitstatus) && WEXITSTATUS(exitstatus) == 0; } close(0); close(1); close(2); if ( open("/dev/null", O_RDONLY) != 0 || open("/dev/null", O_WRONLY) != 1 || open("/dev/null", O_WRONLY) != 2 ) { warn("/dev/null"); _exit(1); } const char* argv[] = { "which", program, NULL }; execvp(argv[0], (char**) argv); _exit(1); } static void emit_compiler_wrapper_invocation(FILE* wrapper, struct metainfo* minfo, const char* name) { fprintf(wrapper, "%s", name); if ( minfo->sysroot ) fprintf(wrapper, " --sysroot=\"$SYSROOT\""); fprintf(wrapper, " \"$@\""); } static void emit_compiler_sysroot_wrapper(struct metainfo* minfo, const char* bindir, const char* name) { if ( !has_in_path(name) ) return; char* wrapper_path = join_paths(bindir, name); if ( !wrapper_path ) err(1, "malloc"); FILE* wrapper = fopen(wrapper_path, "w"); if ( !wrapper ) err(1, "`%s'", wrapper_path); fprintf(wrapper, "#!/bin/sh\n"); fprint_shell_variable_assignment(wrapper, "PATH", getenv("PATH")); if ( minfo->sysroot ) fprint_shell_variable_assignment(wrapper, "SYSROOT", minfo->sysroot); fprintf(wrapper, "exec "); emit_compiler_wrapper_invocation(wrapper, minfo, name); fprintf(wrapper, "\n"); fflush(wrapper); fchmod_plus_x(fileno(wrapper)); if ( ferror(wrapper) || fflush(wrapper) == EOF ) err(1, "%s", wrapper_path); fclose(wrapper); free(wrapper_path); } static void emit_compiler_sysroot_cross_wrapper(struct metainfo* minfo, const char* bindir, const char* name) { char* cross_name = print_string("%s-%s", minfo->host, name); if ( !cross_name ) err(1, "malloc"); emit_compiler_sysroot_wrapper(minfo, bindir, cross_name); free(cross_name); } static void emit_pkg_config_wrapper(struct metainfo* minfo, const char* bindir) { // Create a pkg-config script for the build system. char* pkg_config_for_build_path = join_paths(bindir, "pkg-config"); if ( !pkg_config_for_build_path ) err(1, "malloc"); FILE* pkg_config_for_build = fopen(pkg_config_for_build_path, "w"); if ( !pkg_config_for_build ) err(1, "`%s'", pkg_config_for_build_path); fprintf(pkg_config_for_build, "#!/bin/sh\n"); fprint_shell_variable_assignment(pkg_config_for_build, "PATH", getenv("PATH")); fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG", getenv("PKG_CONFIG")); fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_FOR_BUILD", getenv("PKG_CONFIG_FOR_BUILD")); if ( getenv("PKG_CONFIG_PATH_FOR_BUILD") ) fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_PATH", getenv("PKG_CONFIG_PATH_FOR_BUILD")); else fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_PATH", getenv("PKG_CONFIG_PATH")); if ( getenv("PKG_CONFIG_SYSROOT_DIR_FOR_BUILD") ) fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_SYSROOT_DIR", getenv("PKG_CONFIG_SYSROOT_DIR_FOR_BUILD")); else fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_SYSROOT_DIR", getenv("PKG_CONFIG_SYSROOT_DIR")); if ( getenv("PKG_CONFIG_LIBDIR_FOR_BUILD") ) fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_LIBDIR", getenv("PKG_CONFIG_LIBDIR_FOR_BUILD")); else fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_LIBDIR", getenv("PKG_CONFIG_LIBDIR")); fprintf(pkg_config_for_build, "exec ${PKG_CONFIG_FOR_BUILD:-${PKG_CONFIG:-pkg-config}} \"$@\"\n"); if ( ferror(pkg_config_for_build) || fflush(pkg_config_for_build) == EOF ) err(1, "%s", pkg_config_for_build_path); fchmod_plus_x(fileno(pkg_config_for_build)); fclose(pkg_config_for_build); free(pkg_config_for_build_path); // Create a pkg-config script for the host system. char* var_pkg_config_libdir = print_string("%s%s/lib/pkgconfig", minfo->sysroot ? minfo->sysroot : "", minfo->exec_prefix); if ( !var_pkg_config_libdir ) err(1, "malloc"); char* var_pkg_config_path = strdup(var_pkg_config_libdir); if ( !var_pkg_config_path ) err(1, "malloc"); char* pkg_config_name = print_string("%s-pkg-config", minfo->host); if ( !pkg_config_name ) err(1, "malloc"); char* pkg_config_path = join_paths(bindir, pkg_config_name); if ( !pkg_config_path ) err(1, "malloc"); FILE* pkg_config = fopen(pkg_config_path, "w"); if ( !pkg_config ) err(1, "`%s'", pkg_config_path); fprintf(pkg_config, "#!/bin/sh\n"); fprint_shell_variable_assignment(pkg_config, "PATH", getenv("PATH")); fprint_shell_variable_assignment(pkg_config, "PKG_CONFIG", getenv("PKG_CONFIG")); fprint_shell_variable_assignment(pkg_config, "PKG_CONFIG_PATH", var_pkg_config_path); fprint_shell_variable_assignment(pkg_config, "PKG_CONFIG_SYSROOT_DIR", minfo->sysroot); fprint_shell_variable_assignment(pkg_config, "PKG_CONFIG_LIBDIR", var_pkg_config_libdir); // Pass --static as Sortix only static links at the moment. fprintf(pkg_config, "exec ${PKG_CONFIG:-%s} --static \"$@\"\n", has_in_path(pkg_config_name) ? pkg_config_name : "pkg-config"); fflush(pkg_config); if ( ferror(pkg_config) || fflush(pkg_config) == EOF ) err(1, "%s", pkg_config_path); fchmod_plus_x(fileno(pkg_config)); fclose(pkg_config); free(pkg_config_path); free(var_pkg_config_libdir); free(var_pkg_config_path); } static void append_to_path(const char* directory) { const char* path = getenv("PATH"); if ( path && path[0] ) { char* new_path = print_string("%s:%s", directory, path); if ( !new_path ) err(1, "malloc"); if ( setenv("PATH", new_path, 1) < 0 ) err(1, "setenv"); free(new_path); } else if ( setenv("PATH", directory, 1) < 0 ) err(1, "setenv"); } static void EmitWrappers(struct metainfo* minfo) { if ( !minfo->cross ) return; char* bindir = join_paths(tmp_root, "bin"); if ( mkdir(bindir, 0777) < 0 ) err(1, "mkdir: %s", bindir); emit_pkg_config_wrapper(minfo, bindir); emit_compiler_sysroot_cross_wrapper(minfo, bindir, "cc"); emit_compiler_sysroot_cross_wrapper(minfo, bindir, "gcc"); emit_compiler_sysroot_cross_wrapper(minfo, bindir, "c++"); emit_compiler_sysroot_cross_wrapper(minfo, bindir, "g++"); emit_compiler_sysroot_cross_wrapper(minfo, bindir, "ld"); append_to_path(bindir); free(bindir); } static void SetNeedVariableBuildTool(struct metainfo* minfo, const char* variable, const char* value) { const char* needed_vars = metainfo_get_def(minfo, "MAKE_NEEDED_VARS", "pkg.make.needed-vars", "true"); char* key = minfo->tixbuildinfo ? print_string("pkg.make.needed-vars.%s", variable) : print_string("MAKE_NEEDED_VARS_%s", variable); if ( !key ) err(1, "malloc"); const char* needed_var = metainfo_get_def(minfo, key, key, needed_vars); free(key); if ( !parse_boolean(needed_var) ) return; if ( setenv(variable, value, 1) < 0 ) err(1, "setenv"); } static void SetNeedVariableCrossTool(struct metainfo* minfo, const char* variable, const char* value) { if ( !minfo->cross ) SetNeedVariableBuildTool(minfo, variable, value); else { char* newvalue = print_string("%s-%s", minfo->host, value); if ( !newvalue ) err(1, "malloc"); SetNeedVariableBuildTool(minfo, variable, newvalue); free(newvalue); } } static void SetNeededVariables(struct metainfo* minfo) { if ( minfo->bootstrapping ) { for ( size_t i = 0; i < BUILDVARS_LENGTH; i++ ) unsetenv(buildvars[i].variable); for ( size_t i = 0; i < BUILDVARS_LENGTH; i++ ) { char* for_build = print_string("%s_FOR_BUILD", buildvars[i].variable); if ( !for_build ) err(1, "malloc"); const char* value = getenv(for_build); if ( value && setenv(buildvars[i].variable, value, 1) < 0 ) err(1, "setenv"); free(for_build); } return; } for ( size_t i = 0; i < BUILDVARS_LENGTH; i++ ) { if ( !buildvars[i].program && !getenv(buildvars[i].variable) ) continue; char* for_build = print_string("%s_FOR_BUILD", buildvars[i].variable); if ( !for_build ) err(1, "malloc"); SetNeedVariableBuildTool(minfo, for_build, buildvars[i].value); free(for_build); } for ( size_t i = 0; i < BUILDVARS_LENGTH; i++ ) if ( buildvars[i].program ) SetNeedVariableCrossTool(minfo, buildvars[i].variable, buildvars[i].value); } static void Configure(struct metainfo* minfo) { if ( !fork_and_wait_or_recovery() ) return; const char* subdir = metainfo_get(minfo, "SUBDIR", "pkg.subdir"); const char* configure_raw = metainfo_get_def(minfo, "CONFIGURE", "pkg.configure.cmd", "./configure"); char* configure; if ( strcmp(minfo->build_dir, minfo->package_dir) == 0 ) configure = strdup(configure_raw); else configure = join_paths(minfo->package_dir, configure_raw); if ( !configure ) err(1, "malloc"); const char* conf_extra_args = metainfo_get_def(minfo, "CONFIGURE_ARGS", "pkg.configure.args", ""); const char* conf_extra_vars = metainfo_get_def(minfo, "CONFIGURE_VARS", "pkg.configure.vars", ""); bool with_sysroot = parse_boolean(metainfo_get_def(minfo, "CONFIGURE_WITH_SYSROOT", "pkg.configure.with-sysroot", "false")); // TODO: I am unclear if this issue still affects gcc, I might have // forgotten to set pkg.configure.with-sysroot-ld-bug=true there. const char* with_sysroot_ld_bug_default = "false"; if ( !strcmp(minfo->package_name, "gcc") ) with_sysroot_ld_bug_default = "true"; bool with_sysroot_ld_bug = parse_boolean(metainfo_get_def(minfo, "CONFIGURE_WITH_SYSROOT_LD_BUG", "pkg.configure.with-sysroot-ld-bug", with_sysroot_ld_bug_default )); bool with_build_sysroot = parse_boolean(metainfo_get_def(minfo, "CONFIGURE_WITH_BUILD_SYSROOT", "pkg.configure.with-build-sysroot", "false")); if ( chdir(minfo->build_dir) != 0 ) err(1, "chdir: `%s'", minfo->build_dir); if ( subdir && chdir(subdir) != 0 ) err(1, "chdir: `%s/%s'", minfo->build_dir, subdir); SetNeededVariables(minfo); string_array_t env_vars = string_array_make(); string_array_append_token_string(&env_vars, conf_extra_vars); for ( size_t i = 0; i < env_vars.length; i++ ) { char* key = env_vars.strings[i]; assert(key); char* assignment = strchr((char*) key, '='); if ( !assignment ) { if ( !strncmp(key, "unset ", strlen("unset ")) ) unsetenv(key + strlen("unset ")); continue; } *assignment = '\0'; char* value = assignment+1; setenv(key, value, 1); } const char* fixed_cmd_argv[] = { configure, print_string("--prefix=%s", minfo->prefix), print_string("--exec-prefix=%s", minfo->exec_prefix), print_string("--build=%s", minfo->build), minfo->bootstrapping ? NULL : print_string("--host=%s", minfo->host), print_string("--target=%s", minfo->target), NULL }; string_array_t args = string_array_make(); for ( size_t i = 0; fixed_cmd_argv[i]; i++ ) string_array_append(&args, fixed_cmd_argv[i]); if ( minfo->sysroot && with_build_sysroot ) { string_array_append(&args, print_string("--with-build-sysroot=%s", minfo->sysroot)); if ( minfo->sysroot && with_sysroot ) { // TODO: Binutils has a bug where the empty string means that // sysroot support is disabled and ld --sysroot won't work // so set it to / here for compatibility. // TODO: GCC has a bug where it doesn't use the // --with-build-sysroot value when --with-sysroot= when // locating standard library headers. if ( with_sysroot_ld_bug ) string_array_append(&args, "--with-sysroot=/"); else string_array_append(&args, "--with-sysroot="); } } else if ( minfo->sysroot && with_sysroot ) { string_array_append(&args, print_string("--with-sysroot=%s", minfo->sysroot)); } string_array_append_token_string(&args, conf_extra_args); string_array_append(&args, NULL); recovery_execvp(args.strings[0], (char* const*) args.strings); err(127, "`%s'", args.strings[0]); } static bool TestDirty(struct metainfo* minfo, const char* subdir, const char* candidate) { if ( !subdir ) subdir = "."; char* path; if ( asprintf(&path, "%s/%s/%s", minfo->build_dir, subdir, candidate) < 0 ) err(1, "asprintf"); bool result = access(path, F_OK) == 0; free(path); return result; } static bool IsDirty(struct metainfo* minfo) { const char* dirty_file = metainfo_get(minfo, "DIRTY_FILE", "pkg.dirty-file"); const char* subdir = metainfo_get(minfo, "SUBDIR", "pkg.subdir"); if ( dirty_file ) return TestDirty(minfo, subdir, dirty_file); return TestDirty(minfo, subdir, "config.log") || TestDirty(minfo, subdir, "Makefile") || TestDirty(minfo, subdir, "makefile"); } static void Make(struct metainfo* minfo, const char* make_target, const char* destdir, bool die_on_error, const char* subdir) { if ( !(die_on_error ? fork_and_wait_or_recovery() : fork_and_wait_or_death_def(false)) ) return; char* make = strdup(minfo->make); const char* override_make = metainfo_get(minfo, "MAKE", "pkg.make.cmd"); const char* make_extra_args = metainfo_get_def(minfo, "MAKE_ARGS", "pkg.make.args", ""); const char* make_extra_vars = metainfo_get_def(minfo, "MAKE_VARS", "pkg.make.vars", ""); if ( override_make ) { free(make); make = strdup(override_make); } SetNeededVariables(minfo); if ( chdir(minfo->build_dir) != 0 ) err(1, "chdir: `%s'", minfo->build_dir); if ( subdir && chdir(subdir) != 0 ) err(1, "chdir: `%s/%s'", minfo->build_dir, subdir); if ( !minfo->bootstrapping && destdir ) setenv("DESTDIR", destdir, 1); setenv("BUILD", minfo->build, 1); setenv("HOST", minfo->host, 1); setenv("TARGET", minfo->target, 1); if ( minfo->prefix ) setenv("PREFIX", minfo->prefix, 1); else unsetenv("PREFIX"); if ( minfo->exec_prefix ) setenv("EXEC_PREFIX", minfo->exec_prefix, 1); else unsetenv("EXEC_PREFIX"); if ( minfo->makeflags ) setenv("MAKEFLAGS", minfo->makeflags, 1); setenv("MAKE", minfo->make, 1); string_array_t env_vars = string_array_make(); string_array_append_token_string(&env_vars, make_extra_vars); for ( size_t i = 0; i < env_vars.length; i++ ) { char* key = env_vars.strings[i]; assert(key); char* assignment = strchr((char*) key, '='); if ( !assignment ) { if ( !strncmp(key, "unset ", strlen("unset ")) ) unsetenv(key + strlen("unset ")); continue; } *assignment = '\0'; char* value = assignment+1; setenv(key, value, 1); } const char* fixed_cmd_argv[] = { make, NULL }; string_array_t args = string_array_make(); for ( size_t i = 0; fixed_cmd_argv[i]; i++ ) string_array_append(&args, fixed_cmd_argv[i]); string_array_append_token_string(&args, make_target); string_array_append_token_string(&args, make_extra_args); if ( !die_on_error ) string_array_append(&args, "-k"); string_array_append(&args, NULL); if ( die_on_error ) recovery_execvp(args.strings[0], (char* const*) args.strings); else execvp(args.strings[0], (char* const*) args.strings); err(127, "`%s'", args.strings[0]); } static void Clean(struct metainfo* minfo) { const char* subdir = metainfo_get(minfo, "SUBDIR", "pkg.subdir"); const char* build_system = metainfo_get_def(minfo, "BUILD_SYSTEM", "pkg.build-system", "none"); const char* default_clean_target = !strcmp(build_system, "configure") ? "distclean" : "clean"; const char* clean_target = metainfo_get_def(minfo, "MAKE_CLEAN_TARGET", "pkg.make.clean-target", default_clean_target); const char* ignore_clean_failure_var = metainfo_get_def(minfo, "MAKE_IGNORE_CLEAN_FAILURE", "pkg.make.ignore-clean-failure", "true"); bool ignore_clean_failure = parse_boolean(ignore_clean_failure_var); Make(minfo, clean_target, NULL, !ignore_clean_failure, subdir); } static void Build(struct metainfo* minfo) { const char* subdir = metainfo_get(minfo, "SUBDIR", "pkg.subdir"); const char* build_target = metainfo_get_def(minfo, "MAKE_BUILD_TARGET", "pkg.make.build-target", "all"); Make(minfo, build_target, NULL, true, subdir); } static void CreateDestination(struct metainfo* minfo) { char* tardir_rel = join_paths(tmp_root, "tix"); if ( !tardir_rel ) err(1, "malloc"); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. const char* prefix = 3 <= minfo->generation ? minfo->prefix : ""; char* prefixdir_rel = print_string("%s%s", tardir_rel, prefix); if ( !prefixdir_rel ) err(1, "malloc"); if ( mkdir_p(prefixdir_rel, 0755) < 0 ) err(1, "mkdir: %s", prefixdir_rel); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. if ( minfo->generation == 2 ) { char* destdir_rel = join_paths(prefixdir_rel, "data"); char* tixdir_rel = join_paths(prefixdir_rel, "tix"); if ( mkdir(destdir_rel, 0755) != 0 ) err(1, "mkdir: `%s'", destdir_rel); if ( mkdir(tixdir_rel, 0755) != 0 ) err(1, "mkdir: `%s'", tixdir_rel); free(tixdir_rel); free(destdir_rel); } free(prefixdir_rel); free(tardir_rel); } static void Install(struct metainfo* minfo) { const char* subdir = metainfo_get(minfo, "SUBDIR", "pkg.subdir"); const char* install_target = metainfo_get_def(minfo, "MAKE_INSTALL_TARGET", "pkg.make.install-target", "install"); char* tardir_rel = join_paths(tmp_root, "tix"); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. char* destdir_rel = minfo->generation == 3 ? strdup(tardir_rel) : join_paths(tardir_rel, "data"); char* destdir = realpath(destdir_rel, NULL); if ( !destdir ) err(1, "realpath: %s", tardir_rel); Make(minfo, install_target, destdir, true, subdir); free(tardir_rel); free(destdir_rel); free(destdir); } static void PostInstall(struct metainfo* minfo) { const char* post_install_cmd = metainfo_get(minfo, "POST_INSTALL", "pkg.post-install.cmd"); if ( !post_install_cmd ) return; if ( !fork_and_wait_or_recovery() ) return; const char* subdir = metainfo_get(minfo, "SUBDIR", "pkg.subdir"); char* tardir_rel = join_paths(tmp_root, "tix"); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. char* destdir_rel = minfo->generation == 3 ? strdup(tardir_rel) : join_paths(tardir_rel, "data"); char* destdir = realpath(destdir_rel, NULL); if ( !destdir ) err(1, "realpath: %s", destdir_rel); SetNeededVariables(minfo); if ( chdir(minfo->package_dir) != 0 ) err(1, "chdir: `%s'", minfo->package_dir); if ( subdir && chdir(subdir) != 0 ) err(1, "chdir: `%s/%s'", minfo->build_dir, subdir); setenv("TIX_BUILD_DIR", minfo->build_dir, 1); setenv("TIX_SOURCE_DIR", minfo->package_dir, 1); setenv("TIX_INSTALL_DIR", destdir, 1); if ( minfo->sysroot ) setenv("TIX_SYSROOT", minfo->sysroot, 1); else unsetenv("TIX_SYSROOT"); setenv("BUILD", minfo->build, 1); setenv("HOST", minfo->host, 1); setenv("TARGET", minfo->target, 1); if ( minfo->prefix ) setenv("PREFIX", minfo->prefix, 1); else unsetenv("PREFIX"); if ( minfo->exec_prefix ) setenv("EXEC_PREFIX", minfo->exec_prefix, 1); else unsetenv("EXEC_PREFIX"); const char* cmd_argv[] = { post_install_cmd, NULL }; recovery_execvp(cmd_argv[0], (char* const*) cmd_argv); err(127, "%s", cmd_argv[0]); } static void TixInfo(struct metainfo* minfo) { char* tardir_rel = join_paths(tmp_root, "tix"); if ( !tardir_rel ) err(1, "malloc"); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. const char* prefix = 3 <= minfo->generation ? minfo->prefix : ""; char* prefixdir_rel = print_string("%s%s", tardir_rel, prefix); if ( !prefixdir_rel ) err(1, "malloc"); char* tixdir_rel = join_paths(prefixdir_rel, "tix"); if ( !tixdir_rel ) err(1, "malloc"); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. if ( 3 <= minfo->generation && mkdir(tixdir_rel, 0755) && errno != EEXIST ) err(1, "%s", tixdir_rel); char* tixinfodir_rel = join_paths(tixdir_rel, "tixinfo"); if ( !tixinfodir_rel ) err(1, "malloc"); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. if ( 3 <= minfo->generation && mkdir(tixinfodir_rel, 0755) && errno != EEXIST ) err(1, "%s", tixdir_rel); char* tixinfo_rel = 3 <= minfo->generation ? join_paths(tixinfodir_rel, minfo->package_name) : strdup(tixinfodir_rel); const char* alias = metainfo_get(minfo, "ALIAS_OF", "pkg.alias-of"); const char* runtime_deps = metainfo_get(minfo, "RUNTIME_DEPS", "pkg.runtime-deps"); bool location_independent = parse_boolean(metainfo_get_def(minfo, "LOCATION_INDEPENDENT", "pkg.location-independent", "false")); FILE* tixinfo_fp = fopen(tixinfo_rel, "w"); if ( !tixinfo_fp ) err(1, "`%s'", tixinfo_rel); if ( 3 <= minfo->generation ) { // TODO: Shell escape the values if needed. fwrite_variable(tixinfo_fp, "TIX_VERSION", "3"); fwrite_variable(tixinfo_fp, "NAME", minfo->package_name); const char* version = metainfo_get(minfo, "VERSION", "VERSION"); if ( version ) fwrite_variable(tixinfo_fp, "VERSION", version); const char* version_2 = metainfo_get(minfo, "VERSION_2", "VERSION_2"); if ( version_2 ) fwrite_variable(tixinfo_fp, "VERSION_2", version_2); fwrite_variable(tixinfo_fp, "PLATFORM", minfo->host); if ( alias ) fwrite_variable(tixinfo_fp, "ALIAS_OF", alias); else { if ( runtime_deps ) fwrite_variable(tixinfo_fp, "RUNTIME_DEPS", runtime_deps); if ( location_independent ) fwrite_variable(tixinfo_fp, "LOCATION_INDEPENDENT", "true"); else fwrite_variable(tixinfo_fp, "PREFIX", minfo->prefix); } } // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. else { fprintf(tixinfo_fp, "tix.version=1\n"); fprintf(tixinfo_fp, "tix.class=tix\n"); fprintf(tixinfo_fp, "tix.platform=%s\n", minfo->host); fprintf(tixinfo_fp, "pkg.name=%s\n", minfo->package_name); if ( alias ) fprintf(tixinfo_fp, "pkg.alias-of=%s\n", alias); else { if ( runtime_deps ) fprintf(tixinfo_fp, "pkg.runtime-deps=%s\n", runtime_deps); if ( location_independent ) fprintf(tixinfo_fp, "pkg.location-independent=true\n"); else fprintf(tixinfo_fp, "pkg.prefix=%s\n", minfo->prefix); } } if ( ferror(tixinfo_fp) || fflush(tixinfo_fp) == EOF ) err(1, "write: `%s'", tixinfo_rel); fclose(tixinfo_fp); free(tardir_rel); free(prefixdir_rel); free(tixdir_rel); free(tixinfodir_rel); free(tixinfo_rel); } static void TixManifest(struct metainfo* minfo) { if ( !fork_and_wait_or_recovery() ) return; char* tardir_rel = join_paths(tmp_root, "tix"); if ( !tardir_rel ) err(1, "malloc"); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. const char* prefix = 3 <= minfo->generation ? minfo->prefix : ""; char* prefixdir_rel = print_string("%s%s", tardir_rel, prefix); if ( !prefixdir_rel ) err(1, "malloc"); if ( chdir(prefixdir_rel) < 0 ) err(1, "%s", prefixdir_rel); if ( mkdir("tix", 0755) && errno != EEXIST ) err(1, "%s", "tix"); if ( mkdir("tix/manifest", 0755) && errno != EEXIST ) err(1, "%s", "tix/manifest"); char* command; if ( asprintf(&command, "find . -name tix -prune -o -print | " "sed -E -e 's,^\\.$,/,' -e 's,^\\./,/,' | " "LC_ALL=C sort > tix/manifest/%s", minfo->package_name) < 0 ) err(1, "malloc"); const char* cmd_argv[] = { "sh", "-c", command, NULL }; recovery_execvp(cmd_argv[0], (char* const*) cmd_argv); err(127, "%s", cmd_argv[0]); } static void Package(struct metainfo* minfo) { if ( !fork_and_wait_or_recovery() ) return; char* tardir_rel = join_paths(tmp_root, "tix"); if ( !tardir_rel ) err(1, "malloc"); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. const char* prefix = 3 <= minfo->generation ? minfo->prefix : ""; char* prefixdir_rel = print_string("%s%s", tardir_rel, prefix); if ( !prefixdir_rel ) err(1, "malloc"); char* package_tix = print_string("%s/%s.tix.tar.xz", minfo->destination, minfo->package_name); if ( !package_tix ) err(1, "malloc"); printf("Creating `%s'...\n", package_tix); fflush(stdout); const char* cmd_argv[] = { minfo->tar, "-C", prefixdir_rel, "--remove-files", "--create", "--xz", "--numeric-owner", "--owner=0", "--group=0", "--file", package_tix, "--", "tix", NULL }; string_array_t cmd = string_array_make(); for ( size_t i = 0; cmd_argv[i]; i++ ) if ( !string_array_append(&cmd, cmd_argv[i]) ) err(1, "malloc"); struct dirent** entries; int count = scandir(prefixdir_rel, &entries, NULL, alphasort); if ( count < 0 ) err(1, "scandir: %s", prefixdir_rel); for ( int i = 0; i < count; i++ ) { const char* name = entries[i]->d_name; if ( !strcmp(name, ".") || !strcmp(name, "..") || !strcmp(name, "tix") ) continue; if ( !string_array_append(&cmd, name) ) err(1, "malloc"); } if ( !string_array_append(&cmd, NULL) ) err(1, "malloc"); recovery_execvp(cmd.strings[0], (char* const*) cmd.strings); err(127, "%s", cmd.strings[0]); } static void Compile(struct metainfo* minfo) { // Detect which build system we are interfacing with. const char* build_system = metainfo_get(minfo, "BUILD_SYSTEM", "pkg.build-system"); if ( !build_system ) errx(1, "%s: pkg.build-system was not found", minfo->package_info_path); // Determine whether need to do an out-of-directory build. const char* use_build_dir_var = metainfo_get_def(minfo, "CONFIGURE_USE_BUILD_DIRECTORY", "pkg.configure.use-build-directory", "false"); bool use_build_dir = parse_boolean(use_build_dir_var); if ( use_build_dir ) { const char* build_rel = minfo->bootstrapping ? "build-bootstrap" : "build"; minfo->build_dir = join_paths(tmp_root, build_rel); if ( !minfo->build_dir ) err(1, "malloc"); if ( mkdir(minfo->build_dir, 0777) < 0 ) err(1, "mkdir %s", minfo->build_dir); } else minfo->build_dir = strdup(minfo->package_dir); // Reset the build directory if needed. if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_PRE_CLEAN, minfo) && !use_build_dir && IsDirty(minfo) ) Clean(minfo); // Configure the build directory if needed. if ( strcmp(build_system, "configure") == 0 && SHOULD_DO_BUILD_STEP(BUILD_STEP_CONFIGURE, minfo) ) Configure(minfo); if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_BUILD, minfo) ) Build(minfo); if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_INSTALL, minfo) ) Install(minfo); if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_POST_INSTALL, minfo) ) PostInstall(minfo); // Clean the build directory after the successful build. if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_POST_CLEAN, minfo) ) Clean(minfo); } static void Bootstrap(struct metainfo* minfo) { struct metainfo newinfo = { 0 }; newinfo.build = minfo->build; newinfo.build_dir = NULL; newinfo.destination = NULL; newinfo.generation = minfo->generation; newinfo.host = minfo->build; newinfo.make = minfo->make; newinfo.makeflags = minfo->makeflags; newinfo.package_dir = minfo->package_dir; newinfo.package_info_path = minfo->package_info_path; newinfo.package_name = minfo->package_name; newinfo.prefix = join_paths(tmp_root, "bootstrap"); if ( !newinfo.prefix ) err(1, "malloc"); if ( mkdir(newinfo.prefix, 0777) < 0 ) err(1, "mkdir: %s", newinfo.prefix); newinfo.exec_prefix = newinfo.prefix; newinfo.sysroot = NULL; newinfo.tar = minfo->tar; newinfo.target = minfo->host; newinfo.tmp = minfo->tmp; const char* bootstrap_prefix = minfo->tixbuildinfo ? "bootstrap." : "BOOTSTRAP_"; for ( size_t i = 0; i < minfo->package_info.length; i++ ) { const char* string = minfo->package_info.strings[i]; if ( minfo->tixbuildinfo ? !strncmp(string, "pkg.", strlen("pkg.")) : strncmp(string, bootstrap_prefix, strlen(bootstrap_prefix)) != 0 ) continue; if ( !strncmp(string, bootstrap_prefix, strlen(bootstrap_prefix)) ) { const char* rest = string + strlen(bootstrap_prefix); char* newstring = minfo->tixbuildinfo ? print_string("pkg.%s", rest) : strdup(rest); if ( !newstring ) err(1, "malloc"); if ( !string_array_append(&newinfo.package_info, newstring) ) err(1, "malloc"); free(newstring); } else { if ( !string_array_append(&newinfo.package_info, string) ) err(1, "malloc"); } } newinfo.start_step = BUILD_STEP_PRE_CLEAN; newinfo.end_step = BUILD_STEP_POST_CLEAN; newinfo.bootstrapping = true; newinfo.cross = false; Compile(&newinfo); char* bindir = join_paths(newinfo.prefix, "bin"); if ( !bindir ) err(1, "malloc"); if ( access(bindir, F_OK) == 0 ) append_to_path(bindir); free(bindir); char* sbindir = join_paths(newinfo.prefix, "sbin"); if ( !sbindir ) err(1, "malloc"); if ( access(sbindir, F_OK) == 0 ) append_to_path(sbindir); free(sbindir); string_array_reset(&newinfo.package_info); free(newinfo.prefix); } static void BuildPackage(struct metainfo* minfo) { // Whether this is just an alias for another package. const char* alias = metainfo_get(minfo, "ALIAS_OF", "pkg.alias-of"); // Determine if the package is location independent. bool location_independent = parse_boolean(metainfo_get_def(minfo, "LOCATION_INDEPENDENT", "pkg.location-independent", "false")); if ( !alias && !location_independent && !minfo->prefix ) errx(1, "error: %s is not location independent and you need to " "specify the intended destination prefix using --prefix", minfo->package_name); CreateDestination(minfo); // Possibly build a native version of the package to aid cross-compilation. // This is an anti-feature needed for broken packages that don't properly // handle this case entirely themselves. There's a few packages that need // the exact same version around natively in order to cross-compile. const char* use_bootstrap_var = metainfo_get_def(minfo, "USE_BOOTSTRAP", "pkg.use-bootstrap", "false"); bool use_bootstrap = parse_boolean(use_bootstrap_var); if ( !alias && use_bootstrap && strcmp(minfo->build, minfo->host) != 0 && SHOULD_DO_BUILD_STEP(BUILD_STEP_CONFIGURE, minfo) ) Bootstrap(minfo); EmitWrappers(minfo); if ( !alias ) Compile(minfo); if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_PACKAGE, minfo) ) { TixInfo(minfo); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. if ( 3 <= minfo->generation ) TixManifest(minfo); Package(minfo); } } static void VerifySourceTixInformation(struct metainfo* minfo) { if ( minfo->tixbuildinfo ) { const char* pipath = minfo->package_info_path; string_array_t* pinfo = &minfo->package_info; const char* tix_version = VerifyInfoVariable(pinfo, "tix.version", pipath); if ( atoi(tix_version) != 1 ) errx(1, "error: `%s': tix version `%s' not supported", pipath, tix_version); const char* tix_class = VerifyInfoVariable(pinfo, "tix.class", pipath); if ( !strcmp(tix_class, "tix") ) errx(1, "error: `%s': this object is a binary tix and is already " "compiled.\n", pipath); if ( strcmp(tix_class, "srctix") ) errx(1, "error: `%s': tix class `%s' is not `srctix': this object " "is not suitable for compilation.", pipath, tix_class); } metainfo_verify(minfo, "NAME", "pkg.name"); if ( !metainfo_get(minfo, "ALIAS_OF", "pkg.alias-of") ) metainfo_verify(minfo, "BUILD_SYSTEM", "pkg.build-system"); } // TODO: The MAKEFLAGS variable is actually not in the same format as the token // string language. It appears that GNU make doesn't escape " characters, // but instead consider them normal characters. This should work as // expected, though, as long as the MAKEFLAGS variable doesn't contain any // quote characters. static void PurifyMakeflags(void) { const char* makeflags_environment = getenv("MAKEFLAGS"); if ( !makeflags_environment ) return; string_array_t makeflags = string_array_make(); string_array_append_token_string(&makeflags, makeflags_environment); // Discard all the environmental variables in MAKEFLAGS. for ( size_t i = 0; i < makeflags.length; i++ ) { char* flag = makeflags.strings[i]; assert(flag); if ( flag[0] == '-' ) continue; if ( !strchr(flag, '=') ) continue; free(flag); for ( size_t n = i + 1; n < makeflags.length; n++ ) makeflags.strings[n-1] = makeflags.strings[n]; makeflags.length--; i--; } char* new_makeflags_environment = token_string_of_string_array(&makeflags); assert(new_makeflags_environment); setenv("MAKEFLAGS", new_makeflags_environment, 1); free(new_makeflags_environment); string_array_reset(&makeflags); } static char* FindPortFile(char* package_dir) { char* path = print_string("%s.port", package_dir); if ( !path ) err(1, "malloc"); if ( !access(path, F_OK) ) return path; free(path); path = join_paths(package_dir, "tix.port"); if ( !path ) err(1, "malloc"); if ( !access(path, F_OK) ) return path; free(path); return NULL; } static char* FindTixBuildInfo(char* package_dir) { char* path = join_paths(package_dir, "tixbuildinfo"); if ( !path ) err(1, "malloc"); if ( !access(path, F_OK) ) return path; free(path); return NULL; } int main(int argc, char* argv[]) { PurifyMakeflags(); struct metainfo minfo; memset(&minfo, 0, sizeof(minfo)); minfo.build = NULL; minfo.destination = strdup("."); minfo.host = NULL; char* generation_string = strdup(DEFAULT_GENERATION); minfo.makeflags = strdup_null(getenv_def("MAKEFLAGS", NULL)); minfo.make = strdup(getenv_def("MAKE", "make")); minfo.prefix = strdup(""); minfo.exec_prefix = NULL; minfo.sysroot = NULL; minfo.target = NULL; minfo.tar = strdup("tar"); char* tmp = strdup(getenv_def("TMPDIR", "/tmp")); char* start_step_string = strdup("start"); char* end_step_string = strdup("end"); char* source_port = NULL; const char* argv0 = argv[0]; for ( int i = 0; i < argc; i++ ) { const char* arg = argv[i]; if ( arg[0] != '-' || !arg[1] ) continue; argv[i] = NULL; if ( !strcmp(arg, "--") ) break; if ( arg[1] != '-' ) { char c; while ( (c = *++arg) ) switch ( c ) { default: errx(1, "unknown option -- '%c'", c); } } else if ( GET_OPTION_VARIABLE("--build", &minfo.build) ) { } else if ( GET_OPTION_VARIABLE("--destination", &minfo.destination) ) { } else if ( GET_OPTION_VARIABLE("--end", &end_step_string) ) { } else if ( GET_OPTION_VARIABLE("--exec-prefix", &minfo.exec_prefix) ) { } else if ( GET_OPTION_VARIABLE("--generation", &generation_string) ) { } else if ( GET_OPTION_VARIABLE("--host", &minfo.host) ) { } else if ( GET_OPTION_VARIABLE("--make", &minfo.make) ) { } else if ( GET_OPTION_VARIABLE("--makeflags", &minfo.makeflags) ) { } else if ( GET_OPTION_VARIABLE("--prefix", &minfo.prefix) ) { } // TODO: After releasing Sortix 1.1, remove this option. else if ( GET_OPTION_VARIABLE("--source-package", &source_port) ) { } else if ( GET_OPTION_VARIABLE("--source-port", &source_port) ) { } else if ( GET_OPTION_VARIABLE("--start", &start_step_string) ) { } else if ( GET_OPTION_VARIABLE("--sysroot", &minfo.sysroot) ) { } else if ( GET_OPTION_VARIABLE("--tar", &minfo.tar) ) { } else if ( GET_OPTION_VARIABLE("--target", &minfo.target) ) { } else if ( GET_OPTION_VARIABLE("--tmp", &minfo.tmp) ) { } else errx(1, "unknown option: %s", arg); } minfo.generation = atoi(generation_string); free(generation_string); // TODO: After releasing Sortix 1.1, remove generation 2 compatibility. if ( minfo.generation != 2 && minfo.generation != 3 ) errx(1, "Unsupported generation: %i", minfo.generation); if ( !(minfo.start_step = step_of_step_name(start_step_string)) ) { fprintf(stderr, "%s: no such step `%s'\n", argv0, start_step_string); exit(1); } if ( !(minfo.end_step = step_of_step_name(end_step_string)) ) { fprintf(stderr, "%s: no such step `%s'\n", argv0, end_step_string); exit(1); } compact_arguments(&argc, &argv); if ( minfo.prefix && !strcmp(minfo.prefix, "/") ) minfo.prefix[0] = '\0'; if ( argc < 2 ) { fprintf(stderr, "%s: no package specified\n", argv0); exit(1); } if ( 2 < argc ) { fprintf(stderr, "%s: unexpected extra operand `%s'\n", argv0, argv[2]); exit(1); } initialize_tmp(tmp, "tixbuild"); const char* srctix = argv[1]; minfo.package_dir = realpath(srctix, NULL); if ( !minfo.package_dir ) err(1, "%s", srctix); if ( minfo.build && !minfo.build[0] ) free(minfo.build), minfo.build = NULL; if ( minfo.host && !minfo.host[0] ) free(minfo.host), minfo.host = NULL; if ( minfo.target && !minfo.target[0] ) free(minfo.target), minfo.target = NULL; if ( !minfo.build && !(minfo.build = GetBuildTriplet()) ) err(1, "unable to determine build, use --build"); if ( !minfo.host ) minfo.host = strdup(minfo.build); if ( !minfo.target ) minfo.target = strdup(minfo.host); minfo.cross = strcmp(minfo.build, minfo.host) != 0 || minfo.sysroot; if ( minfo.prefix && !minfo.exec_prefix ) minfo.exec_prefix = strdup(minfo.prefix); if ( !IsDirectory(minfo.package_dir) ) err(1, "`%s'", minfo.package_dir); if ( (minfo.package_info_path = FindPortFile(minfo.package_dir)) ) { minfo.tixbuildinfo = false; minfo.package_info = string_array_make(); string_array_t* package_info = &minfo.package_info; int ret = variables_append_file_path(package_info, minfo.package_info_path); if ( ret == -1 ) err(1, "`%s'", minfo.package_info_path); else if ( ret == -2 ) errx(1, "`%s': Syntax error", minfo.package_info_path); } else if ( (minfo.package_info_path = FindTixBuildInfo(minfo.package_dir)) ) { minfo.tixbuildinfo = true; minfo.package_info = string_array_make(); string_array_t* package_info = &minfo.package_info; if ( variables_append_file_path(package_info, minfo.package_info_path) < 0 ) err(1, "`%s'", minfo.package_info_path); } else err(1, "Failed to find: %s.port or %s/tix.port or %s/tixbuildinfo", minfo.package_dir, minfo.package_dir, minfo.package_dir); VerifySourceTixInformation(&minfo); minfo.package_name = strdup(metainfo_get(&minfo, "NAME", "pkg.name")); const char* pkg_source_port = metainfo_get(&minfo, "SOURCE_PORT", "pkg.source-package"); if ( pkg_source_port && !source_port ) { // TODO: Change this default location to match tix-port(8)? source_port = print_string("%s/../%s", srctix, pkg_source_port); if ( !source_port ) err(1, "malloc"); } if ( source_port ) { free(minfo.package_dir); minfo.package_dir = realpath(source_port, NULL); if ( !minfo.package_dir ) err(1, "%s: looking for source port: %s", srctix, source_port); } BuildPackage(&minfo); return 0; }