/*
This file is part of Darling.
Copyright (C) 2017 Lubos Dolezel
Darling is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Darling is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Darling. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "xcselect.h"
static char path_buffer[1024];
static const char* get_developer_dir_from_file(const char* file)
{
int fd = open(file, O_RDONLY);
if (fd == -1)
return NULL;
int len = read(fd, path_buffer, sizeof(path_buffer)-1);
if (len <= 0)
{
close(fd);
return NULL;
}
path_buffer[len] = 0;
close(fd);
return path_buffer;
}
const char* get_developer_dir_from_symlink(const char* link)
{
ssize_t len;
len = readlink(link, path_buffer, sizeof(path_buffer)-1);
if (len <= 0)
return NULL;
path_buffer[len] = 0;
return path_buffer;
}
static bool dir_exists(const char* dir)
{
struct stat st;
if (stat(dir, &st) != 0)
return false;
return S_ISDIR(st.st_mode);
}
static bool valid_dev_path(const char* path)
{
char buffer[1024];
size_t length;
strcpy(buffer, path);
strcat(buffer, "/");
length = strlen(buffer);
strcat(buffer, "usr/lib/libxcrun.dylib");
if (access(buffer, F_OK) == 0)
return true;
buffer[length] = 0;
strcat(buffer, "usr/bin/xcrun");
if (access(buffer, F_OK) == 0)
return true;
return false;
}
bool xcselect_find_developer_contents_from_path(const char* p, char* dst, bool* is_cmd_line, size_t dst_size)
{
size_t length;
if (*p != '/')
{
getcwd(dst, dst_size);
strcat(dst, "/");
strcat(dst, p);
}
else
strlcpy(dst, p, dst_size);
length = strlen(dst);
if (valid_dev_path(dst))
return true;
dst[length++] = '/';
dst[length] = 0;
strcat(dst, "Library/Developer/CommandLineTools");
if (valid_dev_path(dst))
{
*is_cmd_line = true;
return true;
}
dst[length] = 0;
strcat(dst, "CommandLineTools");
if (valid_dev_path(dst))
{
*is_cmd_line = true;
return true;
}
dst[length] = 0;
strcat(dst, "Contents/Developer");
if (valid_dev_path(dst))
return true;
return false;
}
bool xcselect_get_developer_dir_path(char* path, size_t path_len, bool* is_cmd_line)
{
const char* p;
char* slash;
*is_cmd_line = false;
p = getenv("DEVELOPER_DIR");
if (p)
{
if (xcselect_find_developer_contents_from_path(p, path_buffer, is_cmd_line, sizeof(path_buffer)))
{
p = path_buffer;
goto have_path;
}
}
p = get_developer_dir_from_symlink("/var/db/xcode_select_link");
if (p)
goto have_path;
p = get_developer_dir_from_symlink("/usr/share/xcode-select/xcode_dir_link");
if (p)
goto have_path;
p = get_developer_dir_from_file("/usr/share/xcode-select/xcode_dir_path");
if (p)
goto have_path;
if (dir_exists("/Applications/Xcode.app"))
{
p = "/Applications/Xcode.app/Contents/Developer";
goto have_path;
}
else if (dir_exists("/Library/Developer/CommandLineTools"))
{
p = "/Library/Developer/CommandLineTools";
goto have_path;
}
else if (dir_exists("/Library/Developer/DarlingCLT"))
{
p = "/Library/Developer/DarlingCLT";
*is_cmd_line = true;
goto have_path;
}
return false;
have_path:
slash = strrchr(p, '/');
if (slash != NULL)
{
if (strcmp(slash+1, "CommandLineTools") == 0)
*is_cmd_line = true;
}
strlcpy(path, p, path_len);
strlcat(path, "/", path_len);
return true;
}
int xcselect_invoke_xcrun(const char* tool, int argc, char* argv[], int flags)
{
char dev_dir[1024];
bool is_cmdline;
if (xcselect_get_developer_dir_path(dev_dir, sizeof(dev_dir), &is_cmdline))
{
char* buf = (char*) malloc(2048);
size_t length;
void* lib;
if (is_cmdline && (flags & XCSELECT_FLAG_REQUIRE_XCODE))
{
fprintf(stderr, "xcrun: tool '%s' requires Xcode installation, command line tools are insufficient.\n", tool);
exit(1);
}
strcpy(buf, dev_dir);
if (buf[strlen(buf) - 1] != '/')
{
strcat(buf, "/");
}
length = strlen(buf);
strcat(buf, "usr/lib/libxcrun.dylib");
lib = dlopen(buf, RTLD_LAZY);
if (lib != NULL)
{
void(*fn)(const char*, int, char**, const char*);
*((void**)&fn) = dlsym(lib, "xcrun_main");
if (!fn)
{
fprintf(stderr, "xcrun: broken libxcrun.dylib at '%s'\n", buf);
}
else
{
fn(tool, argc, argv, dev_dir);
fprintf(stderr, "xcrun: xcrun_main unexpectedly exited\n");
}
}
else
{
char** argv2;
int j = 0;
buf[length] = 0;
strcat(buf, "usr/bin/xcrun");
// +3: buf, tool, NULL
argv2 = (char**) __builtin_alloca((argc+3) * sizeof(char*));
argv2[j++] = buf;
if (tool != NULL)
argv2[j++] = (char*) tool;
for (int i = 0; i < argc; i++, j++)
argv2[j] = argv[i];
argv2[j] = NULL;
execv(buf, argv2);
fprintf(stderr, "xcrun: developer path '%s' is invalid, failed to execute '%s': %s\n",
dev_dir, buf, strerror(errno));
}
}
else
{
if (dir_exists("/usr/libexec/DeveloperTools") && tool != NULL)
{
char* buf = __builtin_alloca(strlen(tool) + 30);
strcpy(buf, "/usr/libexec/DeveloperTools/");
strcat(buf, tool);
if (access(buf, F_OK) == 0)
{
char** argv2 = (char **) __builtin_alloca((argc+1+1) * sizeof(char*));
argv2[0] = buf;
for (int i = 0; i < argc; i++)
argv2[i+1] = argv[i];
argv2[argc+1] = NULL;
execv(buf, argv2);
fprintf(stderr, "xcrun: failed to exec '%s': %s\n", buf, strerror(errno));
exit(1);
}
}
fprintf(stderr, "xcrun: cannot find developer tools, set DEVELOPER_DIR if you are using a non-standard location.\n");
}
exit(1);
}
struct __xcselect_manpaths
{
unsigned int count;
char** paths;
};
void xcselect_manpaths_append(xcselect_manpaths* paths, const char* path)
{
paths->count++;
paths->paths = realloc(paths->paths, sizeof(char*) * paths->count);
paths->paths[paths->count - 1] = strdup(path);
}
xcselect_manpaths* xcselect_get_manpaths(const char* sdkname)
{
char path[1024];
bool unused;
size_t len;
xcselect_manpaths* rv;
if (!xcselect_get_developer_dir_path(path, sizeof(path), &unused))
return NULL;
len = strlen(path);
strcat(path, "usr/lib/libxcrun.dylib");
rv = (xcselect_manpaths*) malloc(sizeof(*rv));
memset(rv, 0, sizeof(*rv));
if (access(path, F_OK) == 0)
{
void* module = dlopen(path, RTLD_LAZY);
void (*fn)(const char* devpath, const char* sdkname, void (^)(const char*));
if (!module)
{
free(rv);
fprintf(stderr, "%s: error: cannot load libxcrun (%s)\n", getprogname(), dlerror());
return NULL;
}
*((void**)&fn) = dlsym(module, "xcrun_iter_manpaths");
if (fn != NULL)
{
path[len] = 0;
fn(path, sdkname, ^(const char* path) {
xcselect_manpaths_append(rv, path);
});
}
dlclose(module);
}
// Add standard paths
path[len] = 0;
strcat(path, "usr/share/man");
xcselect_manpaths_append(rv, path);
path[len] = 0;
strcat(path, "usr/llvm-gcc-4,2/share/man");
xcselect_manpaths_append(rv, path);
path[len] = 0;
strcat(path, "Toolchains/XcodeDefault.xctoolchain/usr/share/man");
xcselect_manpaths_append(rv, path);
return rv;
}
unsigned int xcselect_manpaths_get_num_paths(xcselect_manpaths* p)
{
return p->count;
}
const char* xcselect_manpaths_get_path(xcselect_manpaths* p, unsigned int idx)
{
if (idx < xcselect_manpaths_get_num_paths(p))
return p->paths[idx];
else
return NULL;
}
void xcselect_manpaths_free(xcselect_manpaths* p)
{
for (unsigned int i = 0; i < p->count; i++)
free(p->paths[i]);
free(p->paths);
free(p);
}