Skip to content

Commit

Permalink
Add ldc-build-plugin tool, and make use of it in plugins lit tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
JohanEngelen committed Jul 21, 2023
1 parent c3b56d7 commit b0d8748
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 12 deletions.
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
set( LDC2_BIN ${PROJECT_BINARY_DIR}/bin/${LDC_EXE} )
set( LDCPROFDATA_BIN ${PROJECT_BINARY_DIR}/bin/ldc-profdata )
set( LDCPRUNECACHE_BIN ${PROJECT_BINARY_DIR}/bin/${LDCPRUNECACHE_EXE} )
set( LDCBUILDPLUGIN_BIN ${PROJECT_BINARY_DIR}/bin/${LDC_BUILD_PLUGIN_EXE} )
set( TIMETRACE2TXT_BIN ${PROJECT_BINARY_DIR}/bin/${TIMETRACE2TXT_EXE} )
set( LLVM_TOOLS_DIR ${LLVM_ROOT_DIR}/bin )
set( LDC2_BIN_DIR ${PROJECT_BINARY_DIR}/bin )
Expand Down
2 changes: 2 additions & 0 deletions tests/lit.site.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ OFF = False
config.ldc2_bin = "@LDC2_BIN@"
config.ldcprofdata_bin = "@LDCPROFDATA_BIN@"
config.ldcprunecache_bin = "@LDCPRUNECACHE_BIN@"
config.ldcbuildplugin_bin = "@LDCBUILDPLUGIN_BIN@"
config.timetrace2txt_bin = "@TIMETRACE2TXT_BIN@"
config.ldc2_bin_dir = "@LDC2_BIN_DIR@"
config.ldc2_lib_dir = "@LDC2_LIB_DIR@"
Expand Down Expand Up @@ -157,6 +158,7 @@ config.substitutions.append( ('%ldc', config.ldc2_bin) )
config.substitutions.append( ('%gnu_make', config.gnu_make_bin) )
config.substitutions.append( ('%profdata', config.ldcprofdata_bin) )
config.substitutions.append( ('%prunecache', config.ldcprunecache_bin) )
config.substitutions.append( ('%buildplugin', config.ldcbuildplugin_bin + " --ldcSrcDir=" + config.ldc2_source_dir ) )
config.substitutions.append( ('%timetrace2txt', config.timetrace2txt_bin) )
config.substitutions.append( ('%llvm-spirv', os.path.join(config.llvm_tools_dir, 'llvm-spirv')) )
config.substitutions.append( ('%llc', os.path.join(config.llvm_tools_dir, 'llc')) )
Expand Down
2 changes: 1 addition & 1 deletion tests/plugins/basic_sema_plugin.d
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// UNSUPPORTED: Darwin && host_X86

// RUN: split-file %s %t --leading-lines
// RUN: %ldc %t/plugin.d %plugin_compile_flags -of=%t/plugin%so
// RUN: %buildplugin %t/plugin.d -of=%t/plugin%so
// RUN: %ldc -wi -c -o- --plugin=%t/plugin%so %t/testcase.d 2>&1 | FileCheck %t/testcase.d

//--- plugin.d
Expand Down
10 changes: 0 additions & 10 deletions tests/plugins/lit.local.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,6 @@ if (config.plugins_supported):
config.environment['LLVM_CONFIG'] = os.path.join(config.llvm_tools_dir, 'llvm-config')
config.environment['LLVM_VERSION'] = str(config.llvm_version)

plugin_compile_flags = [ '-I' + config.ldc2_source_dir,
'--d-version=IN_LLVM',
'-J' + config.ldc2_source_dir + '/dmd/res',
'--shared',
'--defaultlib=',
]
if (platform.system() == 'Darwin'):
plugin_compile_flags.append('-L-Wl,-undefined,dynamic_lookup')
config.substitutions.append( ('%plugin_compile_flags', " ".join(plugin_compile_flags) ) )

# Set feature that tells us that the just-built LDC is ABI compatible with the host D compiler
# For our tets, the required ABI compatibility seems OK since at least LDC 1.30.
# If the compiler is built not by LDC but another compiler, then assume the ABI to be incompatible.
Expand Down
2 changes: 1 addition & 1 deletion tests/plugins/visitor_example.d
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// UNSUPPORTED: Darwin && host_X86

// RUN: split-file %s %t --leading-lines
// RUN: %ldc -g %t/plugin.d %plugin_compile_flags -of=%t/plugin%so
// RUN: %buildplugin %t/plugin.d -of=%t/plugin%so
// RUN: %ldc -wi -c -o- --plugin=%t/plugin%so %t/testcase.d 2>&1 | FileCheck %t/testcase.d

//--- plugin.d
Expand Down
22 changes: 22 additions & 0 deletions tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,25 @@ build_d_executable(
${COMPILE_D_MODULES_SEPARATELY}
)
install(PROGRAMS ${TIMETRACE2TXT_EXE_FULL} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)

