Skip to content

Commit b4007cf

Browse files
committed
wip
0 parents  commit b4007cf

File tree

8 files changed

+305
-0
lines changed

8 files changed

+305
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
artifacts
2+
.idea

.gitmodules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[submodule "depot_tools"]
2+
path = depot_tools
3+
url = https://chromium.googlesource.com/chromium/tools/depot_tools
4+
[submodule "skia"]
5+
path = skia
6+
url = https://github.com/google/skia.git

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
depot_tools and skia contain Skia/depot_tools sources, they are too large to analyze properly, you are interested in scripts dir

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Skia Builder
2+
3+
4+
This repository contains tools and scripts to build a static version of Skia library in a form that can be referenced
5+
from a regular CMake or Makefile project.
6+
7+
For Linux it uses our cross-compilation Docker image based on Clang 20 with various sysroots
8+
9+
## Building
10+
11+
### Initial preparations and Skia version
12+
13+
1) point `skia` submodule to the version of skia you need to build
14+
2) add `depot_tools` to your shell's PATH
15+
3) run `python3 tools/git-sync-deps
16+
17+
### Headers
18+
19+
Run `./scripts/build.py headers`
20+
21+
### Linux
22+
23+
Run `./scripts/build-linux.sh <arch>`, where `arch` is `x64`, `arm64` or `all`
24+
25+
## Usage
26+
27+
The scripts will produce a Skia build with static libraries in `artifacts/<os>_<arch>` with `skia.h` specific for that
28+
skia configuration. Skia headers extracted from Skia are in `artifacts/headers`. You need to include both of those in
29+
your include search path.
30+
31+
When linking your application, link with the static libraries from `artifacts/<os>_<arch>` (you might want to use `--Wl,--start-group` and `--Wl,--end-group` around those to resolve any circular dependencies).

depot_tools

Submodule depot_tools added at e38a4c4

scripts/build-linux.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#/bin/bash
2+
set -x
3+
set -e
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
cd $SCRIPT_DIR
6+
cd ..
7+
8+
IMAGE=clang-builder:latest
9+
docker run --rm -it \
10+
-u $(id -u):$(id -g) \
11+
-v "$(pwd)":"$(pwd)" \
12+
-w "$(pwd)" \
13+
"$IMAGE" \
14+
./scripts/build.py linux "$@"

scripts/build.py

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#!/usr/bin/python3
2+
3+
import os
4+
import json
5+
import shutil
6+
import subprocess
7+
import sys
8+
9+
def has_env_flag(name):
10+
return os.environ.get(name, "0") == "1"
11+
12+
def _format_scalar(value):
13+
# strings must be quoted and escaped, numbers/bools emitted as literals
14+
if isinstance(value, str):
15+
return json.dumps(value)
16+
if isinstance(value, bool):
17+
return "true" if value else "false"
18+
if isinstance(value, (int, float)):
19+
return str(value)
20+
# fallback to stringified-quoted
21+
return json.dumps(str(value))
22+
23+
24+
def _format_list(lst):
25+
# produce multiline list with each element on its own indented line and a trailing comma
26+
if not lst:
27+
return "[]"
28+
parts = ["["]
29+
for item in lst:
30+
parts.append(" " + _format_scalar(item) + ",")
31+
parts.append("]")
32+
return "\n".join(parts)
33+
34+
35+
def dict_to_gn(obj):
36+
lines = []
37+
for key, val in obj.items():
38+
if isinstance(val, (list, tuple)):
39+
formatted = _format_list(val)
40+
lines.append(f"{key} = {formatted}")
41+
else:
42+
lines.append(f"{key} = {_format_scalar(val)}")
43+
# separate entries with a blank line to match example formatting
44+
return "\n\n".join(lines)
45+
46+
def write_file(path, content):
47+
with open(path, "w") as f:
48+
f.write(content)
49+
50+
def read_all_lines(path):
51+
with open(path, "r") as f:
52+
return f.readlines()
53+
54+
def copy_preserving_dir(root: str, file: str, src_dir: str, dest_dir: str):
55+
src_path = os.path.join(root, file)
56+
rel_path = os.path.relpath(src_path, src_dir)
57+
dest_path = os.path.join(dest_dir, rel_path)
58+
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
59+
shutil.copy2(src_path, dest_path)
60+
61+
def copy_extension(src_dir, dest_dir, extension):
62+
# Copy all files with the given extension from src_dir to dest_dir
63+
for root, dirs, files in os.walk(src_dir):
64+
for file in files:
65+
if file.endswith(extension):
66+
copy_preserving_dir(root, file, src_dir, dest_dir)
67+
68+
def copy_includes(src_dir, dest_dir):
69+
for root, dirs, files in os.walk(src_dir):
70+
for file in files:
71+
if file.endswith('.h'):
72+
copy_preserving_dir(root, file, src_dir, dest_dir)
73+
74+
def copy_headers():
75+
artifacts_dir = os.path.join("..", "artifacts", "headers")
76+
for skiaDir in ["include", "modules", "src"]:
77+
copy_includes(skiaDir, os.path.join(artifacts_dir, skiaDir))
78+
79+
80+
def collect_defines(path):
81+
lines = read_all_lines(path)
82+
defines = []
83+
for line in lines:
84+
line = line.strip()
85+
if line.startswith("defines = "):
86+
parts = line[len("defines = "):].strip().split()
87+
for part in parts:
88+
if part.startswith("-D"):
89+
defines.append(part[2:])
90+
break
91+
return defines
92+
93+
def generate_skia_h(gn_dir):
94+
defines = collect_defines(os.path.join(gn_dir, "obj", "public_headers_warnings_check.ninja"))
95+
defines = [d for d in defines if d.startswith("SK_") or d.startswith("SKIA_")]
96+
97+
define_directives = [f"#define {d.replace('=', ' ')}" for d in defines]
98+
skia_h = read_all_lines(os.path.join(gn_dir, "gen", "skia.h"))
99+
100+
# insert lines from define_directives to skia_h before first line in skia_h that contains #include
101+
output_lines = []
102+
inserted = False
103+
for line in skia_h:
104+
if not inserted and line.strip().startswith("#include"):
105+
output_lines.extend(define_directives)
106+
output_lines.append("\n")
107+
inserted = True
108+
output_lines.append(line.strip())
109+
return "\n".join(output_lines)
110+
111+
def gen_linux(arch, self_contained, args):
112+
113+
# normalize to LLVM-style arch names
114+
if arch == "x64":
115+
llvm_arch = "x86_64"
116+
elif arch == "arm64":
117+
llvm_arch = "aarch64"
118+
elif arch == "arm":
119+
llvm_arch = "armv7a"
120+
else:
121+
raise ValueError(f"Unsupported architecture: {arch}")
122+
123+
llvm_target = llvm_arch + "-linux-gnu"
124+
125+
args.update({
126+
"skia_use_harfbuzz": False,
127+
"skia_use_icu": False,
128+
"skia_use_piex": True,
129+
# "skia_use_sfntly": False, Not supported anymore
130+
"skia_use_system_expat": False,
131+
"skia_use_system_freetype2": False,
132+
"skia_use_system_libjpeg_turbo": False,
133+
"skia_use_system_libpng": False,
134+
"skia_use_system_libwebp": False,
135+
"skia_use_system_zlib": False,
136+
"skia_use_vulkan": True,
137+
"skia_use_x11": False
138+
})
139+
140+
141+
args["cc"] = "clang"
142+
args["cxx"] = "clang++"
143+
args["target_os"] = "linux"
144+
args["target_cpu"] = arch
145+
args["extra_cflags"].extend([
146+
"--target=" + llvm_target,
147+
"--sysroot=/sysroots/" + llvm_target
148+
])
149+
args["extra_cflags_cc"].extend([
150+
"-stdlib=libc++"
151+
])
152+
args["extra_ldflags"].extend([
153+
"--target=" + llvm_target, "--sysroot=/sysroots/" + llvm_target,
154+
"-rtlib=compiler-rt",
155+
"-stdlib=libc++",
156+
"-rtlib=compiler-rt",
157+
"-fuse-ld=lld",
158+
"-lc++",
159+
"-lc++abi",
160+
"-lunwind",
161+
"-lm",
162+
"-lc"
163+
])
164+
165+
166+
def build_target(target_os, arch, self_contained, debug):
167+
output_name = f"{target_os}_{arch}"
168+
if debug:
169+
output_name += "_debug"
170+
gn_dir = os.path.join("out", output_name)
171+
artifacts_dir = os.path.join("..", "artifacts", output_name)
172+
173+
args = {
174+
"is_debug": debug,
175+
"is_official_build": not debug,
176+
"extra_asmflags": [],
177+
"skia_enable_tools": False,
178+
"extra_cflags": ["-ffunction-sections", "-fdata-sections", "-fno-rtti"],
179+
"extra_cflags_c": [],
180+
"extra_cflags_cc": [],
181+
"extra_ldflags": [],
182+
"skia_enable_skottie": True,
183+
}
184+
if target_os == "linux":
185+
gen_linux(arch, self_contained, args)
186+
else:
187+
print(f"Unsupported target OS: {target_os}", file=sys.stderr)
188+
sys.exit(1)
189+
190+
os.makedirs(gn_dir, exist_ok=True)
191+
192+
write_file(os.path.join(gn_dir, "args.gn"), dict_to_gn(args))
193+
194+
if not has_env_flag("DEBUG_SKIP_BUILD"):
195+
subprocess.run(['gn', 'gen', gn_dir], check = True)
196+
subprocess.run(['ninja', '-C', gn_dir], check = True)
197+
198+
199+
# recursively clear artifacts dir
200+
if os.path.exists(artifacts_dir):
201+
shutil.rmtree(artifacts_dir)
202+
203+
os.makedirs(artifacts_dir, exist_ok=True)
204+
205+
# Copy .a files from gn_dir to artifacts_dir/lib
206+
lib_dir = os.path.join(artifacts_dir, "lib")
207+
os.makedirs(lib_dir, exist_ok=True)
208+
copy_extension(gn_dir, lib_dir, ".a")
209+
copy_extension(gn_dir, lib_dir, ".lib")
210+
211+
write_file(os.path.join(artifacts_dir, "skia.h"), generate_skia_h(gn_dir))
212+
if has_env_flag("SKIA_BUILDER_SAVE_SPACE"):
213+
shutil.rmtree(gn_dir)
214+
215+
def main():
216+
argv = sys.argv[1:]
217+
218+
os.chdir("skia")
219+
220+
if argv[0] == "headers":
221+
copy_headers()
222+
sys.exit(0)
223+
224+
if len(argv) < 2:
225+
print(f"Usage: ", file=sys.stderr)
226+
print(f"{sys.argv[0]} <os> <arch> [--self-contained]", file=sys.stderr)
227+
print(f"{sys.argv[0]} headers", file=sys.stderr)
228+
sys.exit(2)
229+
230+
target_os = argv[0]
231+
arch = argv[1]
232+
args = argv[2:]
233+
self_contained = "--self-contained" in args
234+
debug = "--debug" in args
235+
236+
# prepend ./depot_tools to PATH
237+
depot_tools_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "depot_tools"))
238+
os.environ["PATH"] = depot_tools_path + os.pathsep + os.environ.get("PATH", "")
239+
240+
241+
242+
if arch == "all":
243+
for a in ["x64", "arm64"]:
244+
build_target(target_os, a, self_contained, debug)
245+
else:
246+
build_target(target_os, arch, self_contained, debug)
247+
248+
if __name__ == "__main__":
249+
main()

skia

Submodule skia added at f4ed99d

0 commit comments

Comments
 (0)