garage/programs/carmel/carmel-build.c

248 lines
5.1 KiB
C

/*
* carmel-build
* Copyright (c) 2024 Jeremy Baxter.
*/
#define _DEFAULT_SOURCE
#include <sys/stat.h>
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iniparser.h>
#include "util.h"
typedef struct {
char *name;
char *description;
char *version;
char *builder;
} manifest;
static const char *buildBase = "/tmp/carmel-";
static const char *defaultOutputFile = "auto";
static const char *defaultPackageName = "void";
static const char *defaultPackageVersion = "0";
static const char *defaultPackageBuilder = "builder.sh";
static bool oflag, vflag;
#define WARNV(...) if (vflag) warnx(__VA_ARGS__)
void
builder(const char *script)
{
FILE *proc;
ssize_t ret;
int fd;
char ch[1];
proc = popen("sh", "w");
fd = open(script, O_RDONLY);
if (!proc)
err(1, "cannot execute shell");
if (fd == -1)
err(1, "cannot open %s", script);
while ((ret = read(fd, &ch, 1)) > 0) {
fwrite(ch, sizeof(char), 1, proc);
}
if (ret == -1)
err(1, "cannot read from %s", script);
pclose(proc);
}
#define KEY(name, def) strdup(iniparser_getstring(ini, (":" name), (def)))
manifest
makeManifest(const char *path)
{
dictionary *ini;
manifest m;
ini = iniparser_load(path);
m.name = KEY("name", defaultPackageName);
m.description = KEY("description", "");
m.version = KEY("version", defaultPackageVersion);
m.builder = KEY("builder", defaultPackageBuilder);
iniparser_freedict(ini);
return m;
}
#undef KEY
void
freeManifest(manifest *m)
{
free(m->name);
free(m->description);
free(m->version);
free(m->builder);
}
void
makeDirectory(const char *path)
{
if (mkdir(path, 0710) == -1)
err(1, "cannot create directory '%s'", path);
}
void
sh(const char *command)
{
int ret;
ret = system(command);
if (ret == -1)
err(1, "system");
if (WEXITSTATUS(ret) != 0)
errx(1, "command failed with exit code %d: %s", ret, command);
}
char *
workingDirectory(void)
{
char *buffer;
size_t len;
len = 64;
do {
buffer = malloc(len * sizeof(char));
len *= 2;
} while (getcwd(buffer, len) == NULL);
return buffer;
}
void
writeTo(const char *path, const char *contents)
{
int fd, i;
fd = open(path, O_WRONLY | O_CREAT, 0644);
for (i = 0; contents[i] != 0; i++)
write(fd, (char[]){contents[i]}, 1);
}
int
main(int argc, char **argv)
{
manifest m;
char *buildDirectory;
char *outputFile;
char *startDirectory;
size_t buildDirectoryLen, outputFileLen;
char ch;
outputFileLen = strlen(defaultOutputFile) + 1;
outputFile = malloc(outputFileLen * sizeof(char));
strbcpy(outputFile, defaultOutputFile, outputFileLen);
while ((ch = getopt(argc, argv, "C:o:v")) != -1) {
switch (ch) {
case 'C':
changeDirectory(optarg);
break;
case 'o':
oflag = 1;
outputFileLen = strlen(optarg) + 1;
outputFile = malloc(outputFileLen * sizeof(char));
strbcpy(outputFile, optarg, outputFileLen);
break;
case 'v':
vflag = 1;
break;
}
}
if (argc - optind != 1) {
fputs("usage: carmel-build [-v] [-C directory] [-o output] manifest\n", stderr);
return 1;
}
startDirectory = workingDirectory();
if (!exists(argv[optind]))
errx(1, "cannot open %s", argv[optind]);
m = makeManifest(argv[optind]);
if (strcmp(m.name, defaultPackageName) == 0)
warnx("manifest name not provided; using '%s'", m.name);
if (strcmp(m.version, defaultPackageVersion) == 0)
warnx("manifest version not provided; using '%s'", m.version);
if (strlen(m.builder) == 0)
errx(1, "manifest builder not provided; using '%s'", m.builder);
if (strcmp(outputFile, defaultOutputFile) == 0) {
char *extension = ".tar.gz";
outputFileLen = strlen(m.name) + 1 + strlen(m.version)
+ strlen(extension) + 1;
outputFile = malloc(outputFileLen * sizeof(char));
snprintf(outputFile, outputFileLen, "%s-%s%s",
m.name, m.version, extension);
}
WARNV("building %s", outputFile);
/* PIDs up to 7 digits */
buildDirectoryLen = strlen(buildBase) + 8;
buildDirectory = malloc(buildDirectoryLen * sizeof(char));
snprintf(buildDirectory, buildDirectoryLen, "%s%d",
buildBase, getpid());
WARNV("entering build origin");
makeDirectory(buildDirectory);
changeDirectory(buildDirectory);
makeDirectory("origin");
changeDirectory("origin");
{
char *buffer;
size_t len;
len = strlen(startDirectory) + 1 + strlen(m.builder) + 1;
buffer = malloc(len * sizeof(char));
snprintf(buffer, len, "%s/%s", startDirectory, m.builder);
copyFile(buffer, "builder.sh");
}
WARNV("executing builder");
setenv("out", buildDirectory, 1);
builder("builder.sh");
changeDirectory("..");
sh("rm -fr origin/");
WARNV("writing to carmel-meta");
makeDirectory("carmel-meta");
changeDirectory("carmel-meta");
writeTo("name", m.name);
writeTo("description", m.description);
writeTo("version", m.version);
WARNV("making tarball");
setenv("out", outputFile, 1);
setenv("start", startDirectory, 1);
setenv("build", buildDirectory, 1);
changeDirectory(buildDirectory);
sh("tar -czf \"$out\" *");
sh("mv \"$out\" \"$start\"/");
changeDirectory(startDirectory);
sh("rm -fr \"$build\"");
freeManifest(&m);
free(outputFile);
free(startDirectory);
return 0;
}