@@ -11,6 +11,7 @@ | |||
#include <dds/util/time.hpp> | |||
#include <fansi/styled.hpp> | |||
#include <fmt/ostream.h> | |||
#include <array> | |||
#include <set> | |||
@@ -212,6 +213,70 @@ void write_lmi(build_env_ref env, const build_plan& plan, path_ref base_dir, pat | |||
} | |||
} | |||
void write_lib_cmake(build_env_ref env, | |||
std::ostream& out, | |||
const package_plan& pkg, | |||
const library_plan& lib) { | |||
fmt::print(out, "# Library {}/{}\n", pkg.namespace_(), lib.name()); | |||
auto cmake_name = fmt::format("{}::{}", pkg.namespace_(), lib.name()); | |||
auto cm_kind = lib.archive_plan().has_value() ? "STATIC" : "INTERFACE"; | |||
fmt::print( | |||
out, | |||
"if(TARGET {0})\n" | |||
" get_target_property(dds_imported {0} dds_IMPORTED)\n" | |||
" if(NOT dds_imported)\n" | |||
" message(WARNING [[A target \"{0}\" is already defined, and not by a dds import]])\n" | |||
" endif()\n" | |||
"else()\n", | |||
cmake_name); | |||
fmt::print(out, | |||
" add_library({0} {1} IMPORTED GLOBAL)\n" | |||
" set_property(TARGET {0} PROPERTY dds_IMPORTED TRUE)\n" | |||
" set_property(TARGET {0} PROPERTY INTERFACE_INCLUDE_DIRECTORIES [[{2}]])\n", | |||
cmake_name, | |||
cm_kind, | |||
lib.library_().public_include_dir().generic_string()); | |||
for (auto&& use : lib.uses()) { | |||
fmt::print(out, | |||
" set_property(TARGET {} APPEND PROPERTY INTERFACE_LINK_LIBRARIES {}::{})\n", | |||
cmake_name, | |||
use.namespace_, | |||
use.name); | |||
} | |||
for (auto&& link : lib.links()) { | |||
fmt::print(out, | |||
" set_property(TARGET {} APPEND PROPERTY\n" | |||
" INTERFACE_LINK_LIBRARIES $<LINK_ONLY:{}::{}>)\n", | |||
cmake_name, | |||
link.namespace_, | |||
link.name); | |||
} | |||
if (auto& arc = lib.archive_plan()) { | |||
fmt::print(out, | |||
" set_property(TARGET {} PROPERTY IMPORTED_LOCATION [[{}]])\n", | |||
cmake_name, | |||
(env.output_root / arc->calc_archive_file_path(env.toolchain)).generic_string()); | |||
} | |||
fmt::print(out, "endif()\n"); | |||
} | |||
void write_cmake_pkg(build_env_ref env, std::ostream& out, const package_plan& pkg) { | |||
fmt::print(out, "## Imports for {}\n", pkg.name()); | |||
for (auto& lib : pkg.libraries()) { | |||
write_lib_cmake(env, out, pkg, lib); | |||
} | |||
fmt::print(out, "\n"); | |||
} | |||
void write_cmake(build_env_ref env, const build_plan& plan, path_ref cmake_out) { | |||
fs::create_directories(fs::absolute(cmake_out).parent_path()); | |||
auto out = open(cmake_out, std::ios::binary | std::ios::out); | |||
out << "## This CMake file was generated by `dds build-deps`. DO NOT EDIT!\n\n"; | |||
for (const auto& pkg : plan.packages()) { | |||
write_cmake_pkg(env, out, pkg); | |||
} | |||
} | |||
template <typename Func> | |||
void with_build_plan(const build_params& params, | |||
const std::vector<sdist_target>& sdists, | |||
@@ -286,5 +351,9 @@ void builder::build(const build_params& params) const { | |||
if (params.emit_lmi) { | |||
write_lmi(env, plan, params.out_root, *params.emit_lmi); | |||
} | |||
if (params.emit_cmake) { | |||
write_cmake(env, plan, *params.emit_cmake); | |||
} | |||
}); | |||
} |
@@ -12,6 +12,7 @@ struct build_params { | |||
fs::path out_root; | |||
std::optional<fs::path> existing_lm_index; | |||
std::optional<fs::path> emit_lmi; | |||
std::optional<fs::path> emit_cmake{}; | |||
dds::toolchain toolchain; | |||
bool generate_compdb = true; | |||
int parallel_jobs = 0; |
@@ -17,6 +17,7 @@ int build_deps(const options& opts) { | |||
.out_root = opts.out_path.value_or(fs::current_path() / "_deps"), | |||
.existing_lm_index = {}, | |||
.emit_lmi = opts.build.lm_index.value_or("INDEX.lmi"), | |||
.emit_cmake = opts.build_deps.cmake_file, | |||
.toolchain = opts.load_toolchain(), | |||
.parallel_jobs = opts.jobs, | |||
}; |
@@ -221,6 +221,13 @@ struct setup { | |||
.can_repeat = true, | |||
.action = debate::push_back_onto(opts.build_deps.deps_files), | |||
}); | |||
build_deps_cmd.add_argument({ | |||
.long_spellings = {"cmake"}, | |||
.help = "Generate a CMake file at the given path that will create import targets for " | |||
"the dependencies", | |||
.valname = "<file-path>", | |||
.action = debate::put_into(opts.build_deps.cmake_file), | |||
}); | |||
build_deps_cmd.add_argument({ | |||
.help = "Dependency statement strings", | |||
.valname = "<dependency>", |
@@ -154,6 +154,8 @@ struct options { | |||
std::vector<fs::path> deps_files; | |||
/// Dependency strings provided directly in the command-line | |||
std::vector<string> deps; | |||
/// Path to a CMake import file to write | |||
opt_path cmake_file; | |||
} build_deps; | |||
/** |
@@ -0,0 +1,7 @@ | |||
cmake_minimum_required(VERSION 3.12) | |||
project(TestProject) | |||
include(${PROJECT_BINARY_DIR}/libraries.cmake) | |||
add_executable(app main.cpp) | |||
target_link_libraries(app PRIVATE test::foo) |
@@ -0,0 +1,3 @@ | |||
#include <foo.hpp> | |||
int main() { say_hello(); } |
@@ -0,0 +1,3 @@ | |||
#pragma once | |||
extern void say_hello(); |
@@ -0,0 +1,5 @@ | |||
#include <foo.hpp> | |||
#include <iostream> | |||
void say_hello() { std::cout << "Hello!\n"; } |
@@ -2,7 +2,8 @@ import json | |||
import pytest | |||
from dds_ci.testing import RepoServer, Project | |||
from dds_ci.testing import RepoServer, Project, ProjectOpener | |||
from dds_ci import proc, toolchain | |||
SIMPLE_CATALOG = { | |||
"packages": { | |||
@@ -58,3 +59,27 @@ def test_multiple_deps(test_project: Project) -> None: | |||
assert test_project.root.joinpath('_deps/_libman/neo-fun.lmp').is_file() | |||
assert test_project.root.joinpath('_deps/_libman/neo/fun.lml').is_file() | |||
assert test_project.root.joinpath('INDEX.lmi').is_file() | |||
def test_cmake_simple(project_opener: ProjectOpener) -> None: | |||
proj = project_opener.open('projects/simple') | |||
proj.dds.pkg_import(proj.root) | |||
cm_proj_dir = project_opener.test_dir / 'projects/simple-cmake' | |||
proj.build_root.mkdir(exist_ok=True, parents=True) | |||
proj.dds.run( | |||
[ | |||
'build-deps', | |||
proj.dds.repo_dir_arg, | |||
'foo@1.2.3', | |||
('-t', ':gcc' if 'gcc' in toolchain.get_default_toolchain().name else ':msvc'), | |||
f'--cmake=libraries.cmake', | |||
], | |||
cwd=proj.build_root, | |||
) | |||
try: | |||
proc.check_run(['cmake', '-S', cm_proj_dir, '-B', proj.build_root]) | |||
except FileNotFoundError: | |||
assert False, 'Running the integration tests requires a CMake executable' | |||
proc.check_run(['cmake', '--build', proj.build_root]) |
@@ -2,7 +2,7 @@ FROM alpine:3.12.1 | |||
# Base build dependencies | |||
RUN apk add "gcc=9.3.0-r2" "g++=9.3.0-r2" make python3 py3-pip \ | |||
git openssl-libs-static openssl-dev ccache lld curl python3-dev | |||
git openssl-libs-static openssl-dev ccache lld curl python3-dev cmake | |||
# We use version-qualified names for compiler executables | |||
RUN ln -s $(type -P gcc) /usr/local/bin/gcc-9 && \ |
@@ -132,6 +132,8 @@ class DDSWrapper: | |||
toolchain = toolchain or tc_mod.get_default_audit_toolchain() | |||
self.run([ | |||
'compile-file', | |||
self.catalog_path_arg, | |||
self.repo_dir_arg, | |||
paths, | |||
f'--toolchain={toolchain}', | |||
f'{self.project_dir_flag}={project_dir}', |