Skip to content

Commit

Permalink
Add an option to use a package manager
Browse files Browse the repository at this point in the history
Projects can now also be generated to use a package manager (Conan or
vcpkg).

The -p flag accepts a "conan" or "vcpkg" argument to select either. Omit
the flag to generate projects without dependencies already setup.
  • Loading branch information
friendlyanon committed Mar 3, 2022
1 parent 47cde57 commit 05bfbfb
Show file tree
Hide file tree
Showing 39 changed files with 610 additions and 123 deletions.
29 changes: 27 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -28,6 +28,8 @@ jobs:
matrix:
job: [1, 2, 3, 4, 5, 6, 7, 8]

pm: [none, conan, vcpkg]

os: [macos-latest, ubuntu-latest, windows-2022]

include:
Expand All @@ -40,7 +42,7 @@ jobs:
- { job: 7, type: "--c -h --examples", name: "Library (C): header-only", flag: "" }
- { job: 8, type: "--c -e", name: "Executable (C)", flag: "" }

name: ${{ matrix.name }} (${{ matrix.os }})
name: "${{ matrix.name }} (${{ matrix.os }}) (PM: ${{ matrix.pm }})"

runs-on: ${{ matrix.os }}

Expand All @@ -57,7 +59,7 @@ jobs:
- name: Create project
run: |
python -m zipapp cmake-init -p "/usr/bin/env python3"
python cmake-init.pyz ${{ matrix.type }} proj
python cmake-init.pyz ${{ matrix.type }} -p ${{ matrix.pm }} proj
- name: Test --vcpkg mode
working-directory: proj
Expand All @@ -70,6 +72,25 @@ jobs:
working-directory: proj
run: cmake -D FORMAT_COMMAND=clang-format-11 -P cmake/lint.cmake

- name: Install Conan
if: matrix.pm == 'conan'
working-directory: proj
shell: bash
run: |
pip3 install conan
conan profile new default --detect
if [ ${{ matrix.os }} = ubuntu-latest ]; then
conan profile update settings.compiler.libcxx=libstdc++11 default
fi
conan install . -if conan -b missing
- name: Install vcpkg
if: matrix.pm == 'vcpkg'
uses: friendlyanon/setup-vcpkg@v1
with:
committish: "5cf60186a241e84e8232641ee973395d4fde90e1"
ignore-reserve-cache-error: true

- name: Configure
shell: pwsh
run: cmake -S proj "--preset=ci-$("${{ matrix.os }}".split("-")[0])"
Expand All @@ -81,5 +102,9 @@ jobs:
- name: Install
run: cmake --install proj/build --config Release --prefix proj/prefix

- name: Setup PATH
if: matrix.os == 'windows-2022' && matrix.pm != 'none'
run: Add-Content "$env:GITHUB_PATH" "$(Get-Location)\proj\build\Release"

- name: Test
run: ctest --test-dir proj/build -C Release
51 changes: 47 additions & 4 deletions cmake-init/cmake_init.py
Expand Up @@ -168,7 +168,22 @@ def ask(*args, **kwargs):
"include_source": False,
"has_source": True,
"cpus": os.cpu_count(),
"pm_name": "",
}
package_manager = ask(
"Package manager to use ([N]one/[c]onan/[v]cpkg)",
cli_args.package_manager or "n",
mapper=lambda v: v[0:1].lower(),
predicate=lambda v: v in ["n", "c", "v"],
header="""\
Choosing Conan requires it to be installed. Choosing vcpkg requires the
VCPKG_ROOT environment variable to be setup to vcpkg's root directory.""",
)
d["vcpkg"] = package_manager == "v"
d["conan"] = package_manager == "c"
d["pm"] = package_manager != "n"
if d["pm"]:
d["pm_name"] = "conan" if d["conan"] else "vcpkg"
d["uc_name"] = d["name"].upper().replace("-", "_")
if d["type_id"] != "e":
key = "c_examples" if cli_args.c else "cpp_examples"
Expand Down Expand Up @@ -216,20 +231,38 @@ def should_write_examples(d, at):


def should_install_file(name, d):
if name == "install-config.cmake" and d["type_id"] == "e":
if name == "vcpkg.json" and not d["vcpkg"]:
return False
if name == "conanfile.txt" and not d["conan"]:
return False
if name == "install-config.cmake" and d["exe"]:
return False
if name == "windows-set-path.cmake" and d["pm"]:
return False
if name == "install-script.cmake" and not d["exe"]:
return False
if name == "header_impl.c" and (not d["c_header"] or not d["pm"]):
return False
if name == "install-script.cmake" and d["type_id"] != "e":
if name == "clang-11.profile" and not d["conan"]:
return False
return True


def transform_path(path, d):
if not d["exe"] and d["pm"] and path.endswith("install-config.cmake"):
return f"{path}.in"
if d["c"] and d["pm"] and path.endswith("_test.c"):
return f"{path}pp"
return path


def write_dir(path, d, overwrite, zip_path):
for entry in zip_path.iterdir():
name = entry.name.replace("__name__", d["name"])
next_path = os.path.join(path, name)
if entry.is_file():
if should_install_file(name, d):
write_file(next_path, d, overwrite, entry)
write_file(transform_path(next_path, d), d, overwrite, entry)
elif name != "example" or should_write_examples(d, entry.at):
mkdir(next_path)
write_dir(next_path, d, overwrite, entry)
Expand Down Expand Up @@ -273,11 +306,15 @@ def git_init(cwd):

