diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..77466db199 --- /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 03e638de69..e7ea118ba2 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 5859fabf27..1e66ef9893 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 a0a70bb3ce..4b5eb46c10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -631,16 +631,23 @@ set(ALL_HEADERS set_property(GLOBAL PROPERTY VERSION ${PROJECT_VERSION}) ## targets -add_library(capstone ${ALL_SOURCES} ${ALL_HEADERS}) +add_library(capstone OBJECT ${ALL_SOURCES} ${ALL_HEADERS}) add_library(capstone::capstone ALIAS capstone) +add_library(capstone_static STATIC $) +# Use normal capstone name. Otherwise we get libcapstone_static.so +set_target_properties(capstone_static PROPERTIES OUTPUT_NAME "capstone") target_include_directories(capstone PUBLIC $ ) set_property(TARGET capstone PROPERTY C_STANDARD 99) 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") + target_compile_definitions(capstone_shared PUBLIC CAPSTONE_SHARED) + set_target_properties(capstone_shared PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) @@ -753,12 +760,18 @@ 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_static capstone_shared) + else() + set(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/debian/.gitignore b/debian/.gitignore new file mode 100644 index 0000000000..c636824af1 --- /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 0000000000..39d784d43b --- /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 0000000000..bd0e39183e --- /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 0000000000..97e7aa26b4 --- /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 0000000000..35ec39b8a2 --- /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 0000000000..c02212ef0b --- /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