Skip to content

Commit d522436

Browse files
committed
Python build script to make iOS JavaScriptCore.framework and wtf
TODO: Use libtool to link wtf into JavaScriptCore
1 parent dd02d33 commit d522436

File tree

2 files changed

+255
-0
lines changed

2 files changed

+255
-0
lines changed

make.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
import logging
5+
import os
6+
import tempfile
7+
import shutil
8+
import subprocess
9+
from xcodebuild import FrameworkBuild
10+
11+
"""Script to build JavaScriptCore.framework."""
12+
13+
__author__ = "Martijn The"
14+
__email__ = "martijn@getpebble.com"
15+
16+
17+
class PebbleKitiOSException (Exception):
18+
pass
19+
20+
21+
def build(out, derived_data_path):
22+
outdir = out if out else tempfile.mkdtemp()
23+
24+
wtf = FrameworkBuild("WTF/WTF.xcodeproj",
25+
"WTF iOS", "Production", outdir, name="wtf",
26+
derived_data_path=derived_data_path)
27+
wtf.build()
28+
29+
jsc = FrameworkBuild("JavaScriptCore/JavaScriptCore.xcodeproj",
30+
"JavaScriptCore iOS", "Production", outdir,
31+
name="JavaScriptCore",
32+
derived_data_path=derived_data_path)
33+
jsc.build()
34+
35+
return outdir
36+
37+
if __name__ == "__main__":
38+
parser = argparse.ArgumentParser(description='Build'
39+
' JavaScriptCore.framework')
40+
parser.add_argument('--out', default=None, type=str,
41+
help="Output directory. Defaults to a random' \
42+
' temporary folder.")
43+
parser.add_argument('--derived-data', default=None, type=str,
44+
help="Derived data directory. Defaults to a random' \
45+
' temporary folder.")
46+
parser.add_argument('--verbose', action='store_const', default=False,
47+
const=True, help="Enable verbose logging.")
48+
parser.add_argument('--no-open', action='store_const', default=True,
49+
const=False, help="Use `--no-open` to skip' \
50+
' revealing the built product in Finder.")
51+
args = parser.parse_args()
52+
53+
log_level = logging.INFO
54+
if args.verbose:
55+
log_level = logging.DEBUG
56+
logging.basicConfig(format='[%(levelname)-8s] %(message)s',
57+
level=log_level)
58+
59+
outdir = build(args.out, args.derived_data)
60+
61+
if args.no_open:
62+
os.system("open %s" % outdir)

xcodebuild.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#!/usr/bin/env python
2+
3+
import logging
4+
import os
5+
import re
6+
import shutil
7+
import subprocess
8+
import tempfile
9+
10+
"""Python wrapper around the xcodebuild command line program."""
11+
12+
__author__ = "Martijn The"
13+
__email__ = "martijn@getpebble.com"
14+
15+
DEFAULT_SDK_VERSION = "7.0"
16+
17+
18+
class PebbleXcodeBuildException (Exception):
19+
pass
20+
21+
22+
class XcodeBuild(object):
23+
sdk_version = "7.0"
24+
conf = None
25+
archs = None
26+
project = None
27+
scheme = None
28+
derived_data_path = None
29+
is_built = False
30+
31+
def __init__(self, project, derived_data_path=None):
32+
self.project = project
33+
self.derived_data_path = derived_data_path or tempfile.mkdtemp()
34+
self.build_settings = {}
35+
36+
def _pre_build_sanity_check(self):
37+
if self.scheme is None:
38+
raise PebbleXcodeBuildException("No scheme set!")
39+
if self.is_built:
40+
raise PebbleXcodeBuildException("Already built!")
41+
42+
def _get_sdk_string(self):
43+
is_device = len([arch for arch in self.archs
44+
if arch.startswith("arm")]) > 0
45+
is_simulator = len([arch for arch in self.archs
46+
if arch.startswith("i386") or
47+
arch.startswith("x64")]) > 0
48+
if is_device and is_simulator:
49+
raise PebbleXcodeBuildException("Can't build for Device and"
50+
"Simulator in one go! (archs=%s)" %
51+
self.archs)
52+
platform = ("iphoneos" if is_device else "iphonesimulator")
53+
return platform + self.sdk_version
54+
55+
def _get_params(self):
56+
params = []
57+
if self.project:
58+
params.extend(("-project", self.project))
59+
if self.scheme:
60+
params.extend(("-scheme", self.scheme))
61+
params.extend(("-derivedDataPath", self.derived_data_path))
62+
if self.conf:
63+
params.extend(("-configuration", self.conf))
64+
if self.archs:
65+
concat_archs = " ".join(self.archs)
66+
params.append("ARCHS=%s" % concat_archs)
67+
# Auto-select SDK if archs is set:
68+
sdk = self._get_sdk_string()
69+
params.extend(("-sdk", sdk))
70+
return params
71+
72+
def _xcodebuild(self, *actions):
73+
self._pre_build_sanity_check()
74+
params = self._get_params()
75+
base_cmd = ["xcodebuild"] + params
76+
actions_cmd = base_cmd + list(actions)
77+
logging.debug("Executing: %s" % " ".join(actions_cmd))
78+
if subprocess.call(actions_cmd):
79+
raise PebbleXcodeBuildException("Build failed. xcodebuild exited"
80+
"with non-zero return code (%s)" %
81+
self.project)
82+
# Collect the build settings that were used:
83+
process = subprocess.Popen(base_cmd + ["-showBuildSettings"],
84+
stdout=subprocess.PIPE)
85+
if process.returncode:
86+
raise PebbleXcodeBuildException("Gettings settings failed."
87+
" xcodebuild exited with non-zero"
88+
" return code (%s)" % self.project)
89+
out = process.stdout.read()
90+
settings_list = re.findall('\s*([A-Z_]+)\s*=\s*(.*)',
91+
out, re.MULTILINE)
92+
self.build_settings = {pair[0]: pair[1] for pair in settings_list}
93+
94+
self.is_built = True
95+
96+
def build(self):
97+
self._xcodebuild("build")
98+
99+
def _check_is_built(self):
100+
if not self.is_built:
101+
raise PebbleXcodeBuildException("Cannot be accessed before build()"
102+
"has been finished.")
103+
104+
def _path_from_build_settings_components(self, *setting_names):
105+
self._check_is_built()
106+
components = [self.build_settings[n] for n in setting_names]
107+
# We want to treat absolute components as relative paths
108+
concat_path = os.sep.join(components)
109+
return os.path.normpath(concat_path)
110+
111+
def built_product_path(self):
112+
return self._path_from_build_settings_components("BUILT_PRODUCTS_DIR",
113+
"FULL_PRODUCT_NAME")
114+
115+
def public_headers_path(self):
116+
args = ("BUILT_PRODUCTS_DIR", "PUBLIC_HEADERS_FOLDER_PATH")
117+
return self._path_from_build_settings_components(*args)
118+
119+
120+
class FrameworkBuild(object):
121+
def __init__(self, project, scheme=None, conf="Release", outdir=None,
122+
name=None, derived_data_path=None):
123+
self.scheme = scheme
124+
self.name = name
125+
self.devicebuild = XcodeBuild(project,
126+
derived_data_path=derived_data_path)
127+
self.simulatorbuild = XcodeBuild(project,
128+
derived_data_path=derived_data_path)
129+
self.outdir = outdir
130+
for (bld, archs) in [self.devicebuild, ["arm64", "armv7", "armv7s"]], \
131+
[self.simulatorbuild, ["i386"]]:
132+
bld.archs = archs
133+
bld.scheme = scheme
134+
bld.conf = conf
135+
136+
def build(self):
137+
name = self.name or self.scheme
138+
if " " in name:
139+
name = name.replace(" ", "-")
140+
141+
# Run the builds of the libraries:
142+
self.devicebuild.build()
143+
self.simulatorbuild.build()
144+
145+
# Create the framework directory structure:
146+
temp_dir = tempfile.mkdtemp()
147+
framework_name = name + ".framework"
148+
framework_dir = os.path.join(temp_dir, framework_name)
149+
versions_dir = os.path.join(framework_dir, "Versions")
150+
a_dir = os.path.join(versions_dir, "A")
151+
lib_path = os.path.join(a_dir, name)
152+
headers_dir = os.path.join(a_dir, "Headers")
153+
os.makedirs(headers_dir)
154+
os.symlink("A", os.path.join(versions_dir, "Current"))
155+
os.symlink(os.path.join("Versions", "Current", "Headers"),
156+
os.path.join(framework_dir, "Headers"))
157+
os.symlink(os.path.join("Versions", "Current", name),
158+
os.path.join(framework_dir, name))
159+
160+
# Move public headers:
161+
os.renames(self.devicebuild.public_headers_path(), headers_dir)
162+
163+
# Use lipo to create one fat static library:
164+
lipo_cmd = ["lipo", "-create",
165+
self.devicebuild.built_product_path(),
166+
self.simulatorbuild.built_product_path(),
167+
"-output", lib_path]
168+
logging.debug("Executing: %s" % " ".join(lipo_cmd))
169+
if subprocess.call(lipo_cmd):
170+
raise PebbleXcodeBuildException("lipo failed")
171+
172+
# Move to outdir:
173+
if self.outdir:
174+
if not os.path.exists(self.outdir):
175+
os.makedirs(self.outdir)
176+
self._built_product_path = os.path.join(self.outdir,
177+
framework_name)
178+
self._public_headers_path = os.path.join(self.outdir,
179+
framework_name,
180+
"Versions",
181+
"A", "Headers")
182+
if os.path.exists(self._built_product_path):
183+
shutil.rmtree(self._built_product_path)
184+
os.rename(framework_dir, self._built_product_path)
185+
else:
186+
self._built_product_path = framework_dir
187+
self._public_headers_path = headers_dir
188+
189+
def built_product_path(self):
190+
return self._built_product_path
191+
192+
def public_headers_path(self):
193+
return self._public_headers_path

0 commit comments

Comments
 (0)