def print_tips(d):
config = " --config Debug" if is_windows else ""
conan = ""
if d["conan"]:
conan = """
conan install . -if conan -s build_type=Debug -b missing"""
print(f"""\
To get you started with the project in developer mode, you may configure,
build, install and test with the following commands from the project directory,
in that order:
{conan}
cmake --preset=dev
cmake --build --preset=dev
cmake --install build/dev{config} --prefix prefix
Expand Down Expand Up @@ -509,6 +546,12 @@ def main(zip):
const="n",
help="generate examples for a library",
)
p.add_argument(
"-p",
metavar="pm",
dest="package_manager",
help="package manager to use (Options are: conan, vcpkg)",
)
args = p.parse_args()
if args.dummy:
p.print_help()
Expand Down
5 changes: 4 additions & 1 deletion cmake-init/templates/c/executable/CMakeLists.txt
Expand Up @@ -26,7 +26,10 @@ target_include_directories(
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/source>"
)

target_compile_features({= name =}_lib PUBLIC c_std_{= std =})
target_compile_features({= name =}_lib PUBLIC c_std_{= std =}){% if pm %}

find_package(json-c REQUIRED)
target_link_libraries({= name =}_lib PRIVATE json-c::json-c){% end %}

# ---- Declare executable ----

Expand Down
63 changes: 59 additions & 4 deletions cmake-init/templates/c/executable/source/lib.c
@@ -1,8 +1,63 @@
#include "lib.h"
#include "lib.h"{% if pm %}

#include <json-c/json_object.h>
#include <json-c/json_tokener.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

static const char json[] = "{\"name\":\"{= name =}\"}";{% end %}

library create_library()
{
library lib;
lib.name = "{= name =}";
library lib;{% if pm %}
struct json_tokener* tokener = NULL;
struct json_object* object = NULL;
struct json_object* name_object = NULL;
const char* json_name = NULL;
size_t name_size = 0;
char* name = NULL;

tokener = json_tokener_new();
if (tokener == NULL) {
goto exit;
}

object = json_tokener_parse_ex(tokener, json, sizeof(json));
if (object == NULL) {
goto cleanup_tokener;
}

if (json_object_object_get_ex(object, "name", &name_object) == 0) {
goto cleanup_object;
}

json_name = json_object_get_string(name_object);
if (json_name == NULL) {
goto cleanup_object;
}

name_size = strlen(json_name) + 1;
name = malloc(name_size);
if (name == NULL) {
goto cleanup_object;
}

(void)memcpy(name, json_name, name_size);

cleanup_object:
(void)json_object_put(object);

cleanup_tokener:
json_tokener_free(tokener);

exit:
lib.name = name;{% else %}
lib.name = "{= name =}";{% end %}
return lib;
}
}{% if pm %}

void destroy_library(library* lib)
{
free((void*)lib->name);
}{% end %}
7 changes: 6 additions & 1 deletion cmake-init/templates/c/executable/source/lib.h
Expand Up @@ -10,4 +10,9 @@ typedef struct library {
/**
* @brief Creates an instance of library with the name of the project
*/
library create_library(void);
library create_library(void);{% if pm %}

/**
* @brief Destroys resources held by the library
*/
void destroy_library(library* lib);{% end %}
16 changes: 12 additions & 4 deletions cmake-init/templates/c/executable/source/main.c
@@ -1,13 +1,21 @@
#include <stdio.h>
{% if pm %}#include <stddef.h>
{% end %}#include <stdio.h>

#include "lib.h"

int main(int argc, const char* argv[])
{
library lib = create_library();

(void)argc;
(void)argv;

library lib = create_library();
printf("Hello from %%s!", lib.name);
{% if not pm %}
(void)printf("Hello from %s!", lib.name);{% else %}
if (lib.name == NULL) {
(void)puts("Hello from unknown! (JSON parsing failed in library)");
} else {
(void)printf("Hello from %s!", lib.name);
}
destroy_library(&lib);{% end %}
return 0;
}
13 changes: 0 additions & 13 deletions cmake-init/templates/c/executable/test/CMakeLists.txt

This file was deleted.

34 changes: 29 additions & 5 deletions cmake-init/templates/c/executable/test/source/__name___test.c
@@ -1,13 +1,37 @@
#include <string.h>
{% if pm %}#include <memory>
#include <string>

#include "lib.h"
#include <catch2/catch.hpp>{% else %}#include <string.h>{% end %}

{% if pm %}extern "C" {
{% end %}#include "lib.h"
{% if pm %}}

namespace {

struct library_delete
{
void operator()(void* p) const
{
destroy_library(static_cast<library*>(p));
}
};

} // namespace

TEST_CASE("Name is {= name =}", "[library]")
{
library lib = create_library();
auto ptr = std::unique_ptr<library, library_delete>(&lib, {});

REQUIRE(std::string("{= name =}") == lib.name);
}{% else %}
int main(int argc, const char* argv[])
{
library lib = create_library();

(void)argc;
(void)argv;

library lib = create_library();

return strcmp(lib.name, "{= name =}") == 0 ? 0 : 1;
}
}{% end %}
5 changes: 4 additions & 1 deletion cmake-init/templates/c/header/CMakeLists.txt
Expand Up @@ -29,7 +29,10 @@ target_include_directories(
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
)

target_compile_features({= name =}_{= name =} INTERFACE c_std_{= std =})
target_compile_features({= name =}_{= name =} INTERFACE c_std_{= std =}){% if pm %}

find_package(json-c REQUIRED)
target_link_libraries({= name =}_{= name =} INTERFACE json-c::json-c){% end %}

# ---- Install rules ----

Expand Down

0 comments on commit 05bfbfb

Please sign in to comment.