From d7be5f9f9ff701f8b3f502b2f4500ca8bde44c0c Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 4 Nov 2024 07:32:53 -0500 Subject: [PATCH] Change CI to create Debian Package to Release (#2521) * Updating CI to create Debian package and version is assigned by tag version. Also updating release CI to not use end-of-life workflows * Clear up usage of static libraries. - Python bindings only use the dynamic lib. But built and copied the static ones sometimes nonetheless. - Add toggles to build only static, static/dyn or only dynamic. --------- Co-authored-by: Rot127 --- .dockerignore | 21 +++++++++ .gitattributes | 3 ++ .github/workflows/build_release.yml | 50 +++++++++++++-------- CMakeLists.txt | 54 +++++++++++++++++------ bindings/python/setup.py | 16 ++----- debian/.gitignore | 2 + debian/Dockerfile | 61 ++++++++++++++++++++++++++ debian/check_capstone.sh | 68 +++++++++++++++++++++++++++++ debian/control | 8 ++++ debian/postinst | 6 +++ debian/setup.sh | 53 ++++++++++++++++++++++ nmake.bat | 34 --------------- 12 files changed, 298 insertions(+), 78 deletions(-) create mode 100644 .dockerignore create mode 100644 debian/.gitignore create mode 100644 debian/Dockerfile create mode 100644 debian/check_capstone.sh create mode 100644 debian/control create mode 100644 debian/postinst create mode 100644 debian/setup.sh delete mode 100644 nmake.bat diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..77466db19 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +# Ignore source control directories +.git +.svn + +# Ignore build directories +build +dist + +# Ignore dependency directories +node_modules +vendor + +# Ignore temporary files +*.log +*.tmp + +# Ignore environment files +.env + +# Ignore tests +tests \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 03e638de6..e7ea118ba 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,4 @@ /arch/**/*.inc linguist-language=C + +# Ensure shell scripts have LF line endings +*.sh text eol=lf \ No newline at end of file diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 5859fabf2..1e66ef989 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -11,9 +11,32 @@ jobs: name: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true + + - name: Make setup.sh and check_capstone.sh are executable + run: | + chmod +x debian/setup.sh + chmod +x debian/check_capstone.sh + + - name: Build Debian Package + working-directory: ./debian + run: ./setup.sh ${{ github.event.release.tag_name }} + + - name: Run sanity checks on the Debian package + working-directory: ./debian + run: | + ./check_capstone.sh ./libcapstone-dev.deb ${{ github.event.release.tag_name }} + + - name: Upload debian package to release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.event.release.tag_name }} + files: | + ./debian/*.deb - name: archive id: archive @@ -27,24 +50,15 @@ jobs: TARBALL=$PKGNAME.tar.xz tar cJf $TARBALL $PKGNAME sha256sum $TARBALL > $SHASUM - echo "::set-output name=tarball::$TARBALL" - echo "::set-output name=shasum::$SHASUM" - - name: upload tarball - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./${{ steps.archive.outputs.tarball }} - asset_name: ${{ steps.archive.outputs.tarball }} - asset_content_type: application/gzip + echo "tarball=$TARBALL" >> $GITHUB_OUTPUT + echo "shasum=$SHASUM" >> $GITHUB_OUTPUT - - name: upload shasum - uses: actions/upload-release-asset@v1 + - name: Upload tarball and shasum to release + uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./${{ steps.archive.outputs.shasum }} - asset_name: ${{ steps.archive.outputs.shasum }} - asset_content_type: text/plain + tag_name: ${{ github.event.release.tag_name }} + files: | + ${{ steps.archive.outputs.tarball }} + ${{ steps.archive.outputs.shasum }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 345740c99..e7439379e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,8 @@ endif() # to configure the options specify them in the command line or change them in the cmake UI. # Don't edit the makefile! option(BUILD_SHARED_LIBS "Build shared library" OFF) -option(CAPSTONE_BUILD_STATIC_RUNTIME "Embed static runtime" ${BUILD_SHARED_LIBS}) +option(BUILD_STATIC_LIBS "Build static library" ON) +option(BUILD_STATIC_RUNTIME "Embed static MSVC runtime (Windows only). Always set if BUILD_SHARED_LIBS=ON" ${BUILD_SHARED_LIBS}) option(CAPSTONE_BUILD_MACOS_THIN "Disable universal2 builds on macOS" OFF) option(CAPSTONE_BUILD_DIET "Build diet library" OFF) option(CAPSTONE_BUILD_LEGACY_TESTS "Build legacy tests" ${PROJECT_IS_TOP_LEVEL}) @@ -69,6 +70,10 @@ option(CAPSTONE_INSTALL "Generate install target" ${PROJECT_IS_TOP_LEVEL}) option(ENABLE_ASAN "Enable address sanitizer" OFF) option(ENABLE_COVERAGE "Enable test coverage" OFF) +if (NOT BUILD_SHARED_LIBS AND NOT BUILD_STATIC_LIBS) + FATAL_ERROR("BUILD_SHARED_LIBS and BUILD_STATIC_LIBS are both unset. Nothing to build.") +endif() + if (ENABLE_ASAN) message("Enabling ASAN") add_definitions(-DASAN_ENABLED) @@ -154,7 +159,7 @@ if(CAPSTONE_DEBUG OR CMAKE_BUILD_TYPE STREQUAL "Debug") endif() # Force static runtime libraries -if(CAPSTONE_BUILD_STATIC_RUNTIME) +if(BUILD_STATIC_RUNTIME) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() @@ -761,19 +766,34 @@ set(ALL_HEADERS set_property(GLOBAL PROPERTY VERSION ${PROJECT_VERSION}) ## targets -add_library(capstone ${ALL_SOURCES} ${ALL_HEADERS}) -add_library(capstone::capstone ALIAS capstone) +add_library(capstone OBJECT ${ALL_SOURCES} ${ALL_HEADERS}) +set_property(TARGET capstone PROPERTY C_STANDARD 99) target_include_directories(capstone PUBLIC $ ) -set_property(TARGET capstone PROPERTY C_STANDARD 99) + +if(BUILD_STATIC_LIBS) + add_library(capstone_static STATIC $) + # Use normal capstone name. Otherwise we get libcapstone_static.a + set_target_properties(capstone_static PROPERTIES OUTPUT_NAME "capstone") + target_include_directories(capstone_static PUBLIC + $ + ) +endif() if(BUILD_SHARED_LIBS) - target_compile_definitions(capstone PUBLIC CAPSTONE_SHARED) - set_target_properties(capstone PROPERTIES + set_property(TARGET capstone PROPERTY POSITION_INDEPENDENT_CODE 1) + add_library(capstone_shared SHARED $) + # Use normal capstone name. Otherwise we get libcapstone_shared.so + set_target_properties(capstone_shared PROPERTIES OUTPUT_NAME "capstone") + set_target_properties(capstone_shared PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) + target_include_directories(capstone_shared PUBLIC + $ + ) + target_compile_definitions(capstone PUBLIC CAPSTONE_SHARED) endif() # Fuzzer if this is moved to it's own CMakeLists.txt (as it should be) @@ -878,12 +898,20 @@ if(CAPSTONE_INSTALL) DESTINATION ${CAPSTONE_CMAKE_CONFIG_INSTALL_DIR} ) - install(TARGETS capstone - EXPORT capstone-targets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + if(BUILD_SHARED_LIBS) + set(LIB_INSTALL_TARGETS capstone_shared) + endif() + + if (BUILD_STATIC_LIBS) + set(LIB_INSTALL_TARGETS ${LIB_INSTALL_TARGETS} capstone_static) + endif() + + install(TARGETS ${LIB_INSTALL_TARGETS} + EXPORT capstone-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) install(EXPORT capstone-targets diff --git a/bindings/python/setup.py b/bindings/python/setup.py index e96a4589e..6dbc83b9b 100755 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -62,15 +62,12 @@ else: if SYSTEM == 'darwin': VERSIONED_LIBRARY_FILE = "libcapstone.{PKG_MAJOR}.dylib".format(**VERSION_DATA) LIBRARY_FILE = "libcapstone.dylib" - STATIC_LIBRARY_FILE = 'libcapstone.a' elif SYSTEM in ('win32', 'cygwin'): VERSIONED_LIBRARY_FILE = "capstone.dll" LIBRARY_FILE = "capstone.dll" - STATIC_LIBRARY_FILE = None else: VERSIONED_LIBRARY_FILE = "libcapstone.so.{PKG_MAJOR}".format(**VERSION_DATA) LIBRARY_FILE = "libcapstone.so" - STATIC_LIBRARY_FILE = 'libcapstone.a' def clean_bins(): @@ -123,12 +120,9 @@ def build_libraries(): shutil.copytree(os.path.join(BUILD_DIR, 'include', 'capstone'), os.path.join(HEADERS_DIR, 'capstone')) # if prebuilt libraries are available, use those and cancel build - if os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE)) and \ - (not STATIC_LIBRARY_FILE or os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE))): + if os.path.exists(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE)): logger.info('Using prebuilt libraries') shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', LIBRARY_FILE), LIBS_DIR) - if STATIC_LIBRARY_FILE is not None: - shutil.copy(os.path.join(ROOT_DIR, 'prebuilt', STATIC_LIBRARY_FILE), LIBS_DIR) return os.chdir(BUILD_DIR) @@ -141,8 +135,8 @@ def build_libraries(): os.chdir("build_py") print("Build Directory: {}\n".format(os.getcwd())) # Only build capstone.dll / libcapstone.dylib - if SYSTEM == "win32": - os.system('cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCAPSTONE_BUILD_LEGACY_TESTS=OFF -DCAPSTONE_BUILD_CSTOOL=OFF -G "NMake Makefiles" ..') + if SYSTEM in ('win32', 'cygwin'): + os.system('cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DBUILD_STATIC_LIBS=OFF -DCAPSTONE_BUILD_LEGACY_TESTS=OFF -DCAPSTONE_BUILD_CSTOOL=OFF -G "NMake Makefiles" ..') elif 'AFL_NOOPT' in os.environ: # build for test_corpus os.system('cmake -DBUILD_SHARED_LIBS=ON -DCAPSTONE_BUILD_LEGACY_TESTS=OFF -DCAPSTONE_BUILD_CSTOOL=OFF ..') @@ -151,10 +145,6 @@ def build_libraries(): os.system("cmake --build .") shutil.copy(VERSIONED_LIBRARY_FILE, os.path.join(LIBS_DIR, LIBRARY_FILE)) - - # only copy static library if it exists (it's a build option) - if STATIC_LIBRARY_FILE and os.path.exists(STATIC_LIBRARY_FILE): - shutil.copy(STATIC_LIBRARY_FILE, LIBS_DIR) os.chdir(cwd) diff --git a/debian/.gitignore b/debian/.gitignore new file mode 100644 index 000000000..c636824af --- /dev/null +++ b/debian/.gitignore @@ -0,0 +1,2 @@ +*.deb +*.txt \ No newline at end of file diff --git a/debian/Dockerfile b/debian/Dockerfile new file mode 100644 index 000000000..39d784d43 --- /dev/null +++ b/debian/Dockerfile @@ -0,0 +1,61 @@ +ARG VERSION="" + +# Assume this is run from capstone/debian directory +# Run in the root of the repo +# docker build -f ./debian/Dockerfile -t packager . +FROM debian:bookworm-slim + +# Install necessary tools for packaging +RUN apt-get -qq update && \ + DEBIAN_FRONTEND=noninteractive apt-get -qq install -y \ + fakeroot dpkg-dev dos2unix cmake + +# Copy your project files into the container +RUN mkdir /capstone +COPY . /capstone +WORKDIR /capstone/ + +# Using cmake, see BUILDING.md file +# For debug build change "Release" to "Debug" +RUN cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=1 +RUN cmake --build build + +# List files before cmake install +# RUN find / -type f > /before-install.txt + +# Run cmake install, by default everything goes into /usr/local +RUN cmake --install build + +# List files after cmake install +# RUN find / -type f > /after-install.txt + +# Make directories as needed +RUN mkdir -p /package-root/usr/local/include/capstone/ +RUN mkdir -p /package-root/usr/local/lib/pkgconfig/ +RUN mkdir -p /package-root/usr/local/bin/ + +# Copy /usr/local/include/capstone/ to /package-root/usr/local/include/capstone/ and all other cases +RUN cp -r /usr/local/include/capstone/* /package-root/usr/local/include/capstone/ +RUN cp -r /usr/local/lib/libcapstone* /package-root/usr/local/lib/ +RUN cp -r /usr/local/lib/pkgconfig/capstone* /package-root/usr/local/lib/pkgconfig/ +RUN cp -r /usr/local/bin/cstool /package-root/usr/local/bin/ + +# Create DEBIAN directory and control file +COPY ./debian/control /package-root/DEBIAN/control + +# Update capstone.pc file with the correct version and remove archs field +# Update control file with the correct version +ARG VERSION +RUN sed -i "s/^Version:.*/Version: ${VERSION}/" /package-root/DEBIAN/control +RUN sed -i "s/^Version:.*/Version: ${VERSION}/" /package-root/usr/local/lib/pkgconfig/capstone.pc +RUN sed -i "/^archs=/d" /package-root/usr/local/lib/pkgconfig/capstone.pc + +# Add postinst script to run ldconfig after installation +COPY ./debian/postinst /package-root/DEBIAN/postinst +RUN chmod 755 /package-root/DEBIAN/postinst + +# Build the package +RUN fakeroot dpkg-deb --build /package-root /libcapstone-dev.deb + +# The user can now extract the .deb file from the container with something like +# docker run --rm -v $(pwd):/out packager bash -c "cp /libcapstone-dev.deb /out" diff --git a/debian/check_capstone.sh b/debian/check_capstone.sh new file mode 100644 index 000000000..bd0e39183 --- /dev/null +++ b/debian/check_capstone.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Usage: ./check_capstone_pc.sh + +DEB_FILE=$1 +EXPECTED_VERSION=$2 + +# Check if the deb file exists +if [[ ! -f "$DEB_FILE" ]]; then + echo "Debian package file not found!" + exit 1 +fi + +# Create a temporary directory to extract the deb file +TEMP_DIR=$(mktemp -d) + +# Extract the deb file +dpkg-deb -x "$DEB_FILE" "$TEMP_DIR" + +# Path to the capstone.pc file +CAPSTONE_PC="$TEMP_DIR/usr/local/lib/pkgconfig/capstone.pc" + +# Check if the capstone.pc file exists +if [[ ! -f "$CAPSTONE_PC" ]]; then + echo "capstone.pc file not found in the package!" + rm -rf "$TEMP_DIR" + exit 1 +fi + +# Remove leading 'v' if present, e. g. v1.5.1 -> 1.5.1 +if [[ "$EXPECTED_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + EXPECTED_VERSION=${EXPECTED_VERSION:1} +fi + +# Check if the version follows the format X.Y.Z, e. g. 1.5.1 or 1.9.1 +if [[ ! "$EXPECTED_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: Version must be in the format X.Y.Z" + exit 1 +fi + + +# Check the version in the capstone.pc file +ACTUAL_VERSION=$(grep "^Version:" "$CAPSTONE_PC" | awk '{print $2}') +if [[ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]]; then + echo "Version mismatch! Expected: $EXPECTED_VERSION, Found: $ACTUAL_VERSION" + rm -rf "$TEMP_DIR" + exit 1 +fi + +# Check if libcapstone.a is included in the package +LIBCAPSTONE_A="$TEMP_DIR/usr/local/lib/libcapstone.a" +if [[ ! -f "$LIBCAPSTONE_A" ]]; then + echo "libcapstone.a not found in the package!" + rm -rf "$TEMP_DIR" + exit 1 +fi + +# Check if libcapstone.so is included in the package +LIBCAPSTONE_SO="$TEMP_DIR/usr/local/lib/libcapstone.so" +if [[ ! -f "$LIBCAPSTONE_SO" ]]; then + echo "libcapstone.so not found in the package!" + rm -rf "$TEMP_DIR" + exit 1 +fi + +echo "libcapstone-dev.deb file is correct." +rm -rf "$TEMP_DIR" +exit 0 \ No newline at end of file diff --git a/debian/control b/debian/control new file mode 100644 index 000000000..97e7aa26b --- /dev/null +++ b/debian/control @@ -0,0 +1,8 @@ +Package: capstone +Version: +Architecture: all +Maintainer: Rot127 +Description: Capstone is a lightweight multi-platform, multi-architecture disassembly framework. + Capstone supports the following frameworks; + Alpha, BPF, Ethereum VM, HPPA, LoongArch, M68K, M680X, Mips, MOS65XX, PPC, RISC-V(rv32G/rv64G), + SH, Sparc, SystemZ, TMS320C64X, TriCore, Webassembly, XCore and X86. diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 000000000..35ec39b8a --- /dev/null +++ b/debian/postinst @@ -0,0 +1,6 @@ +#!/bin/bash +# postinst script for capstone package +set -e + +# Update the shared library cache +ldconfig diff --git a/debian/setup.sh b/debian/setup.sh new file mode 100644 index 000000000..c02212ef0 --- /dev/null +++ b/debian/setup.sh @@ -0,0 +1,53 @@ +# !/bin/bash +set -eu + +# Function to get the current Ubuntu version +get_os_version() { + lsb_release -i -s 2>/dev/null +} + +# Check if the script is running in the ./debian folder +if [[ $(basename "$PWD") != "debian" ]]; then + echo "ERROR: Script must be run from the ./debian directory" + exit 1 +fi + +OS_VERSION=$(get_os_version) +if [[ "$OS_VERSION" != "Ubuntu" && "$OS_VERSION" != "Debian" ]]; then + echo "ERROR: OS is not Ubuntu or Debian and unsupported" + exit 1 +fi + +# Get the version number as an input +# Check if version argument is provided +if [[ $# -ne 1 ]]; then + echo "ERROR: Version argument is required" + exit 1 +fi + +# Get the version number as an input +version=$1 + +# Remove leading 'v' if present, e. g. v1.5.1 -> 1.5.1 +if [[ "$version" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + version=${version:1} +fi + +# Check if the version follows the format X.Y.Z, e. g. 1.5.1 or 1.9.1 +if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: Version must be in the format X.Y.Z" + exit 1 +fi + +# Now build the packager container from that +pushd ../ +docker build -f ./debian/Dockerfile -t packager --build-arg VERSION="${version}" . +popd + +# Copy deb file out of container to host +docker run --rm -v $(pwd):/out packager bash -c "cp /libcapstone-dev.deb /out" + +# Check which files existed before and after 'make install' was executed. +# docker run --rm -v $(pwd):/out packager bash -c "cp /before-install.txt /out" +# docker run --rm -v $(pwd):/out packager bash -c "cp /after-install.txt /out" +# diff before-install.txt after-install.txt \ No newline at end of file diff --git a/nmake.bat b/nmake.bat deleted file mode 100644 index 275396f4e..000000000 --- a/nmake.bat +++ /dev/null @@ -1,34 +0,0 @@ -:: Capstone disassembler engine (www.capstone-engine.org) -:: Build Capstone libs (capstone.dll & capstone.lib) on Windows with CMake & Nmake -:: By Nguyen Anh Quynh, Jorn Vernee, 2017, 2019 - -@echo off - -set flags="-DCMAKE_BUILD_TYPE=Release -DCAPSTONE_BUILD_STATIC_RUNTIME=ON" - -if "%1"=="ARM" set %arch%=ARM -if "%1"=="ARM64" set %arch%=ARM64 -if "%1"=="M68K" set %arch%=M68K -if "%1"=="MIPS" set %arch%=MIPS -if "%1"=="PowerPC" set %arch%=PPC -if "%1"=="Sparc" set %arch%=SPARC -if "%1"=="SystemZ" set %arch%=SYSZ -if "%1"=="XCore" set %arch%=XCORE -if "%1"=="x86" set %arch%=X86 -if "%1"=="TMS320C64x" set %arch%=TMS320C64X -if "%1"=="M680x" set %arch%=M680X -if "%1"=="EVM" set %arch%=EVM -if "%1"=="MOS65XX" set %arch%=MOS65XX -if "%1"=="WASM" set %arch%=WASM -if "%1"=="BPF" set %arch%=BPF -if "%1"=="RISCV" set %arch%=RISCV -if "%1"=="ALPHA" set %arch%=ALPHA -if "%1"=="HPPA" set %arch%=HPPA -if "%1"=="LOONGARCH" set %arch%=LOONGARCH -if "%1"=="XTENSA" set %arch%=XTENSA - -if not "%arch%"=="" set flags=%flags% and " -DCAPSTONE_ARCHITECTURE_DEFAULT=OFF -DCAPSTONE_%arch%_SUPPORT=ON" - -cmake %flags% -G "NMake Makefiles" .. -nmake -