diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cb7369..a2160ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -191,3 +191,27 @@ jobs: - name: Run API tests run: npm run test:api + + test-conan: + name: Conan Package Test + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Conan + run: | + pip install conan + conan profile detect --force + + - name: Test Conan package + run: conan create . --build=missing diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a44ca73..5d029ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -273,3 +273,57 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 + + publish-conan: + name: Publish to Conan + needs: [build-matrix, create-release] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + strategy: + matrix: + include: + - os: Linux + arch: x86_64 + - os: Linux + arch: armv8 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Conan + run: | + pip install conan + conan profile detect --force + + - name: Extract version from tag + id: version + run: | + TAG="${{ github.ref_name }}" + VERSION=${TAG#v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create Conan package + run: | + conan create . --build=missing + + - name: Upload Conan package artifact + uses: actions/upload-artifact@v4 + with: + name: conan-package-${{ matrix.os }}-${{ matrix.arch }} + path: ~/.conan2/p/ + + # Publish to a custom Conan remote (requires CONAN_REMOTE_URL and CONAN_API_KEY secrets) + - name: Publish to Conan remote + if: ${{ vars.CONAN_REMOTE_URL != '' }} + env: + CONAN_REMOTE_URL: ${{ vars.CONAN_REMOTE_URL }} + CONAN_API_KEY: ${{ secrets.CONAN_API_KEY }} + run: | + conan remote add publish "$CONAN_REMOTE_URL" + conan remote login publish -p "$CONAN_API_KEY" ci + conan upload "ipv6-parse/${{ steps.version.outputs.version }}" -r publish --confirm diff --git a/.gitignore b/.gitignore index 7cb8015..4735fdd 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,7 @@ install_manifest.txt # pkg-config generated file (but not .pc.in templates) ipv6-parse.pc + +# Conan +CMakeUserPresets.json +test_package/build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 263a2dc..2d25648 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,8 +81,16 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) file(GLOB ipv6_sources "ipv6.h" "ipv6.c" ${IPV6_CONFIG_HEADER_PATH}/ipv6_config.h) +option(IPV6_CONAN_BUILD "Building with Conan package manager" OFF) + if (MSVC) - set(ipv6_target_compile_flags "/MTd /Wall /WX /wd5045 /ZI /Od /D_NO_CRT_STDIO_INLINE=1") + if (IPV6_CONAN_BUILD) + # When building with Conan, use minimal flags and let Conan control runtime + # Disable C4711 (auto inline) and C5045 (Spectre) warnings + set(ipv6_target_compile_flags "/W4 /wd4711 /wd5045") + else () + set(ipv6_target_compile_flags "/MTd /Wall /WX /wd5045 /ZI /Od /D_NO_CRT_STDIO_INLINE=1") + endif () else () set(ipv6_target_compile_flags "-Wall -Werror -Wno-long-long -pedantic -std=c99 -Wno-unused-but-set-variable") if (ENABLE_COVERAGE) diff --git a/README_CONAN.md b/README_CONAN.md new file mode 100644 index 0000000..b98411d --- /dev/null +++ b/README_CONAN.md @@ -0,0 +1,137 @@ +# Conan Package for ipv6-parse + +This document describes how to use the ipv6-parse library with [Conan](https://conan.io/), the C/C++ package manager. + +## Quick Start + +### Installing from Local Source + +```bash +# Build and install to local cache +conan create . --build=missing + +# Or export without building +conan export . +``` + +### Using in Your Project + +Add ipv6-parse to your `conanfile.txt`: + +```ini +[requires] +ipv6-parse/1.2.1 + +[generators] +CMakeDeps +CMakeToolchain +``` + +Or in your `conanfile.py`: + +```python +from conan import ConanFile + +class MyProjectConan(ConanFile): + requires = "ipv6-parse/1.2.1" + generators = "CMakeDeps", "CMakeToolchain" +``` + +### CMake Integration + +In your `CMakeLists.txt`: + +```cmake +cmake_minimum_required(VERSION 3.12) +project(myproject C) + +find_package(ipv6-parse REQUIRED) + +add_executable(myapp main.c) +target_link_libraries(myapp ipv6-parse::ipv6-parse) +``` + +### Building Your Project + +```bash +# Install dependencies +conan install . --output-folder=build --build=missing + +# Configure with CMake +cmake -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake + +# Build +cmake --build build +``` + +## Package Options + +| Option | Default | Description | +|----------|---------|-----------------------------------| +| `shared` | `False` | Build shared library | +| `fPIC` | `True` | Generate position-independent code | + +### Example: Building Shared Library + +```bash +conan create . -o ipv6-parse/*:shared=True --build=missing +``` + +## Example Usage + +```c +#include +#include +#include + +int main(void) { + ipv6_address_full_t addr; + const char *input = "2001:db8::1"; + + if (ipv6_from_str(input, strlen(input), &addr)) { + char buffer[IPV6_STRING_SIZE]; + ipv6_to_str(&addr, buffer, sizeof(buffer)); + printf("Parsed: %s\n", buffer); + } + return 0; +} +``` + +## Development + +### Running Tests + +The package includes a test consumer in `test_package/` that verifies the package works correctly: + +```bash +conan create . --build=missing +``` + +### Supported Platforms + +- Linux (GCC, Clang) +- macOS (Apple Clang) +- Windows (MSVC) + +### Conan 2.x Compatibility + +This package is designed for Conan 2.x and uses the modern Conan API. + +## CI/CD Integration + +The Conan package is automatically tested and published via GitHub Actions: + +- **CI Testing**: Every push and PR runs `conan create` on Linux, macOS, and Windows +- **Release Publishing**: Tagged releases (e.g., `v1.2.1`) trigger automatic Conan package creation + +### Publishing to a Custom Remote + +To publish releases to your own Conan remote, configure these GitHub repository settings: + +1. **Variables** (Settings > Secrets and variables > Actions > Variables): + - `CONAN_REMOTE_URL`: Your Conan remote URL (e.g., `https://your-artifactory.com/artifactory/api/conan/conan-local`) + +2. **Secrets** (Settings > Secrets and variables > Actions > Secrets): + - `CONAN_API_KEY`: API key or token for authentication + +The publish step will be skipped if `CONAN_REMOTE_URL` is not configured. diff --git a/conanfile.py b/conanfile.py index d2e85e1..809550e 100644 --- a/conanfile.py +++ b/conanfile.py @@ -32,18 +32,17 @@ class Ipv6ParseConan(ConanFile): "cmake/*", "ipv6.c", "ipv6.h", - "ipv6_wasm.c", - "cmdline.c", - "LICENSE", - "README.md", + "ipv6_config.h.in", "ipv6-parse.pc.in", - "cmake/ipv6-parse-config.cmake.in", + "LICENSE", + "README.md", # Required by CPack in CMakeLists.txt ) def set_version(self): """Extract version from CMakeLists.txt""" content = load(self, os.path.join(self.recipe_folder, "CMakeLists.txt")) - version_match = re.search(r"project\(ipv6-parse VERSION ([0-9.]+)", content) + # CMakeLists.txt uses project(ipv6 VERSION x.y.z) + version_match = re.search(r"project\(ipv6 VERSION \"?([0-9.]+)\"?", content) if version_match: self.version = version_match.group(1) else: @@ -69,6 +68,8 @@ def generate(self): tc.variables["BUILD_SHARED_LIBS"] = self.options.shared tc.variables["ENABLE_COVERAGE"] = False tc.variables["PARSE_TRACE"] = False + # Let Conan control MSVC runtime settings + tc.variables["IPV6_CONAN_BUILD"] = True tc.generate() def build(self): @@ -77,29 +78,19 @@ def build(self): cmake.build() def package(self): - cmake = CMake(self) - cmake.install() - - # Copy license copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses")) - - # Copy documentation - copy(self, "README*.md", - src=self.source_folder, - dst=os.path.join(self.package_folder, "docs")) + cmake = CMake(self) + cmake.install() def package_info(self): self.cpp_info.libs = ["ipv6-parse"] self.cpp_info.includedirs = ["include"] - # Set component for pkg-config + # Set properties for pkg-config self.cpp_info.set_property("pkg_config_name", "ipv6-parse") - # Set component for CMake + # Set properties for CMake find_package self.cpp_info.set_property("cmake_file_name", "ipv6-parse") self.cpp_info.set_property("cmake_target_name", "ipv6-parse::ipv6-parse") - - # Add bin folder to PATH for CLI tool - self.cpp_info.bindirs = ["bin"] diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 0000000..c47ec64 --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.12) +project(test_ipv6_parse C) + +find_package(ipv6-parse REQUIRED) + +add_executable(test_ipv6_parse test_ipv6_parse.c) +target_link_libraries(test_ipv6_parse ipv6-parse::ipv6-parse) diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 0000000..1115842 --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,25 @@ +import os +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout +from conan.tools.build import can_run + + +class Ipv6ParseTestConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + + def requirements(self): + self.requires(self.tested_reference_str) + + def layout(self): + cmake_layout(self) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + if can_run(self): + cmd = os.path.join(self.cpp.build.bindir, "test_ipv6_parse") + self.run(cmd, env="conanrun") diff --git a/test_package/test_ipv6_parse.c b/test_package/test_ipv6_parse.c new file mode 100644 index 0000000..a1dc2a9 --- /dev/null +++ b/test_package/test_ipv6_parse.c @@ -0,0 +1,18 @@ +#include +#include +#include + +int main(void) { + ipv6_address_full_t addr; + const char *test_addr = "2001:db8::1"; + + if (ipv6_from_str(test_addr, strlen(test_addr), &addr)) { + char buffer[IPV6_STRING_SIZE]; + ipv6_to_str(&addr, buffer, sizeof(buffer)); + printf("Successfully parsed IPv6 address: %s\n", buffer); + return 0; + } + + printf("Failed to parse IPv6 address\n"); + return 1; +}