Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,16 @@ on:
jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Nix
uses: cachix/install-nix-action@v27
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Build with Nix

- name: Build and test
run: nix build

- name: Run core tests
run: |
export LGX_BINARY="$(pwd)/result/bin/lgx"
./result/bin/lgx_tests

- name: Run library tests
run: ./result/bin/lgx_lib_tests
26 changes: 1 addition & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,7 @@ The C API header will be at `./result/include/lgx.h`.

#### Running Tests with Nix

After building with `nix build '.'` or `nix build '.#all'`, run the tests:

```bash
# Run core tests (including CLI integration tests)
export LGX_BINARY="$(pwd)/result/bin/lgx"
./result/bin/lgx_tests

# Run library API tests
./result/bin/lgx_lib_tests
```

**Note:** The `LGX_BINARY` environment variable tells the CLI tests where to find the `lgx` binary. Without it, the CLI integration tests will be skipped (though all other tests will still run).
Tests run automatically during `nix build`. The build will fail if any tests do not pass.

**Note:** If you haven't enabled flakes, you'll need to add the experimental features flag:

Expand Down Expand Up @@ -212,13 +201,6 @@ This will create:
```bash
mkdir build
cd build
cmake .. -DLGX_BUILD_TESTS=ON
make -j$(nproc)
```

To also test the library:

```bash
cmake .. -DLGX_BUILD_TESTS=ON -DLGX_BUILD_SHARED=ON
make -j$(nproc)
```
Expand All @@ -229,9 +211,3 @@ make -j$(nproc)
cd build
ctest --output-on-failure
```

Or run the test executable directly:

```bash
./build/lgx_tests
```
28 changes: 3 additions & 25 deletions docs/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ The header file `src/lgx.h` is installed to `include/` when using `make install`
- `lgx_set_version(pkg, version) → lgx_result_t` - Set package version
- `lgx_get_description(pkg) → const char*` - Get package description (owned by library)
- `lgx_set_description(pkg, description)` - Set package description
- `lgx_get_icon(pkg) → const char*` - Get package icon path (owned by library)
- `lgx_set_icon(pkg, icon)` - Set package icon path
- `lgx_get_manifest_json(pkg) → const char*` - Get the full manifest as a JSON string (owned by library)

**Memory Management:**
Expand Down Expand Up @@ -594,37 +596,13 @@ This builds:

**Running Tests with CMake:**

Tests are built using Google Test and can be run via CMake's CTest or directly:
Tests are built using Google Test and can be run via CMake's CTest:

```bash
cd build
ctest --output-on-failure
```

Or run test executables directly:

```bash
# Core tests (including CLI integration tests)
./build/tests/lgx_tests

