From 9a0416d218818ac2f3cd25421f11a4816f8f29e5 Mon Sep 17 00:00:00 2001 From: Paul I Date: Fri, 9 Feb 2018 22:51:30 +0300 Subject: [PATCH] Introducing mesonbuild for Cutter (#314) * Introducing mesonbuild * appveyor.yml: Added meson builder * Cleanup Windows build scripts * Updated radare2 submodule * meson: Clone capstone before building r2 * Some appveyor.yml cleanup --- .appveyor.yml | 28 +++++++-- .gitignore | 4 +- build.bat | 27 ++++---- meson.py | 140 ++++++++++++++++++++++++++++++++++++++++++ prepare_r2.bat | 52 ++++++---------- radare2 | 2 +- src/meson.build | 51 +++++++++++++++ src/meson_options.txt | 6 ++ 8 files changed, 257 insertions(+), 53 deletions(-) create mode 100644 meson.py create mode 100644 src/meson.build create mode 100644 src/meson_options.txt diff --git a/.appveyor.yml b/.appveyor.yml index bd452f18..e636160d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -22,23 +22,43 @@ environment: QT64PATH: 'C:\Qt\5.10.0\msvc2015_64' VSVARSALLPATH: 'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat' matrix: + # Build: qmake vs2015 x86 shared - BITS: 32 + QMAKE: 1 + # Build: qmake vs2015 x64 shared - BITS: 64 + QMAKE: 1 + # Build: meson ninja x64 static + - BITS: 64 + ARCH: x64 + MESON: 1 + BACKEND: ninja + +install: + # Artifacts + - cmd: if defined MESON ( set "ARTIFACT_NAME=Cutter%BITS%_static" ) else ( set "ARTIFACT_NAME=Cutter%BITS%" ) + - cmd: if defined MESON ( set "ARTIFACT=dist%BITS%" ) else ( set "ARTIFACT=build%BITS%\cutter%BITS%" ) + # Meson specific + - cmd: if defined MESON ( if "%ARCH%" == "x64" ( set "PATH=%QT64PATH%\bin;%PATH%" ) else ( set "PATH=%QT32PATH%\bin;%PATH%" ) ) + - cmd: if defined MESON ( %PYTHON%\python.exe -m pip install meson ) + - cmd: if defined MESON ( if "%BACKEND%" == "ninja" ( powershell -Command wget %NINJA_URL% -OutFile ninja.zip && unzip ninja.zip ) ) before_build: - - cmd: prepare_r2.bat %BITS% + - cmd: if defined QMAKE ( prepare_r2.bat %BITS% ) + - cmd: if defined MESON ( git submodule update --init ) # Build config build_script: - - cmd: build.bat %BITS% + - cmd: if defined QMAKE ( build.bat %BITS% ) + - cmd: if defined MESON ( call "%VSVARSALLPATH%" %ARCH% && %PYTHON%\python.exe meson.py --dist=%ARTIFACT% --backend=%BACKEND% ) # Tests test: off # Artifacts artifacts: - - path: build%BITS%\cutter%BITS% - name: Cutter%BITS% + - path: "%ARTIFACT%" + name: "%ARTIFACT_NAME%" deploy: release: cutter-$(appveyor_build_version) diff --git a/.gitignore b/.gitignore index e7b5ed2b..d1eea7aa 100644 --- a/.gitignore +++ b/.gitignore @@ -72,8 +72,10 @@ debug/ /src/cutter_resource.rc #prepare_r2 -meson.py ninja.exe /dist32/ /dist64/ *.pdb + +#Mesonbuild +src/subprojects/ diff --git a/build.bat b/build.bat index 3a4f04ad..f6c821ae 100644 --- a/build.bat +++ b/build.bat @@ -5,33 +5,34 @@ IF NOT DEFINED QT32PATH SET QT32PATH=C:\Qt\5.9.2\msvc2015 IF NOT DEFINED QT64PATH SET QT64PATH=C:\Qt\5.9.2\msvc2015_64 IF NOT DEFINED VSVARSALLPATH SET VSVARSALLPATH=C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat -if "%1" == "32" ( +IF "%1" == "32" ( SET "PATH=%QT32PATH%\bin;%PATH%" CALL "%VSVARSALLPATH%" x86 SET MSBUILDPLATFORM=Win32 -) else if "%1" == "64" ( +) ELSE IF "%1" == "64" ( SET "PATH=%QT64PATH%\bin;%PATH%" CALL "%VSVARSALLPATH%" x64 SET MSBUILDPLATFORM=x64 -) else ( +) ELSE ( ECHO Usage: %0 {32^|64} - EXIT /B + EXIT /B 1 ) +SET BITS=%1 ECHO Preparing directory -RMDIR /S /Q build%1 -MKDIR build%1 -CD build%1 +RMDIR /S /Q build%BITS% +MKDIR build%BITS% +CD build%BITS% ECHO Building cutter qmake ..\src\cutter.pro -config release -tp vc -IF !ERRORLEVEL! NEQ 0 EXIT /B +IF !ERRORLEVEL! NEQ 0 EXIT /B 1 msbuild /m cutter.vcxproj /p:Configuration=Release;Platform=%MSBUILDPLATFORM% -IF !ERRORLEVEL! NEQ 0 EXIT /B +IF !ERRORLEVEL! NEQ 0 EXIT /B 1 ECHO Deploying cutter -MKDIR cutter%1 -MOVE release\cutter.exe cutter%1\cutter.exe -XCOPY /S ..\dist%1 cutter%1\ -windeployqt cutter%1\cutter.exe +MKDIR cutter%BITS% +MOVE release\cutter.exe cutter%BITS%\cutter.exe +XCOPY /S ..\dist%BITS% cutter%BITS%\ +windeployqt cutter%BITS%\cutter.exe CD .. diff --git a/meson.py b/meson.py new file mode 100644 index 00000000..ad3bb3ee --- /dev/null +++ b/meson.py @@ -0,0 +1,140 @@ +import argparse +import importlib.util +import logging +import os +import pprint +import subprocess +import sys + +VARS = {'QT':[], 'SOURCES':[], 'HEADERS':[], 'FORMS':[], 'RESOURCES':[], + 'VERSION':[], 'ICON':[]} + +ROOT = None +log = None +r2_meson_mod = None + +def import_r2_meson_mod(): + """Import radare2/sys/meson.py""" + global r2_meson_mod + folder = os.path.dirname(__file__) + r2_meson_path = os.path.join(folder, 'radare2', 'sys', 'meson.py') + r2_meson_spec = importlib.util.spec_from_file_location('meson', r2_meson_path) + r2_meson_mod = importlib.util.module_from_spec(r2_meson_spec) + r2_meson_spec.loader.exec_module(r2_meson_mod) + +def set_global_vars(): + global log + global ROOT + + ROOT = os.path.abspath(os.path.dirname(__file__)) + + logging.basicConfig(format='[%(name)s][%(levelname)s]: %(message)s', + level=logging.DEBUG) + log = logging.getLogger('cutter-meson') + log.debug('ROOT: %s', ROOT) + + r2_meson_mod.set_global_variables() + +def parse_qmake_file(): + log.info('Parsing qmake file') + with open(os.path.join(ROOT, 'src', 'cutter.pro')) as qmake_file: + lines = qmake_file.readlines() + var_name = None + end_of_def = True + for line in lines: + words = line.split() + if not words: + continue + if words[0].startswith('#'): + continue + if not var_name and words[0] in VARS: + var_name = words[0] + words = words[2:] + if not var_name: + continue + end_of_def = words[-1] != '\\' + if not end_of_def: + words = words[:-1] + for word in words: + VARS[var_name].append(word) + if end_of_def: + var_name = None + VARS['QT'] = list(map(str.title, VARS['QT'])) + log.debug('Variables: \n%s', pprint.pformat(VARS, compact=True)) + +def win_dist(args): + build = os.path.join(ROOT, args.dir) + dist = os.path.join(ROOT, args.dist) + os.makedirs(dist) + r2_meson_mod.copy(os.path.join(build, 'Cutter.exe'), dist) + log.debug('Deploying Qt5') + subprocess.call(['windeployqt', '--release', os.path.join(dist, 'Cutter.exe')]) + log.debug('Deploying libr2') + r2_meson_mod.win_dist_libr2(DIST=dist) + +def build(args): + r2_meson_mod.prepare_capstone() + cutter_builddir = os.path.join(ROOT, args.dir) + if not os.path.exists(cutter_builddir): + defines = [] + defines.append('-Dversion=%s' % VARS['VERSION'][0]) + defines.append('-Dqt_modules=%s' % ','.join(VARS['QT'])) + defines.append('-Dsources=%s' % ','.join(VARS['SOURCES'])) + defines.append('-Dheaders=%s' % ','.join(VARS['HEADERS'])) + defines.append('-Dui_files=%s' % ','.join(VARS['FORMS'])) + defines.append('-Dqresources=%s' % ','.join(VARS['RESOURCES'])) + r2_meson_mod.meson(os.path.join(ROOT, 'src'), cutter_builddir, + prefix=cutter_builddir, backend=args.backend, + release=True, shared=False, options=defines) + r2_meson_mod.build_sdb(args.backend, release=True) + log.info('Building cutter') + if args.backend == 'ninja': + r2_meson_mod.ninja(cutter_builddir) + else: + project = os.path.join(cutter_builddir, 'Cutter.sln') + r2_meson_mod.msbuild(project, '/m') + +def create_sp_dir(): + sp_dir = os.path.join(ROOT, 'src', 'subprojects') + sp_r2_dir = os.path.join(sp_dir, 'radare2') + if not os.path.exists(sp_r2_dir): + os.makedirs(sp_dir, exist_ok=True) + r2_dir = os.path.join(ROOT, 'radare2') + try: + os.symlink(r2_dir, sp_r2_dir, target_is_directory=True) + except OSError as e: + log.error('%s', e) + if os.name == 'nt': + log.info('Execute command as Administrator:\n' + 'MKLINK /D "%s" "%s"', sp_r2_dir, r2_dir) + sys.exit(1) + +def main(): + set_global_vars() + + parser = argparse.ArgumentParser(description='Meson script for Cutter') + parser.add_argument('--backend', choices=r2_meson_mod.BACKENDS, + default='ninja', help='Choose build backend') + parser.add_argument('--dir', default='build', + help='Destination build directory') + if os.name == 'nt': + parser.add_argument('--dist', help='dist directory') + args = parser.parse_args() + + if args.dist and os.path.exists(args.dist): + log.error('%s already exists', args.dist) + sys.exit(1) + + log.debug('Arguments: %s', args) + + create_sp_dir() + parse_qmake_file() + + build(args) + + if args.dist: + win_dist(args) + +import_r2_meson_mod() +if __name__ == '__main__': + main() diff --git a/prepare_r2.bat b/prepare_r2.bat index 9ffabb95..1e9f38e1 100644 --- a/prepare_r2.bat +++ b/prepare_r2.bat @@ -1,61 +1,45 @@ @ECHO OFF SETLOCAL ENABLEDELAYEDEXPANSION -SET BADARG=1 -FOR %%i IN ("32" "64") DO (IF "%1" == %%i SET BADARG=) -IF DEFINED BADARG ( - ECHO Usage: %0 [32^|64] - EXIT /B -) -SET BITS=%1 - FOR %%i IN (python.exe) DO (IF NOT DEFINED PYTHON SET PYTHON=%%~dp$PATH:i) IF NOT DEFINED PYTHON SET PYTHON=C:\Program Files\Python36 IF NOT DEFINED NINJA_URL SET NINJA_URL=https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-win.zip IF NOT DEFINED VSVARSALLPATH SET VSVARSALLPATH=C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat -IF NOT EXIST %PYTHON%\python.exe EXIT /B +IF "%1" == "32" ( + CALL "%VSVARSALLPATH%" x86 +) ELSE IF "%1" == "64" ( + CALL "%VSVARSALLPATH%" x64 +) ELSE ( + ECHO Usage: %0 {32^|64} + EXIT /B 1 +) +SET BITS=%1 -SET "PYTHONHOME=%PYTHON%" -SET "PATH=%CD%;%PYTHON%;%PYTHON%\Scripts;%PATH%" +SET "PATH=%CD%;%PYTHON%;%PATH%" git submodule update --init ECHO Downloading meson and ninja python -m pip install meson -IF !ERRORLEVEL! NEQ 0 EXIT /B +IF !ERRORLEVEL! NEQ 0 EXIT /B 1 IF NOT EXIST ninja.exe ( powershell -Command wget %NINJA_URL% -OutFile ninja.zip && powershell -Command Expand-Archive .\ninja.zip -DestinationPath .\ && DEL ninja.zip - IF !ERRORLEVEL! NEQ 0 EXIT /B + IF !ERRORLEVEL! NEQ 0 EXIT /B 1 ) -IF NOT "%BITS%" == "32" ( - SET VARSALL=x64 - CALL :BUILD - IF !ERRORLEVEL! NEQ 0 EXIT /B -) -IF NOT "%BITS%" == "64" ( - SET VARSALL=x86 - CALL :BUILD - IF !ERRORLEVEL! NEQ 0 EXIT /B -) - -ECHO Copying relevant files in cutter_win32 -XCOPY /S /Y dist%BITS%\include\libr cutter_win32\radare2\include\libr\ -EXIT /B - -:BUILD -ECHO Building radare2 (%VARSALL%) +ECHO Building radare2 (%BITS%) CD radare2 git clean -xfd -RMDIR /s /q ..\dist%BITS% -CALL "%VSVARSALLPATH%" %VARSALL% -python sys\meson.py --release --prefix="%CD%" --install=..\dist%BITS% --shared --copylib +RMDIR /S /Q ..\dist%BITS% +python sys\meson.py --release --install=..\dist%BITS% --shared --copylib IF !ERRORLEVEL! NEQ 0 EXIT /B 1 COPY /Y build\r_userconf.h ..\dist%BITS%\include\libr\ COPY /Y build\r_version.h ..\dist%BITS%\include\libr\ COPY /Y build\shlr\liblibr2sdb.a ..\dist%BITS%\r_sdb.lib CD .. COPY /Y dist%BITS%\*.lib cutter_win32\radare2\lib%BITS%\ -EXIT /B 0 + +ECHO Copying relevant files in cutter_win32 +XCOPY /S /Y dist%BITS%\include\libr cutter_win32\radare2\include\libr\ diff --git a/radare2 b/radare2 index 9aabb069..1b60dbd9 160000 --- a/radare2 +++ b/radare2 @@ -1 +1 @@ -Subproject commit 9aabb069398373fe5faa426193e9a1fe6197fc98 +Subproject commit 1b60dbd9e2be5aeee2b7ddd1a13f69d334bc87da diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 00000000..24847109 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,51 @@ +#TODO: icon +project('cutter', 'cpp', default_options: 'cpp_std=c++11') + +#TODO: add console option +console = false + +qt5_mod = import('qt5') + +version = get_option('version') + +qt_modules = get_option('qt_modules') +sources = get_option('sources') +headers = get_option('headers') +ui_files = get_option('ui_files') +qresources = get_option('qresources') + +qt5dep = dependency('qt5', modules: qt_modules) + +moc_files = qt5_mod.preprocess( + moc_headers: headers, + ui_files: ui_files, + qresources: qresources +) + +cpp = meson.get_compiler('cpp') + +platform_inc = [] +if host_machine.system() == 'windows' + add_project_arguments('-D_CRT_NONSTDC_NO_DEPRECATE', language: 'cpp') + add_project_arguments('-D_CRT_SECURE_NO_WARNINGS', language: 'cpp') + platform_inc = include_directories('../cutter_win32/include') + if not console + # Workaround for https://github.com/mesonbuild/meson/issues/2327 + qt_lib = run_command('qmake', '-query', 'QT_HOST_LIBS').stdout().strip() + add_project_link_arguments('@0@/qtmain.lib'.format(qt_lib), language: 'cpp') + endif +endif + +add_project_arguments('-DAPP_VERSION="@0@"'.format(version), language: 'cpp') + +r2 = subproject('radare2') +libr2_dep = r2.get_variable('libr2_dep') + +cutter_exe = executable( + 'Cutter', + moc_files, + gui_app: not console, + sources: sources, + include_directories: platform_inc, + dependencies: [libr2_dep, qt5dep], +) diff --git a/src/meson_options.txt b/src/meson_options.txt new file mode 100644 index 00000000..03bc58cb --- /dev/null +++ b/src/meson_options.txt @@ -0,0 +1,6 @@ +option('version', type : 'string') +option('qt_modules', type : 'array') +option('sources', type : 'array') +option('headers', type : 'array') +option('ui_files', type : 'array') +option('qresources', type : 'array')