mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-06 02:45:25 +03:00
293 lines
9.7 KiB
D
293 lines
9.7 KiB
D
module ldcBuildRuntime;
|
|
|
|
import core.stdc.stdlib : exit;
|
|
import std.file;
|
|
import std.path;
|
|
import std.stdio;
|
|
|
|
struct Config {
|
|
string ldcExecutable;
|
|
string buildDir;
|
|
bool resetBuildDir;
|
|
bool resetOnly;
|
|
string ldcSourceDir;
|
|
bool ninja;
|
|
bool buildTestrunners;
|
|
string targetPreset;
|
|
string[] targetSystem;
|
|
string[] dFlags;
|
|
string[] cFlags;
|
|
string[] linkerFlags;
|
|
uint numBuildJobs;
|
|
string[string] cmakeVars;
|
|
}
|
|
|
|
string defaultLdcExecutable;
|
|
Config config;
|
|
|
|
int main(string[] args) {
|
|
version(Windows)
|
|
enum exeName = "ldc2.exe";
|
|
else
|
|
enum exeName = "ldc2";
|
|
defaultLdcExecutable = buildPath(thisExePath.dirName, exeName);
|
|
|
|
parseCommandLine(args);
|
|
|
|
findLdcExecutable();
|
|
prepareBuildDir();
|
|
|
|
if (config.resetOnly) {
|
|
writefln("Runtime libraries build directory successfully reset (%s)", config.buildDir);
|
|
return 0;
|
|
}
|
|
|
|
prepareLdcSource();
|
|
runCMake();
|
|
build();
|
|
|
|
writefln("Runtime libraries built successfully into: %s", config.buildDir);
|
|
return 0;
|
|
}
|
|
|
|
void findLdcExecutable() {
|
|
if (config.ldcExecutable !is null) {
|
|
if (!config.ldcExecutable.exists) {
|
|
writefln("Error: LDC executable not found: %s", config.ldcExecutable);
|
|
exit(1);
|
|
}
|
|
config.ldcExecutable = config.ldcExecutable.absolutePath;
|
|
return;
|
|
}
|
|
|
|
if (defaultLdcExecutable.exists) {
|
|
config.ldcExecutable = defaultLdcExecutable;
|
|
return;
|
|
}
|
|
|
|
writeln("Please specify LDC executable via '--ldc=<path/to/ldc2[.exe]>'. Aborting.");
|
|
exit(1);
|
|
}
|
|
|
|
void prepareBuildDir() {
|
|
if (config.buildDir is null)
|
|
config.buildDir = "ldc-build-runtime.tmp";
|
|
|
|
if (config.buildDir.exists) {
|
|
if (!config.resetBuildDir) {
|
|
writefln("Warning: build directory already exists: %s", config.buildDir);
|
|
} else {
|
|
writefln("Resetting build directory: %s", config.buildDir);
|
|
import std.array : array;
|
|
auto items = dirEntries(config.buildDir, SpanMode.shallow, false).array;
|
|
const ldcSrc = buildPath(config.buildDir, "ldc-src");
|
|
foreach (i; items) {
|
|
if (i.isFile) {
|
|
remove(i.name);
|
|
} else if (i.isDir && i.name != ldcSrc) {
|
|
rmdirRecurse(i.name);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
writefln("Creating build directory: %s", config.buildDir);
|
|
mkdirRecurse(config.buildDir);
|
|
}
|
|
|
|
config.buildDir = config.buildDir.absolutePath;
|
|
}
|
|
|
|
void prepareLdcSource() {
|
|
if (config.ldcSourceDir !is null) {
|
|
if (!config.ldcSourceDir.exists) {
|
|
writefln("Error: LDC source directory not found: %s", config.ldcSourceDir);
|
|
exit(1);
|
|
}
|
|
config.ldcSourceDir = config.ldcSourceDir.absolutePath;
|
|
return;
|
|
}
|
|
|
|
const ldcSrc = "ldc-src";
|
|
config.ldcSourceDir = buildPath(config.buildDir, ldcSrc);
|
|
if (buildPath(config.ldcSourceDir, "runtime").exists)
|
|
return;
|
|
|
|
// Download & extract LDC source archive if <buildDir>/ldc-src/runtime doesn't exist yet.
|
|
|
|
const wd = WorkingDirScope(config.buildDir);
|
|
|
|
auto ldcVersion = "@LDC_VERSION@";
|
|
void removeVersionSuffix(string beginning) {
|
|
import std.string : indexOf;
|
|
const suffixIndex = ldcVersion.indexOf(beginning);
|
|
if (suffixIndex > 0)
|
|
ldcVersion = ldcVersion[0 .. suffixIndex];
|
|
}
|
|
removeVersionSuffix("git-");
|
|
removeVersionSuffix("-dirty");
|
|
|
|
import std.format : format;
|
|
const localArchiveFile = "ldc-src.zip";
|
|
if (!localArchiveFile.exists) {
|
|
const url = "https://github.com/ldc-developers/ldc/releases/download/v%1$s/ldc-%1$s-src.zip".format(ldcVersion);
|
|
writefln("Downloading LDC source archive: %s", url);
|
|
import std.net.curl : download;
|
|
download(url, localArchiveFile);
|
|
if (getSize(localArchiveFile) < 1_048_576) {
|
|
writefln("Error: downloaded file is corrupt; has LDC v%s been released?", ldcVersion);
|
|
writefln("You can work around this by manually downloading a src package and moving it to: %s",
|
|
buildPath(config.buildDir, localArchiveFile));
|
|
localArchiveFile.remove;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
extractZipArchive(localArchiveFile, ".");
|
|
rename("ldc-%1$s-src".format(ldcVersion), ldcSrc);
|
|
}
|
|
|
|
void runCMake() {
|
|
import std.array : empty, byPair, join;
|
|
import std.regex : matchFirst;
|
|
|
|
const wd = WorkingDirScope(config.buildDir);
|
|
|
|
if(config.dFlags.empty)
|
|
config.dFlags ~= "-w";
|
|
|
|
if(config.targetSystem.empty)
|
|
config.targetSystem ~= "AUTO";
|
|
|
|
string[] args = [
|
|
"cmake",
|
|
"-DLDC_EXE_FULL=" ~ config.ldcExecutable,
|
|
"-DD_VERSION=@D_VERSION@",
|
|
"-DDMDFE_MINOR_VERSION=@DMDFE_MINOR_VERSION@",
|
|
"-DDMDFE_PATCH_VERSION=@DMDFE_PATCH_VERSION@",
|
|
"-DLDC_TARGET_PRESET=" ~ config.targetPreset,
|
|
"-DTARGET_SYSTEM=" ~ config.targetSystem.join(";"),
|
|
"-DD_FLAGS=" ~ config.dFlags.join(";"),
|
|
"-DRT_CFLAGS=" ~ config.cFlags.join(" "),
|
|
"-DLD_FLAGS=" ~ config.linkerFlags.join(" "),
|
|
];
|
|
if(config.targetPreset.matchFirst("^Android"))
|
|
args ~= ["-DCMAKE_SYSTEM_NAME=Linux", "-DCMAKE_C_COMPILER_WORKS=True"];
|
|
foreach (pair; config.cmakeVars.byPair)
|
|
args ~= "-D" ~ pair[0] ~ '=' ~ pair[1];
|
|
if (config.ninja)
|
|
args ~= [ "-G", "Ninja" ];
|
|
args ~= buildPath(config.ldcSourceDir, "runtime");
|
|
|
|
exec(args);
|
|
}
|
|
|
|
void build() {
|
|
const wd = WorkingDirScope(config.buildDir);
|
|
|
|
string[] args = [ config.ninja ? "ninja" : "make" ];
|
|
if (config.numBuildJobs != 0) {
|
|
import std.conv : to;
|
|
args ~= "-j" ~ config.numBuildJobs.to!string;
|
|
}
|
|
args ~= "all";
|
|
if (config.buildTestrunners)
|
|
args ~= "all-test-runners";
|
|
|
|
exec(args);
|
|
}
|
|
|
|
/*** helpers ***/
|
|
|
|
struct WorkingDirScope {
|
|
string originalPath;
|
|
this(string path) { originalPath = getcwd(); chdir(path); }
|
|
~this() { chdir(originalPath); }
|
|
}
|
|
|
|
void exec(string[] command ...) {
|
|
writeln("Invoking: ", command);
|
|
|
|
import std.process;
|
|
auto pid = spawnProcess(command);
|
|
const exitStatus = wait(pid);
|
|
|
|
if (exitStatus != 0) {
|
|
writeln("Error: command failed with status ", exitStatus);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void extractZipArchive(string archivePath, string destination) {
|
|
import std.string : endsWith;
|
|
import std.zip;
|
|
|
|
auto archive = new ZipArchive(std.file.read(archivePath));
|
|
foreach (name, am; archive.directory) {
|
|
const destPath = buildNormalizedPath(destination, name);
|
|
|
|
const isDir = name.endsWith("/");
|
|
const destDir = isDir ? destPath : destPath.dirName;
|
|
mkdirRecurse(destDir);
|
|
|
|
if (!isDir)
|
|
std.file.write(destPath, archive.expand(am));
|
|
}
|
|
}
|
|
|
|
void parseCommandLine(string[] args) {
|
|
import std.getopt : arraySep, getopt, defaultGetoptPrinter;
|
|
|
|
try {
|
|
arraySep = ";";
|
|
auto helpInformation = getopt(
|
|
args,
|
|
"ldc", "Path to LDC executable (default: '" ~ defaultLdcExecutable ~ "')", &config.ldcExecutable,
|
|
"buildDir", "Path to build directory (default: './ldc-build-runtime.tmp')", &config.buildDir,
|
|
"reset", "If build directory exists, start with removing everything but the ldc-src subdirectory", &config.resetBuildDir,
|
|
"resetOnly", "Like --reset, but only resets the build directory. No other actions are taken.", &config.resetOnly,
|
|
"ldcSrcDir", "Path to LDC source directory (if not specified: downloads & extracts source archive into '<buildDir>/ldc-src')", &config.ldcSourceDir,
|
|
"ninja", "Use Ninja as CMake build system", &config.ninja,
|
|
"testrunners", "Build the testrunner executables too", &config.buildTestrunners,
|
|
"targetPreset","Target configuration preset by LDC devs, e.g. Android-arm", &config.targetPreset,
|
|
"targetSystem","Target OS/toolchain (separated by ';'), e.g. Windows;MSVC", &config.targetSystem,
|
|
"dFlags", "LDC flags for the D modules (separated by ';')", &config.dFlags,
|
|
"cFlags", "C/ASM compiler flags for the handful of C/ASM files (separated by ';')", &config.cFlags,
|
|
"linkerFlags", "C linker flags for shared libraries and testrunner executables (separated by ';')", &config.linkerFlags,
|
|
"j", "Number of parallel build jobs", &config.numBuildJobs
|
|
);
|
|
|
|
// getopt() removed all consumed args from `args`
|
|
import std.range : drop;
|
|
foreach (arg; args.drop(1)) {
|
|
import std.algorithm.searching : findSplit;
|
|
const r = arg.findSplit("=");
|
|
if (r[1].length == 0) {
|
|
helpInformation.helpWanted = true;
|
|
break;
|
|
}
|
|
config.cmakeVars[r[0]] = r[2];
|
|
}
|
|
|
|
if (helpInformation.helpWanted) {
|
|
defaultGetoptPrinter(
|
|
"Builds the LDC runtime libraries.\n" ~
|
|
"Programs required to be found in your PATH:\n" ~
|
|
" * CMake\n" ~
|
|
" * either Make or Ninja (recommended, enable with '--ninja')\n" ~
|
|
" * C toolchain (compiler and linker)\n" ~
|
|
"--targetPreset currently supports Android-arm or Android-aarch64.\n" ~
|
|
"All arguments are optional.\n" ~
|
|
"CMake variables (see runtime/CMakeLists.txt in LDC source) can be specified via arguments like 'VAR=value'.\n",
|
|
helpInformation.options
|
|
);
|
|
exit(1);
|
|
}
|
|
|
|
if (config.resetOnly) config.resetBuildDir = true;
|
|
}
|
|
catch (Exception e) {
|
|
writefln("Error processing command line arguments: %s", e.msg);
|
|
writeln("Use '--help' for help.");
|
|
exit(1);
|
|
}
|
|
}
|