# Library API tests (requires shared library build)
./build/tests/lgx_lib_tests
```

**CLI Integration Tests:**

The CLI tests (`test_cli.cpp`) require the `lgx` binary to be available. They will:
1. Check for `LGX_BINARY` environment variable first
2. Search common locations (build directory, parent directories)
3. Skip if binary is not found

To run CLI tests explicitly:

```bash
export LGX_BINARY="$(pwd)/build/lgx"
./build/tests/lgx_tests
```

**Installation:**

To install the built binaries and libraries system-wide:
Expand Down
3 changes: 3 additions & 0 deletions docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ The manifest is a UTF-8 encoded JSON file with the following required fields:
"author": "Author Name",
"type": "library",
"category": "crypto",
"icon": "icon.png",
"dependencies": ["dep1", "dep2"],
"main": {
"linux-amd64": "path/to/main.so",
Expand All @@ -82,6 +83,7 @@ The manifest is a UTF-8 encoded JSON file with the following required fields:
| `author` | string | Author/maintainer name | Human metadata |
| `type` | string | Package type classification | Classification |
| `category` | string | Package category | Classification |
| `icon` | string | Relative path to icon file bundled in the package | Display/branding |
| `dependencies` | array | List of dependency strings | Runtime needs |
| `main` | object | Map of variant name → relative path to entry point (e.g ) `"linux-amd64": "path/to/main.so"` means `linux-amd64/path/to/main.so` | Entry point resolution |

Expand Down Expand Up @@ -156,6 +158,7 @@ lgx create <name>
- `author`: `""`
- `type`: `""`
- `category`: `""`
- `icon`: `""`
- `dependencies`: `[]`
- `main`: `{}`
4. Create empty `variants/` directory
Expand Down
14 changes: 0 additions & 14 deletions nix/all.nix
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,6 @@ pkgs.stdenv.mkDerivation {
mkdir -p $out/include
cp ${src}/src/lgx.h $out/include/

# Install test executables
mkdir -p $out/bin
cp build/tests/lgx_tests $out/bin/
cp build/tests/lgx_lib_tests $out/bin/

# Fix rpath for lgx_lib_tests to find the shared library
if [ -f $out/bin/lgx_lib_tests ]; then
if [[ "$OSTYPE" == "darwin"* ]]; then
install_name_tool -add_rpath $out/lib $out/bin/lgx_lib_tests
else
patchelf --set-rpath $out/lib:$(patchelf --print-rpath $out/bin/lgx_lib_tests) $out/bin/lgx_lib_tests
fi
fi

runHook postInstall
'';

Expand Down
10 changes: 9 additions & 1 deletion src/core/manifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Manifest::Manifest()
, author("")
, type("")
, category("")
, icon("")
{
}

Expand Down Expand Up @@ -70,7 +71,13 @@ std::optional<Manifest> Manifest::fromJson(const std::string& jsonStr) {
return std::nullopt;
}
m.category = j["category"].get<std::string>();


if (!j.contains("icon") || !j["icon"].is_string()) {
lastError_ = "Missing or invalid 'icon' field";
return std::nullopt;
}
m.icon = j["icon"].get<std::string>();

if (!j.contains("dependencies") || !j["dependencies"].is_array()) {
lastError_ = "Missing or invalid 'dependencies' field";
return std::nullopt;
Expand Down Expand Up @@ -115,6 +122,7 @@ std::string Manifest::toJson() const {
j["author"] = author;
j["type"] = type;
j["category"] = category;
j["icon"] = icon;
j["dependencies"] = dependencies;

// main as object with sorted keys
Expand Down
1 change: 1 addition & 0 deletions src/core/manifest.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Manifest {
std::string author;
std::string type;
std::string category;
std::string icon;
std::vector<std::string> dependencies;

// Main mapping: variant -> relative path to entry point
Expand Down
1 change: 1 addition & 0 deletions src/core/package.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Package::Result Package::create(
pkg.manifest_.author = "";
pkg.manifest_.type = "";
pkg.manifest_.category = "";
pkg.manifest_.icon = "";
pkg.manifest_.dependencies = {};

// Create skeleton with manifest and variants directory
Expand Down
16 changes: 16 additions & 0 deletions src/lgx.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,22 @@ LGX_EXPORT const char* lgx_get_description(lgx_package_t pkg);
*/
LGX_EXPORT void lgx_set_description(lgx_package_t pkg, const char* description);

/**
* Get the package icon path from manifest.
*
* @param pkg Package handle
* @return Package icon path, owned by library (valid until package is freed)
*/
LGX_EXPORT const char* lgx_get_icon(lgx_package_t pkg);

/**
* Set the package icon path in manifest.
*
* @param pkg Package handle
* @param icon New icon path string
*/
LGX_EXPORT void lgx_set_icon(lgx_package_t pkg, const char* icon);

/**
* Get the full manifest as a JSON string.
*
Expand Down
22 changes: 22 additions & 0 deletions src/lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ struct lgx_package_opaque {
std::string name_cache;
std::string version_cache;
std::string description_cache;
std::string icon_cache;
std::string manifest_json_cache;
};

Expand Down Expand Up @@ -280,6 +281,27 @@ LGX_EXPORT void lgx_set_description(lgx_package_t pkg, const char* description)
pkg->pkg->getManifest().description = description;
}

LGX_EXPORT const char* lgx_get_icon(lgx_package_t pkg) {
if (!pkg) {
set_error("Invalid argument: pkg cannot be NULL");
return nullptr;
}

clear_error();
pkg->icon_cache = pkg->pkg->getManifest().icon;
return pkg->icon_cache.c_str();
}

LGX_EXPORT void lgx_set_icon(lgx_package_t pkg, const char* icon) {
if (!pkg || !icon) {
set_error("Invalid arguments: pkg and icon cannot be NULL");
return;
}

clear_error();
pkg->pkg->getManifest().icon = icon;
}

LGX_EXPORT const char* lgx_get_manifest_json(lgx_package_t pkg) {
if (!pkg) {
set_error("Invalid argument: pkg cannot be NULL");
Expand Down
17 changes: 14 additions & 3 deletions tests/test_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ TEST_F(LibraryTest, GetPackageMetadata) {
// Get description (should be empty initially)
const char* desc = lgx_get_description(pkg);
ASSERT_NE(desc, nullptr);


// Get icon (should be empty initially)
const char* icon = lgx_get_icon(pkg);
ASSERT_NE(icon, nullptr);
EXPECT_STREQ(icon, "");

lgx_free_package(pkg);
}

Expand All @@ -123,7 +128,12 @@ TEST_F(LibraryTest, SetPackageMetadata) {
lgx_set_description(pkg, "Test package");
const char* desc = lgx_get_description(pkg);
EXPECT_STREQ(desc, "Test package");


// Set icon
lgx_set_icon(pkg, "icon.png");
const char* icon = lgx_get_icon(pkg);
EXPECT_STREQ(icon, "icon.png");

lgx_free_package(pkg);
}

Expand Down Expand Up @@ -303,7 +313,8 @@ TEST_F(LibraryTest, NullPackageHandles) {
EXPECT_EQ(lgx_get_name(nullptr), nullptr);
EXPECT_EQ(lgx_get_version(nullptr), nullptr);
EXPECT_EQ(lgx_get_description(nullptr), nullptr);

EXPECT_EQ(lgx_get_icon(nullptr), nullptr);

// These should not crash with NULL
lgx_free_package(nullptr);
}
Expand Down
24 changes: 24 additions & 0 deletions tests/test_manifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ static const char* VALID_MANIFEST_JSON = R"({
"author": "Test Author",
"type": "library",
"category": "test",
"icon": "icon.png",
"dependencies": ["dep1", "dep2"],
"main": {
"linux-amd64": "lib/test.so",
Expand All @@ -34,6 +35,7 @@ TEST(ManifestTest, FromJson_ValidManifest) {
EXPECT_EQ(manifest->author, "Test Author");
EXPECT_EQ(manifest->type, "library");
EXPECT_EQ(manifest->category, "test");
EXPECT_EQ(manifest->icon, "icon.png");
EXPECT_EQ(manifest->dependencies.size(), 2);
EXPECT_EQ(manifest->main.size(), 2);
}
Expand All @@ -46,6 +48,7 @@ TEST(ManifestTest, FromJson_MissingManifestVersion) {
"author": "",
"type": "",
"category": "",
"icon": "",
"dependencies": [],
"main": {}
})";
Expand All @@ -62,6 +65,7 @@ TEST(ManifestTest, FromJson_MissingName) {
"author": "",
"type": "",
"category": "",
"icon": "",
"dependencies": [],
"main": {}
})";
Expand All @@ -79,6 +83,7 @@ TEST(ManifestTest, FromJson_MissingMain) {
"author": "",
"type": "",
"category": "",
"icon": "",
"dependencies": []
})";

Expand All @@ -94,6 +99,23 @@ TEST(ManifestTest, FromJson_InvalidJson) {
EXPECT_FALSE(Manifest::getLastError().empty());
}

TEST(ManifestTest, FromJson_MissingIcon) {
const char* json = R"({
"manifestVersion": "0.1.0",
"name": "test",
"version": "1.0.0",
"description": "",
"author": "",
"type": "",
"category": "",
"dependencies": [],
"main": {}
})";

auto manifest = Manifest::fromJson(json);
EXPECT_FALSE(manifest.has_value());
}

TEST(ManifestTest, FromJson_EmptyDependencies) {
const char* json = R"({
"manifestVersion": "0.1.0",
Expand All @@ -103,6 +125,7 @@ TEST(ManifestTest, FromJson_EmptyDependencies) {
"author": "",
"type": "",
"category": "",
"icon": "",
"dependencies": [],
"main": {}
})";
Expand All @@ -127,6 +150,7 @@ TEST(ManifestTest, ToJson_Roundtrip) {
EXPECT_EQ(parsed->manifestVersion, original->manifestVersion);
EXPECT_EQ(parsed->name, original->name);
EXPECT_EQ(parsed->version, original->version);
EXPECT_EQ(parsed->icon, original->icon);
EXPECT_EQ(parsed->main, original->main);
}

Expand Down