-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_processing_cpp_packaging.py
More file actions
executable file
·263 lines (220 loc) · 8.47 KB
/
Copy path_processing_cpp_packaging.py
File metadata and controls
executable file
·263 lines (220 loc) · 8.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
"""
_processing_cpp_packaging.py -- shared engine-file and example-program
content used by both generate_dragdrop.py and generate_cmake.py.
Not run directly. This exists so the two generator scripts don't each
carry their own copy of "which files make up the engine" or "what the
example sketches look like" -- there is exactly one place that lists
ENGINE_HEADERS/ENGINE_SOURCES and one place that defines each example
program, imported by both generators, so they can never drift apart
from each other on those specifics. Everything that's genuinely
PACKAGE-specific (the README, the build script, the CMakeLists.txt)
stays in each generator's own file, not here.
"""
import shutil
import sys
import zipfile
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
SRC_DIR = REPO_ROOT / "src"
DIST_DIR = REPO_ROOT / "dist"
# Every one of these must exist in src/ -- if the engine's file layout
# ever changes, check_source_layout() fails loudly rather than silently
# shipping a stale/incomplete package.
#
# Notably absent: Processing_api.h and Platform.h. Both exist in the real
# CppMode source tree, but neither is included by Processing.h or
# Processing.cpp -- the engine itself never needs them. Processing_api.h
# is only ever included by CODE THE TRANSPILER GENERATES (CppBuild.java
# writes that #include into every sketch it emits, to support the
# free-function setup()/draw() style); Platform.h isn't included by
# anything in src/ at all. A hand-written PApplet subclass -- which is
# the only way either of these packages is meant to be used -- needs
# neither, confirmed by actually compiling examples/ without them before
# removing them from this list.
ENGINE_HEADERS = [
"Processing.h",
"stb_image.h",
"stb_image_write.h",
"stb_truetype.h",
]
ENGINE_SOURCES = [
"Processing.cpp",
"Processing_defaults.cpp",
]
# Fallback only -- get_library_version() below reads the real value from
# mode.properties at generation time. This constant exists so a missing
# or unparseable mode.properties degrades to an obviously-a-fallback
# version rather than crashing the generator outright, since shipping a
# package without a version isn't worse than a hard failure here.
_FALLBACK_VERSION = "0.0.0"
def get_library_version() -> str:
"""Reads prettyVersion from mode.properties -- the same version
number CppMode itself reports inside the Processing IDE -- so the
standalone packages and the IDE plugin can never silently drift to
different version numbers from having two separately-maintained
sources of truth. Used for the CMake package's
write_basic_package_version_file() call, so find_package(processing_cpp 1.0)
-style version constraints (if anyone ever writes one) check against
a real, meaningful number instead of an arbitrary one."""
props_path = REPO_ROOT / "mode.properties"
try:
for line in props_path.read_text().splitlines():
line = line.strip()
if line.startswith("prettyVersion="):
value = line.split("=", 1)[1].strip()
if value:
return value
except OSError:
pass
print(
f"warning: could not read prettyVersion from {props_path} -- "
f"falling back to {_FALLBACK_VERSION}. The generated package's "
f"version number will not reflect the real CppMode version.",
file=sys.stderr,
)
return _FALLBACK_VERSION
def die(msg: str) -> None:
print(f"error: {msg}", file=sys.stderr)
sys.exit(1)
def check_source_layout() -> None:
missing = [f for f in ENGINE_HEADERS + ENGINE_SOURCES if not (SRC_DIR / f).is_file()]
if missing:
die(
"src/ is missing expected engine file(s): " + ", ".join(missing) + "\n"
"This script must be run from a CppMode checkout with an intact src/ directory."
)
def copy_engine_files(out_dir: Path) -> None:
include_dir = out_dir / "include"
src_out_dir = out_dir / "src"
include_dir.mkdir(parents=True, exist_ok=True)
src_out_dir.mkdir(parents=True, exist_ok=True)
for name in ENGINE_HEADERS:
shutil.copy2(SRC_DIR / name, include_dir / name)
for name in ENGINE_SOURCES:
shutil.copy2(SRC_DIR / name, src_out_dir / name)
def write_examples(out_dir: Path) -> None:
examples_dir = out_dir / "examples"
examples_dir.mkdir(parents=True, exist_ok=True)
(examples_dir / "bouncing_ball.cpp").write_text(EXAMPLE_BOUNCING_BALL)
embed_dir = examples_dir / "embedding"
embed_dir.mkdir(exist_ok=True)
(embed_dir / "app.h").write_text(EXAMPLE_EMBED_APP_H)
(embed_dir / "app.cpp").write_text(EXAMPLE_EMBED_APP_CPP)
(embed_dir / "main.cpp").write_text(EXAMPLE_EMBED_MAIN_CPP)
def zip_directory(src_dir: Path, zip_path: Path) -> Path:
"""Zips src_dir's contents under a top-level folder named after src_dir,
e.g. zipping .../dist/processing-cpp-dragdrop/ produces a zip where
every entry starts with processing-cpp-dragdrop/ -- so unzipping it
anywhere drops a single clean folder, not a pile of loose files."""
if zip_path.exists():
zip_path.unlink()
zip_path.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
for path in sorted(src_dir.rglob("*")):
if path.is_file():
zf.write(path, arcname=src_dir.name / path.relative_to(src_dir))
return zip_path
# =============================================================================
# Example programs -- identical content in both packages. Only the build
# instructions in each package's own README differ, which is genuinely
# package-specific, unlike the engine-usage code itself.
# =============================================================================
EXAMPLE_BOUNCING_BALL = '''\
// bouncing_ball.cpp -- a complete sketch, no Processing IDE involved.
// Build: see this package's README.md.
#include "Processing.h"
struct BouncingBall : public Processing::PApplet {
float x = 0, y = 0;
float vx = 180, vy = 140;
float radius = 30;
void settings() override {
size(640, 360);
}
void setup() override {
windowTitle("processing-cpp standalone example");
x = width / 2.0f;
y = height / 2.0f;
}
void draw() override {
x += vx * deltaTime;
y += vy * deltaTime;
if (x < radius || x > width - radius) vx = -vx;
if (y < radius || y > height - radius) vy = -vy;
background(20);
noStroke();
fill(255, 140, 0);
circle(x, y, radius * 2);
}
void keyPressed() override {
if (key == ' ') { vx = -vx; vy = -vy; }
}
};
int main() {
BouncingBall sketch;
sketch.run();
return 0;
}
'''
EXAMPLE_EMBED_APP_H = '''\
// app.h -- the "drop into an existing project" pattern: keep the PApplet
// subclass and the Processing.h include confined to app.cpp. The rest of
// your codebase only ever needs to see this plain function declaration.
#pragma once
void run_particle_view();
'''
EXAMPLE_EMBED_APP_CPP = '''\
// app.cpp -- the only file in this example that touches Processing.h.
#include "Processing.h"
#include "app.h"
#include <algorithm>
#include <vector>
using namespace Processing;
namespace {
struct Particle {
PVector pos;
PVector vel;
float life = 1.0f;
void draw() {
fill(255, 200, 60, life * 255);
circle(pos.x, pos.y, 8);
}
};
struct ParticleView : public PApplet {
std::vector<Particle> particles;
void settings() override { size(800, 500); }
void setup() override { windowTitle("processing-cpp -- embedded in an existing project"); }
void spawn(float x, float y) {
Particle p;
p.pos = PVector(x, y);
p.vel = PVector::random2D() * random(60.0f, 200.0f);
particles.push_back(p);
}
void draw() override {
if (isMousePressed()) spawn(mouseX, mouseY);
background(0);
noStroke();
for (auto& p : particles) {
p.pos += p.vel * deltaTime;
p.life -= deltaTime * 0.6f;
p.draw();
}
particles.erase(
std::remove_if(particles.begin(), particles.end(),
[](const Particle& p) { return p.life <= 0.0f; }),
particles.end());
}
};
} // namespace
void run_particle_view() {
ParticleView view;
view.run();
}
'''
EXAMPLE_EMBED_MAIN_CPP = '''\
// main.cpp -- knows nothing about Processing, PApplet, GLFW, or GLEW.
#include "app.h"
int main() {
run_particle_view();
return 0;
}
'''