├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── create_release.yml ├── .gitignore ├── .releasezri └── template.md ├── CHANGELOG.md ├── README.md ├── add ├── add.cpp └── add.h ├── buildzri.config.json ├── main.cpp ├── platform ├── linux.cpp └── linux.h ├── scripts ├── bz.py └── rz.py └── subtract ├── subtract.cpp └── subtract.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: shalithasuranga 4 | patreon: shalithasuranga 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | linux-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Build the sample program 16 | run: ./scripts/bz.py 17 | 18 | - name: Run the sample program 19 | run: ./bin/bzsample-linux_x64 20 | 21 | macos-test: 22 | runs-on: macos-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Build the sample program 27 | run: ./scripts/bz.py 28 | 29 | - name: Run the sample program 30 | run: ./bin/bzsample-mac_x64 31 | 32 | windows-test: 33 | runs-on: windows-latest 34 | steps: 35 | - uses: actions/checkout@v2 36 | 37 | - name: Build the sample program 38 | run: python ./scripts/bz.py 39 | 40 | - name: Run the sample program 41 | run: bin\bzsample-win_x64 42 | shell: cmd 43 | -------------------------------------------------------------------------------- /.github/workflows/create_release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | type: text 7 | description: 'Version number Eg: 4.2.0' 8 | 9 | jobs: 10 | create-release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Create Release Notes 17 | run: | 18 | chmod +x ./scripts/rz.py 19 | ./scripts/rz.py create ${{github.event.inputs.version}} 20 | 21 | - name: Commit and Push Changelog 22 | uses: EndBug/add-and-commit@v7.4.0 23 | with: 24 | default_author: github_actions 25 | message: 'Update changelog for v${{github.event.inputs.version}}' 26 | add: 'CHANGELOG.md' 27 | tag: v${{github.event.inputs.version}} 28 | 29 | - name: Create a GitHub release 30 | uses: ncipollo/release-action@v1 31 | with: 32 | tag: v${{github.event.inputs.version}} 33 | name: BuildZri v${{github.event.inputs.version}} released! 34 | bodyFile: ./.tmprz/release_notes.md 35 | 36 | - name: Clean Release Notes 37 | run: | 38 | ./scripts/rz.py cleanup 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /.releasezri/template.md: -------------------------------------------------------------------------------- 1 | {RZ_TOP} 2 | 3 | ## What's new 4 | {RZ_CHANGELOG} 5 | 6 | Update your `scripts/bz.py` to use v{RZ_VERSION} (Released on {RZ_DATE}). 7 | 8 | Get started: https://github.com/codezri/buildzri 9 | 10 |
11 | 12 | 13 | This release was auto-generated by [ReleaseZri {RZ_RZVERSION}](https://github.com/codezri/releasezri) :rocket: 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Add all code changes (features, deprecations, and enhancements) under the `Unreleased` topic to track changes for 4 | the next release. Once the changes are released, 5 | rename `Unreleased` topic with the new version tag. Finally, create a new `Unreleased` topic for future changes. 6 | 7 | ## Unreleased 8 | 9 | ## v1.0.0 10 | 11 | - Initial version 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | ____ _ _ _ ______ _ 3 | | _ \ (_) | | |___ / (_) 4 | | |_) |_ _ _| | __| | / / _ __ _ 5 | | _ <| | | | | |/ _` | / / | '__| | 6 | | |_) | |_| | | | (_| |/ /__| | | | 7 | |____/ \__,_|_|_|\__,_/_____|_| |_| 8 | 9 | BuildZri - A minimal build automation tool for C++ 10 | ``` 11 | 12 | Most C++ build automation tools come with a bit complex syntax and make simple projects complex. As a result, C++ programmers often try to write shell scripts for compilation, but then they have to maintain multiple scripts for each platform. 13 | 14 | BuildZri is a minimal cross-platform C++ build automation tool written in Python. It comes with a simple JSON-based configuration file with the features you need. 15 | 16 | Please check the [BuildZri documentation](https://codezri.org/docs/buildzri/intro) for more details. 17 | 18 | ## Features 19 | 20 | - Minimal JSON-based configuration 21 | - Supports, GNU C++, Clang, and MSVC compilers 22 | - Written in Python, works on any popular OS 23 | - Built for both developers and CI/CD servers 24 | - No installation required, it comes as a simple script 25 | 26 | 27 | ## Example build configuration 28 | 29 | ```json 30 | { 31 | "std": "c++17", 32 | "name": "BuildZri Sample", 33 | "version": "1.0.1", 34 | "output": "./bin/bzsample-${BZ_OS}_${BZ_ARCH}", 35 | "include": { 36 | "*": [ 37 | "." 38 | ] 39 | }, 40 | "source": { 41 | "*": [ 42 | "*.cpp", 43 | "add/*.cpp", 44 | "subtract/*.cpp" 45 | ], 46 | "linux": [ 47 | "platform/linux.cpp" 48 | ] 49 | }, 50 | "definitions": { 51 | "*": [ 52 | "BZ_TESTV=1", 53 | "BZ_PROJ_VERSION=\\\"${BZ_VERSION}\\\"" 54 | ] 55 | }, 56 | "options": { 57 | "linux": [ 58 | "-Os" 59 | ], 60 | "darwin": [ 61 | "-Os" 62 | ], 63 | "windows": [ 64 | "/EHsc", 65 | "/Os", 66 | "/link" 67 | ] 68 | } 69 | } 70 | ``` 71 | 72 | The above sample configuration generates the following binaries: 73 | 74 | - `./bin/bzsample-linux_x64` on x64 GNU/Linux machines 75 | - `./bin/bzsample-mac_x64` on x64/arm64 macOS machines 76 | - `./bin/bzsample-win_x64.exe` on x64 Windows machines 77 | 78 | This repository contains a simple C++ project that uses BuildZri as the build automation tool. 79 | 80 | ## License 81 | 82 | [MIT](LICENSE) 83 | -------------------------------------------------------------------------------- /add/add.cpp: -------------------------------------------------------------------------------- 1 | namespace add { 2 | 3 | int calc(int a, int b) { 4 | return a + b; 5 | } 6 | 7 | } 8 | -------------------------------------------------------------------------------- /add/add.h: -------------------------------------------------------------------------------- 1 | namespace add { 2 | 3 | int calc(int a, int b); 4 | 5 | } 6 | -------------------------------------------------------------------------------- /buildzri.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "std": "c++17", 3 | "name": "BuildZri Sample", 4 | "version": "1.0.1", 5 | "output": "./bin/bzsample-${BZ_OS}_${BZ_ARCH}", 6 | "include": { 7 | "*": [ 8 | "." 9 | ] 10 | }, 11 | "source": { 12 | "*": [ 13 | "*.cpp", 14 | "add/*.cpp", 15 | "subtract/*.cpp" 16 | ], 17 | "linux": [ 18 | "platform/linux.cpp" 19 | ] 20 | }, 21 | "definitions": { 22 | "*": [ 23 | "BZ_TESTV=1", 24 | "BZ_PROJ_VERSION=\\\"${BZ_VERSION}\\\"" 25 | ] 26 | }, 27 | "options": { 28 | "linux": [ 29 | "-Os" 30 | ], 31 | "darwin": [ 32 | "-Os" 33 | ], 34 | "windows": [ 35 | "/EHsc", 36 | "/Os", 37 | "/link" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "add/add.h" 4 | #include "subtract/subtract.h" 5 | 6 | using namespace std; 7 | 8 | int main() { 9 | cout << "5 + 10 = " << add::calc(5, 10) << endl; 10 | cout << "15 - 10 = " << subtract::calc(15, 10) << endl; 11 | 12 | #if defined(BZ_TESTV) 13 | cout << "BZ_TESTV is defined" << endl; 14 | #endif 15 | 16 | #if defined(BZ_PROJ_VERSION) 17 | cout << "Project version: v" << BZ_PROJ_VERSION << endl; 18 | #endif 19 | return 0; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /platform/linux.cpp: -------------------------------------------------------------------------------- 1 | namespace linux { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /platform/linux.h: -------------------------------------------------------------------------------- 1 | namespace linux { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /scripts/bz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import os 5 | import subprocess 6 | import platform 7 | import json 8 | import glob 9 | import argparse 10 | 11 | parser = argparse.ArgumentParser() 12 | parser.add_argument('--verbose', action='store_true') 13 | parser.add_argument('--target_arch', choices=['x64', 'arm64', 'armhf']) 14 | args = parser.parse_args() 15 | 16 | C = {} 17 | 18 | BZ_VERSION = '1.0.0' 19 | BZ_CONFIG_FILE = 'buildzri.config.json' 20 | BZ_OS = platform.system().lower() 21 | BZ_ISLINUX = BZ_OS == 'linux' 22 | BZ_ISDARWIN = BZ_OS == 'darwin' 23 | BZ_ISWIN = BZ_OS == 'windows' 24 | 25 | def get_arch(short_names = True): 26 | arch = platform.machine().lower() 27 | 28 | if short_names and (arch in ['x86_64', 'amd64']): 29 | arch = 'x64' 30 | 31 | if short_names and (arch in ['aarch64']): 32 | arch = 'arm64' 33 | 34 | if short_names and (arch in ['armv7l']): 35 | arch = 'armhf' 36 | 37 | return arch 38 | 39 | def get_target_arch(): 40 | if args.target_arch != None: 41 | return args.target_arch 42 | return get_arch() 43 | 44 | def get_os_shortname(): 45 | osnames = {'linux': 'linux', 'windows': 'win', 'darwin': 'mac'} 46 | return osnames[BZ_OS] 47 | 48 | def get_compiler(): 49 | compilers = {'linux': 'g++', 'windows': 'cl', 'darwin': 'c++'} 50 | return compilers[BZ_OS] + ' ' 51 | 52 | def get_latest_commit(): 53 | return subprocess.getoutput('git rev-parse HEAD').strip() 54 | 55 | def apply_template_vars(text): 56 | return text\ 57 | .replace('${BZ_OS}', get_os_shortname()) \ 58 | .replace('${BZ_VERSION}', C['version']) \ 59 | .replace('${BZ_ARCH}', get_arch()) \ 60 | .replace('${BZ_ARCHL}', get_arch(short_names = False)) \ 61 | .replace('${BZ_TARGET_ARCH}', get_target_arch()) \ 62 | .replace('${BZ_COMMIT}', get_latest_commit()) 63 | 64 | def get_target_name(): 65 | out_file = C['output'] 66 | 67 | out_file = apply_template_vars(out_file) 68 | 69 | if BZ_ISWIN: 70 | out_file += '.exe' 71 | 72 | return out_file 73 | 74 | def configure_vs_tools(): 75 | vsw_path = '' 76 | vsw_path_f = '%s\\Microsoft Visual Studio\\Installer\\vswhere.exe' 77 | for prog_path in [os.getenv('ProgramFiles(x86)'), os.getenv('ProgramFiles')]: 78 | path = vsw_path_f % prog_path 79 | if os.path.exists(path): 80 | vsw_path = path 81 | break 82 | 83 | if vsw_path == '': 84 | print('ERR: Unable to find vswhere.exe') 85 | sys.exit(1) 86 | 87 | if args.verbose: 88 | print('vswhere.exe path: %s' % vsw_path) 89 | 90 | vs_paths = subprocess\ 91 | .getoutput('"%s" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath' % vsw_path) \ 92 | .strip().split('\r\n') 93 | 94 | vs_path = vs_paths[-1] 95 | vs_devcmd_file = '%s\\Common7\\Tools\\vsdevcmd.bat' % vs_path 96 | 97 | if not os.path.exists(vs_devcmd_file): 98 | print('ERR: Unable to find VS dev command-line tools') 99 | sys.exit(1) 100 | 101 | return '"%s" -host_arch=%s -arch=%s' % (vs_devcmd_file, get_arch(), get_target_arch()) 102 | 103 | 104 | def get_std(): 105 | if 'std' not in C: 106 | return '' 107 | 108 | std_prefix = '--std=' 109 | if BZ_ISWIN: 110 | std_prefix = '/std:' 111 | return '%s%s ' % (std_prefix, C['std']) 112 | 113 | def get_source_files(): 114 | file_defs = ['*', BZ_OS] 115 | files = '' 116 | 117 | for file_def in file_defs: 118 | if file_def not in C['source']: 119 | continue 120 | for entry in C['source'][file_def]: 121 | glob_files = glob.glob(entry, recursive = True) 122 | if len(glob_files) > 0: 123 | files += ' '.join(glob_files) + ' ' 124 | 125 | return files 126 | 127 | def get_includes(): 128 | if 'include' not in C: 129 | return '' 130 | 131 | inc_defs = ['*', BZ_OS] 132 | incs = '' 133 | inc_prefix = '-I' 134 | if BZ_ISWIN: 135 | inc_prefix = '/I' 136 | 137 | for inc_def in inc_defs: 138 | if inc_def not in C['include']: 139 | continue 140 | for entry in C['include'][inc_def]: 141 | incs += '%s %s ' % (inc_prefix, entry) 142 | 143 | return incs 144 | 145 | def get_definitions(): 146 | if 'definitions' not in C: 147 | return '' 148 | 149 | defs = '' 150 | def_defs = ['*', BZ_OS] 151 | def_prefix = '-D' 152 | if BZ_ISWIN: 153 | def_prefix = '/D' 154 | 155 | for def_def in def_defs: 156 | if def_def not in C['definitions']: 157 | continue 158 | for entry in C['definitions'][def_def]: 159 | defs += '%s%s ' % (def_prefix, apply_template_vars(entry)) 160 | 161 | return defs 162 | 163 | def get_target(): 164 | if 'output' not in C: 165 | return '' 166 | 167 | out_prefix = '-o ' 168 | if BZ_ISWIN: 169 | out_prefix = '/OUT:' 170 | out_file = get_target_name() 171 | 172 | out_path = os.path.dirname(out_file) 173 | 174 | if out_path != '' and not os.path.isdir(out_path): 175 | os.makedirs(out_path, exist_ok = True) 176 | 177 | if os.path.exists(out_file): 178 | os.remove(out_file) 179 | 180 | return '%s%s ' % (out_prefix, out_file) 181 | 182 | def get_options(): 183 | if 'options' not in C: 184 | return '' 185 | 186 | opt_defs = ['*', BZ_OS] 187 | opts = '' 188 | 189 | for opt_def in opt_defs: 190 | if opt_def not in C['options']: 191 | continue 192 | for entry in C['options'][opt_def]: 193 | opts += '%s ' % apply_template_vars(entry) 194 | return opts 195 | 196 | def build_compiler_cmd(): 197 | 198 | arch = get_arch() 199 | target_arch = get_target_arch() 200 | 201 | cmd = '' 202 | if BZ_ISWIN: 203 | cmd += '%s && ' % configure_vs_tools() 204 | cmd += get_compiler() 205 | cmd += get_std() 206 | cmd += get_includes() 207 | cmd += get_source_files() 208 | cmd += get_definitions() 209 | cmd += get_options() 210 | cmd += get_target() 211 | 212 | if BZ_ISDARWIN: 213 | if target_arch == 'x64': 214 | cmd += '-arch x86_64 ' 215 | elif target_arch == 'arm64': 216 | cmd += '-arch arm64 ' 217 | else: 218 | print('ERR: Unsupported target architecture for macOS cross-compile: %s' % target_arch) 219 | sys.exit(1) 220 | else: 221 | if target_arch != arch: 222 | print('ERR: Unable to cross-compile: target %s host %s' % (target_arch, arch)) 223 | sys.exit(1) 224 | return cmd 225 | 226 | def compile(cmd): 227 | print('Compiling %s...' % C['name']) 228 | 229 | if args.verbose: 230 | print('Running command: %s' % cmd) 231 | 232 | exit_code = subprocess.call(cmd, shell = True) 233 | msg = '' 234 | if exit_code == 0: 235 | msg = 'OK: %s compiled into %s' % (C['name'], get_target_name()) 236 | else: 237 | msg = 'ERR: Unable to compile %s' % C['name'] 238 | 239 | print(msg) 240 | sys.exit(exit_code) 241 | 242 | def print_ascii_art(): 243 | print(''' 244 | ____ _ _ _ ______ _ 245 | | _ \ (_) | | |___ / (_) 246 | | |_) |_ _ _| | __| | / / _ __ _ 247 | | _ <| | | | | |/ _` | / / | '__| | 248 | | |_) | |_| | | | (_| |/ /__| | | | 249 | |____/ \__,_|_|_|\__,_/_____|_| |_| 250 | 251 | BuildZri v%s - A minimal build automation tool for C++ 252 | 253 | ''' % BZ_VERSION) 254 | 255 | if __name__ == '__main__': 256 | with open(BZ_CONFIG_FILE) as configFile: 257 | print_ascii_art() 258 | C = json.loads(configFile.read()) 259 | cmd = build_compiler_cmd() 260 | compile(cmd) 261 | -------------------------------------------------------------------------------- /scripts/rz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import os 5 | import shutil 6 | from datetime import datetime 7 | 8 | RZ_DIR = './.releasezri' 9 | TEMPLATE_FILE = './.releasezri/template.md' 10 | CHANGELOG_FILE = './CHANGELOG.md' 11 | TMP_DIR = './.tmprz' 12 | RELEASE_NOTE_FILE = './.tmprz/release_notes.md' 13 | RZ_VERSION = '1.3.0' 14 | VERSION = '' 15 | VERSION_WITH_V = '' 16 | 17 | def get_cli_option_value(option): 18 | for arg in sys.argv: 19 | if option in arg: 20 | return arg.split('=')[1] 21 | return '' 22 | 23 | def apply_notes_to_template(note): 24 | md = '' 25 | today = datetime.today() 26 | with open(TEMPLATE_FILE, 'r') as tf: 27 | md = tf.read() \ 28 | .replace('{RZ_RZVERSION}', RZ_VERSION) \ 29 | .replace('{RZ_VERSION}', VERSION) \ 30 | .replace('{RZ_DATE}', today.strftime('%Y-%m-%d')) \ 31 | .replace('{RZ_TIME}', today.strftime('%H:%M:%S')) \ 32 | .replace('{RZ_CHANGELOG}', note) \ 33 | .replace('{RZ_TOP}', get_cli_option_value('--top')) \ 34 | .strip() 35 | return md 36 | 37 | def parse_release_note(): 38 | note = '' 39 | collect = False 40 | updated_changelog = '' 41 | 42 | with open(CHANGELOG_FILE, 'r') as cf: 43 | for line in cf: 44 | if '## Unreleased' in line: 45 | collect = True 46 | continue 47 | if '## v' in line: 48 | break 49 | if collect: 50 | note += line 51 | return note 52 | 53 | def save_release_note(note): 54 | os.makedirs(TMP_DIR, exist_ok = True) 55 | 56 | with open(RELEASE_NOTE_FILE, 'w') as rf: 57 | rf.write(note) 58 | 59 | def update_changelog(): 60 | updated_changelog = '' 61 | pre_replace = '''## Unreleased 62 | 63 | ''' 64 | with open(CHANGELOG_FILE, 'r') as cf: 65 | replace_text = pre_replace + '## ' + VERSION_WITH_V 66 | updated_changelog = cf.read().replace('## Unreleased', replace_text) 67 | 68 | with open(CHANGELOG_FILE, 'w') as cf: 69 | cf.write(updated_changelog) 70 | 71 | def create_note(): 72 | print('INFO: Preparing release notes...') 73 | note = parse_release_note() 74 | if note.strip() == '': 75 | print('ERROR: No changelog so far.') 76 | sys.exit(1) 77 | print('---- Release note for %s (parsed) ----' % VERSION_WITH_V) 78 | print('----') 79 | 80 | note = apply_notes_to_template(note) 81 | print('---- Release note for %s (applied to template) ----' % VERSION_WITH_V) 82 | print(note) 83 | print('----') 84 | save_release_note(note) 85 | 86 | print('INFO: Updating change log...') 87 | update_changelog() 88 | 89 | print('OK: All done. Run "cleanup" command after using release note.') 90 | 91 | def cleanup(): 92 | shutil.rmtree(TMP_DIR) 93 | print('OK: All done.') 94 | 95 | def print_art(): 96 | print(''' 97 | ____ __ _____ _ 98 | / __ \___ / /__ ____ ________/__ / _____(_) 99 | / /_/ / _ \/ / _ \/ __ `/ ___/ _ \/ / / ___/ / 100 | / _, _/ __/ / __/ /_/ (__ ) __/ /__/ / / / 101 | /_/ |_|\___/_/\___/\__,_/____/\___/____/_/ /_/ 102 | ''') 103 | 104 | if __name__ == '__main__': 105 | print_art() 106 | if len(sys.argv) < 2: 107 | print('ERROR: Missing command (create or cleanup)') 108 | sys.exit(1) 109 | 110 | command = sys.argv[1] 111 | 112 | if command == 'create': 113 | if len(sys.argv) < 3: 114 | print('ERROR: Missing version argument') 115 | sys.exit(1) 116 | VERSION = sys.argv[2] 117 | VERSION_WITH_V = 'v' + VERSION 118 | create_note() 119 | 120 | elif command == 'cleanup': 121 | cleanup() 122 | 123 | else: 124 | print('ERROR: Invalid command!') 125 | -------------------------------------------------------------------------------- /subtract/subtract.cpp: -------------------------------------------------------------------------------- 1 | namespace subtract { 2 | 3 | int calc(int a, int b) { 4 | return a - b; 5 | } 6 | 7 | } 8 | -------------------------------------------------------------------------------- /subtract/subtract.h: -------------------------------------------------------------------------------- 1 | namespace subtract { 2 | 3 | int calc(int a, int b); 4 | 5 | } 6 | --------------------------------------------------------------------------------