├── .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 |
--------------------------------------------------------------------------------