#############################################################################
# Only build ldc-build-plugin tool for platforms where plugins are actually enabled.
if(LDC_ENABLE_PLUGINS)
configure_file(${PROJECT_SOURCE_DIR}/tools/ldc-build-plugin.d.in ${PROJECT_BINARY_DIR}/ldc-build-plugin.d @ONLY)
set(LDC_BUILD_PLUGIN_EXE ldc-build-plugin)
set(LDC_BUILD_PLUGIN_EXE ${LDC_BUILD_PLUGIN_EXE} PARENT_SCOPE) # needed for correctly populating lit.site.cfg.in
set(LDC_BUILD_PLUGIN_EXE_NAME ${PROGRAM_PREFIX}${LDC_BUILD_PLUGIN_EXE}${PROGRAM_SUFFIX})
set(LDC_BUILD_PLUGIN_EXE_FULL ${PROJECT_BINARY_DIR}/bin/${LDC_BUILD_PLUGIN_EXE_NAME}${CMAKE_EXECUTABLE_SUFFIX})
build_d_executable(
"${LDC_BUILD_PLUGIN_EXE}"
"${LDC_BUILD_PLUGIN_EXE_FULL}"
"${PROJECT_BINARY_DIR}/ldc-build-plugin.d"
"${DFLAGS_BUILD_TYPE}"
""
"${PROJECT_SOURCE_DIR}/tools/ldc-build-plugin.d.in"
""
${COMPILE_D_MODULES_SEPARATELY}
)
install(PROGRAMS ${LDC_BUILD_PLUGIN_EXE_FULL} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
endif()

239 changes: 239 additions & 0 deletions tools/ldc-build-plugin.d.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
module ldcBuildRuntime;

import core.stdc.stdlib : exit;
import std.algorithm;
import std.array;
import std.file;
import std.path;
import std.stdio;

version (OSX)
version = Darwin;
else version (iOS)
version = Darwin;
else version (TVOS)
version = Darwin;
else version (WatchOS)
version = Darwin;

struct Config {
string ldcExecutable;
string buildDir;
string ldcSourceDir;
string[] dFlags;
string[] linkerFlags;
bool verbose;
string[] ldcArgs;
string userWorkDir;
}

version (Windows) enum exeSuffix = ".exe";
else enum exeSuffix = "";

string defaultLdcExecutable;
Config config;

int main(string[] args) {
enum exeName = "ldc2" ~ exeSuffix;
defaultLdcExecutable = buildPath(thisExePath.dirName, exeName);
config.userWorkDir = getcwd();

parseCommandLine(args);

findLdcExecutable();

prepareBuildDir();

prepareLdcSource();

build();

if (config.verbose)
writefln(".: Plugin library built successfully.");
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;
}

writefln(".: Please specify LDC executable via '--ldc=<path/to/ldc2%s>'. Aborting.", exeSuffix);
exit(1);
}

void prepareBuildDir() {
if (config.buildDir is null)
config.buildDir = "ldc-build-plugin.tmp";

if (!config.buildDir.exists) {
if (config.verbose)
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, "dmd").exists)
return;

// Download & extract LDC source archive if <buildDir>/ldc-src/dmd doesn't exist yet.

const wd = WorkingDirScope(config.buildDir);

auto ldcVersion = "@LDC_VERSION@";
void removeVersionSuffix(string beginning) {
const suffixIndex = ldcVersion.countUntil(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 build() {
string[] args = [
config.ldcExecutable,
"-I" ~ config.ldcSourceDir,
"--d-version=IN_LLVM",
"-J" ~ buildPath(config.ldcSourceDir, "dmd", "res"),
"--shared",
"--defaultlib=",
"--od=" ~ config.buildDir
];

version (Darwin) {
args ~= "-L-Wl,-undefined,dynamic_lookup";
}

args ~= config.ldcArgs;

exec(args);
}

/*** helpers ***/

struct WorkingDirScope {
string originalPath;
this(string path) { originalPath = getcwd(); chdir(path); }
~this() { chdir(originalPath); }
}

void exec(string[] command) {
import std.process;

static string quoteIfNeeded(string arg) {
const r = arg.findAmong(" ;");
return !r.length ? arg : "'" ~ arg ~ "'";
}
string flattened = command.map!quoteIfNeeded.join(" ");
if (config.verbose) {
writefln(".: Invoking: %s", flattened);
stdout.flush();
}

auto pid = spawnProcess(command, null, std.process.Config.none, config.userWorkDir);
const exitStatus = wait(pid);

if (exitStatus != 0) {
if (config.verbose)
writeln(".: Error: command failed with status ", exitStatus);
exit(1);
}
}

void extractZipArchive(string archivePath, string destination) {
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;

try {
arraySep = ";";
auto helpInformation = getopt(
args,
std.getopt.config.passThrough,
"ldc", "Path to LDC executable (default: '" ~ defaultLdcExecutable ~ "')", &config.ldcExecutable,
"buildDir", "Path to build directory (default: './ldc-build-plugin.tmp')", &config.buildDir,
"ldcSrcDir", "Path to LDC source directory (if not specified: downloads & extracts source archive into '<buildDir>/ldc-src')", &config.ldcSourceDir,
"dFlags", "Extra LDC flags for the D module (separated by ';')", &config.dFlags,
"verbose|v", "Verbose output (e.g. showing the compile commandline)", &config.verbose,
"linkerFlags", "Extra C linker flags for shared libraries and testrunner executables (separated by ';')", &config.linkerFlags
);

// getopt() has removed all consumed args from `args`
// Remaining arguments are interpreted as LDC arguments (e.g. plugin source files and -of=<output file>).
config.ldcArgs = args[1 .. $];

if (helpInformation.helpWanted) {
defaultGetoptPrinter(
"OVERVIEW: Builds a Semantic Analysis plugin for LDC.\n\n" ~
"USAGE: ldc-build-plugin [options] sourcefiles... -of=<output file>\n\n" ~
"OPTIONS:\n" ~
" Unrecognized options are passed through to LDC.",
helpInformation.options
);
exit(1);
}
}
catch (Exception e) {
writefln("Error processing command line arguments: %s", e.msg);
writeln("Use '--help' for help.");
exit(1);
}
}

0 comments on commit b0d8748

Please sign in to comment.