Skip to content

Commit 157e28c

Browse files
authored
openharmony: add servoshell for ohos (servo#33295)
* openharmony: add servoshell for ohos Signed-off-by: Mukilan Thiyagarajan <[email protected]> * ohos: handle missing signing config on forks Signed-off-by: Mukilan Thiyagarajan <[email protected]> --------- Signed-off-by: Mukilan Thiyagarajan <[email protected]>
1 parent 457d37d commit 157e28c

31 files changed

+625
-9
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
* text=auto eol=lf
22
tests/wpt/** linguist-vendored
3+
support/openharmony/**/*.ets linguist-language=ts
34

45
# Denote all files that are truly binary and should not be modified.
56
*.png binary

.github/workflows/main.yml

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ jobs:
5757
uses: ./.github/workflows/ohos.yml
5858
with:
5959
profile: "release"
60+
secrets: inherit
6061

6162
build-result:
6263
name: Result

.github/workflows/ohos.yml

+35
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,32 @@ jobs:
5555
uses: openharmony-rs/[email protected]
5656
with:
5757
version: "4.1"
58+
components: "native;toolchains;ets;js;previewer"
59+
fixup-path: true
60+
- name: Install node for hvigor
61+
uses: actions/setup-node@v4
62+
with:
63+
node-version: 18
64+
- name: Install hvigor modules
65+
run: |
66+
mkdir ~/hvigor-installation
67+
cd ~/hvigor-installation
68+
echo "@ohos:registry=https://repo.harmonyos.com/npm/" > .npmrc
69+
npm install "@ohos/hvigor@5" "@ohos/hvigor-ohos-plugin@5"
70+
echo "HVIGOR_PATH=$PWD" >> $GITHUB_ENV
71+
- name: "Setup HAP signing config"
72+
env:
73+
SIGNING_MATERIAL: ${{ secrets.SERVO_OHOS_SIGNING_MATERIAL }}
74+
if: ${{ env.SIGNING_MATERIAL != '' }} # Allows the build to pass on forks.
75+
run: |
76+
cd ~
77+
echo "${SIGNING_MATERIAL}" | base64 -d > servo-ohos-material.zip
78+
unzip servo-ohos-material.zip
79+
echo "SERVO_OHOS_SIGNING_CONFIG=${PWD}/servo-ohos-material/signing-configs.json" >> $GITHUB_ENV
5880
- name: Build (arch ${{ matrix.arch }} profile ${{ inputs.profile }})
5981
env:
6082
OHOS_SDK_NATIVE: ${{ steps.setup_sdk.outputs.ohos_sdk_native }}
83+
OHOS_BASE_SDK_HOME: ${{ steps.setup_sdk.outputs.ohos-base-sdk-home }}
6184
run: |
6285
python3 ./mach build --locked --target ${{ matrix.arch }} --${{ inputs.profile }}
6386
cp -r target/cargo-timings target/cargo-timings-ohos-${{ matrix.arch }}
@@ -67,3 +90,15 @@ jobs:
6790
name: cargo-timings-ohos-${{ matrix.arch }}
6891
# Using a wildcard here ensures that the archive includes the path.
6992
path: target/cargo-timings-*
93+
- name: Upload signed HAP artifact
94+
if: ${{ env.SERVO_OHOS_SIGNING_CONFIG != '' }} # Build output has different name if not signed.
95+
uses: actions/upload-artifact@v4
96+
with:
97+
name: ${{ inputs.profile }}-binary-ohos-${{ matrix.arch }}
98+
path: target/openharmony/${{ matrix.arch }}/${{ inputs.profile }}/entry/build/default/outputs/default/servoshell-default-signed.hap
99+
- name: Upload unsigned HAP artifact
100+
if: ${{ env.SERVO_OHOS_SIGNING_CONFIG == '' }} # Build output has different name if not signed.
101+
uses: actions/upload-artifact@v4
102+
with:
103+
name: ${{ inputs.profile }}-binary-ohos-${{ matrix.arch }}
104+
path: target/openharmony/${{ matrix.arch }}/${{ inputs.profile }}/entry/build/default/outputs/default/servoshell-default-unsigned.hap

python/servo/command_base.py

+25-8
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ def resolverelative(category, key):
297297

298298
self.config.setdefault("build", {})
299299
self.config["build"].setdefault("android", False)
300+
self.config["build"].setdefault("ohos", False)
300301
self.config["build"].setdefault("mode", "")
301302
self.config["build"].setdefault("debug-assertions", False)
302303
self.config["build"].setdefault("debug-mozjs", False)
@@ -328,12 +329,6 @@ def rust_toolchain(self):
328329
def get_top_dir(self):
329330
return self.context.topdir
330331

331-
def get_apk_path(self, build_type: BuildType):
332-
base_path = util.get_target_dir()
333-
base_path = path.join(base_path, "android", self.target.triple())
334-
apk_name = "servoapp.apk"
335-
return path.join(base_path, build_type.directory_name(), apk_name)
336-
337332
def get_binary_path(self, build_type: BuildType, asan: bool = False):
338333
base_path = util.get_target_dir()
339334
if asan or self.target.is_cross_build():
@@ -551,7 +546,12 @@ def common_command_arguments(build_configuration=False, build_type=False, binary
551546
CommandArgument(
552547
'--android', default=None, action='store_true',
553548
help='Build for Android. If --target is not specified, this '
554-
'will choose a default target architecture.',
549+
f'will choose the default target architecture ({AndroidTarget.DEFAULT_TRIPLE}).',
550+
),
551+
CommandArgument(
552+
'--ohos', default=None, action='store_true',
553+
help='Build for OpenHarmony. If --target is not specified, this '
554+
f'will choose a default target architecture ({OpenHarmonyTarget.DEFAULT_TRIPLE}).',
555555
),
556556
CommandArgument('--win-arm64', action='store_true', help="Use arm64 Windows target"),
557557
CommandArgumentGroup('Feature Selection'),
@@ -646,6 +646,7 @@ def target_configuration_decorator(self, *args, **kwargs):
646646
self.configure_build_target(kwargs, suppress_log=True)
647647
kwargs.pop('target', False)
648648
kwargs.pop('android', False)
649+
kwargs.pop('ohos', False)
649650
return original_function(self, *args, **kwargs)
650651

651652
return target_configuration_decorator
@@ -686,15 +687,28 @@ def configure_build_target(self, kwargs: Dict[str, Any], suppress_log: bool = Fa
686687
return
687688

688689
android = kwargs.get('android') or self.config["build"]["android"]
690+
ohos = kwargs.get('ohos') or self.config["build"]["ohos"]
689691
target_triple = kwargs.get('target')
690692

693+
if android and ohos:
694+
print("Cannot build both android and ohos targets simultaneously.")
695+
sys.exit(1)
696+
691697
if android and target_triple:
692698
print("Please specify either --target or --android.")
693699
sys.exit(1)
694700

695701
# Set the default Android target
696702
if android and not target_triple:
697-
target_triple = "aarch64-linux-android"
703+
target_triple = AndroidTarget.DEFAULT_TRIPLE
704+
705+
if ohos and target_triple:
706+
print("Please specify either --target or --ohos.")
707+
sys.exit(1)
708+
709+
# Set the default OpenHarmony target
710+
if ohos and not target_triple:
711+
target_triple = OpenHarmonyTarget.DEFAULT_TRIPLE
698712

699713
self.target = BuildTarget.from_triple(target_triple)
700714

@@ -705,6 +719,9 @@ def configure_build_target(self, kwargs: Dict[str, Any], suppress_log: bool = Fa
705719
def is_android(self):
706720
return isinstance(self.target, AndroidTarget)
707721

722+
def is_openharmony(self):
723+
return isinstance(self.target, OpenHarmonyTarget)
724+
708725
def is_media_enabled(self, media_stack: Optional[str]):
709726
"""Determine whether media is enabled based on the value of the build target
710727
platform and the value of the '--media-stack' command-line argument.

python/servo/package_commands.py

+62-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
archive_deterministically,
3636
BuildNotFound,
3737
cd,
38+
check_output,
3839
CommandBase,
3940
is_windows,
4041
)
@@ -124,6 +125,10 @@ class PackageCommands(CommandBase):
124125
default=None,
125126
action='store_true',
126127
help='Package Android')
128+
@CommandArgument('--ohos',
129+
default=None,
130+
action='store_true',
131+
help='Package OpenHarmony')
127132
@CommandArgument('--target', '-t',
128133
default=None,
129134
help='Package for given target platform')
@@ -178,6 +183,55 @@ def package(self, build_type: BuildType, flavor=None, with_asan=False):
178183
except subprocess.CalledProcessError as e:
179184
print("Packaging Android exited with return value %d" % e.returncode)
180185
return e.returncode
186+
elif self.is_openharmony():
187+
# hvigor doesn't support an option to place output files in a specific directory
188+
# so copy the source files into the target/openharmony directory first.
189+
ohos_app_dir = path.join(self.get_top_dir(), "support", "openharmony")
190+
build_mode = build_type.directory_name()
191+
ohos_target_dir = path.join(
192+
self.get_top_dir(), "target", "openharmony", self.target.triple(), build_mode)
193+
if path.exists(ohos_target_dir):
194+
print("Cleaning up from previous packaging")
195+
delete(ohos_target_dir)
196+
shutil.copytree(ohos_app_dir, ohos_target_dir)
197+
198+
# Map non-debug profiles to 'release' buildMode HAP.
199+
if build_type.is_custom():
200+
build_mode = "release"
201+
202+
hvigor_command = ["--no-daemon", "assembleHap", "-p", "product=default", "-p", f"buildMode={build_mode}"]
203+
# Detect if PATH already has hvigor, or else fallback to npm installation
204+
# provided via HVIGOR_PATH
205+
if "HVIGOR_PATH" not in env:
206+
try:
207+
with cd(ohos_target_dir):
208+
version = check_output(["hvigorw", "--version", "--no-daemon"])
209+
print(f"Found `hvigorw` with version {str(version, 'utf-8').strip()} in system PATH")
210+
hvigor_command[0:0] = ["hvigorw"]
211+
except FileNotFoundError:
212+
print("Unable to find `hvigor` tool. Please either modify PATH to include the"
213+
"path to hvigorw or set the HVIGOR_PATH environment variable to the npm"
214+
"installation containing `node_modules` directory with hvigor modules.")
215+
sys.exit(1)
216+
else:
217+
env["NODE_PATH"] = env["HVIGOR_PATH"] + "/node_modules"
218+
hvigor_script = f"{env['HVIGOR_PATH']}/node_modules/@ohos/hvigor/bin/hvigor.js"
219+
hvigor_command[0:0] = ["node", hvigor_script]
220+
221+
abi_string = self.target.abi_string()
222+
ohos_libs_dir = path.join(ohos_target_dir, "entry", "libs", abi_string)
223+
os.makedirs(ohos_libs_dir)
224+
# The libservoshell.so binary that was built needs to be copied
225+
# into the app folder heirarchy where hvigor expects it.
226+
print(f"Copying {binary_path} to {ohos_libs_dir}")
227+
shutil.copy(binary_path, ohos_libs_dir)
228+
try:
229+
with cd(ohos_target_dir):
230+
print("Calling", hvigor_command)
231+
subprocess.check_call(hvigor_command, env=env)
232+
except subprocess.CalledProcessError as e:
233+
print("Packaging OpenHarmony exited with return value %d" % e.returncode)
234+
return e.returncode
181235
elif 'darwin' in self.target.triple():
182236
print("Creating Servo.app")
183237
dir_to_dmg = path.join(target_dir, 'dmg')
@@ -347,6 +401,9 @@ def package(self, build_type: BuildType, flavor=None, with_asan=False):
347401
@CommandArgument('--android',
348402
action='store_true',
349403
help='Install on Android')
404+
@CommandArgument('--ohos',
405+
action='store_true',
406+
help='Install on OpenHarmony')
350407
@CommandArgument('--emulator',
351408
action='store_true',
352409
help='For Android, install to the only emulated device')
@@ -376,7 +433,7 @@ def install(self, build_type: BuildType, emulator=False, usb=False, with_asan=Fa
376433
return 1
377434

378435
if self.is_android():
379-
pkg_path = self.get_apk_path(build_type)
436+
pkg_path = self.target.get_package_path(build_type.directory_name())
380437
exec_command = [self.android_adb_path(env)]
381438
if emulator and usb:
382439
print("Cannot install to both emulator and USB at the same time.")
@@ -386,6 +443,10 @@ def install(self, build_type: BuildType, emulator=False, usb=False, with_asan=Fa
386443
if usb:
387444
exec_command += ["-d"]
388445
exec_command += ["install", "-r", pkg_path]
446+
elif self.is_openharmony():
447+
pkg_path = self.target.get_package_path(build_type.directory_name())
448+
hdc_path = path.join(env["OHOS_SDK_NATIVE"], "../", "toolchains", "hdc")
449+
exec_command = [hdc_path, "install", "-r", pkg_path]
389450
elif is_windows():
390451
pkg_path = path.join(path.dirname(binary_path), 'msi', 'Servo.msi')
391452
exec_command = ["msiexec", "/i", pkg_path]

python/servo/platform/build_target.py

+25
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from typing import Any, Dict, Optional
2222

2323
import servo.platform
24+
import servo.util as util
2425

2526

2627
class BuildTarget(object):
@@ -61,6 +62,8 @@ def is_cross_build(self) -> bool:
6162

6263

6364
class AndroidTarget(CrossBuildTarget):
65+
DEFAULT_TRIPLE = "aarch64-linux-android"
66+
6467
def ndk_configuration(self) -> Dict[str, str]:
6568
target = self.triple()
6669
config = {}
@@ -237,8 +240,16 @@ def is_cross_build(self) -> bool:
237240
def needs_packaging(self) -> bool:
238241
return True
239242

243+
def get_package_path(self, build_type_directory: str) -> str:
244+
base_path = util.get_target_dir()
245+
base_path = path.join(base_path, "android", self.triple())
246+
apk_name = "servoapp.apk"
247+
return path.join(base_path, build_type_directory, apk_name)
248+
240249

241250
class OpenHarmonyTarget(CrossBuildTarget):
251+
DEFAULT_TRIPLE = "aarch64-unknown-linux-ohos"
252+
242253
def configure_build_environment(self, env: Dict[str, str], config: Dict[str, Any], topdir: pathlib.Path):
243254
# Paths to OpenHarmony SDK and build tools:
244255
# Note: `OHOS_SDK_NATIVE` is the CMake variable name the `hvigor` build-system
@@ -370,3 +381,17 @@ def binary_name(self) -> str:
370381

371382
def needs_packaging(self) -> bool:
372383
return True
384+
385+
def get_package_path(self, build_type_directory: str) -> str:
386+
base_path = util.get_target_dir()
387+
base_path = path.join(base_path, "openharmony", self.triple())
388+
hap_name = "servoshell-default-signed.hap"
389+
build_output_path = path.join("entry", "build", "default", "outputs", "default")
390+
return path.join(base_path, build_type_directory, build_output_path, hap_name)
391+
392+
def abi_string(self) -> str:
393+
abi_map = {
394+
"aarch64-unknown-linux-ohos": "arm64-v8a",
395+
"x86_64-unknown-linux-ohos": "x86_64"
396+
}
397+
return abi_map[self.triple()]
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"app": {
3+
"bundleName": "org.servo.servoshell",
4+
"vendor": "example",
5+
"versionCode": 1000000,
6+
"versionName": "1.0.0",
7+
"icon": "$media:servo_64",
8+
"label": "$string:app_name"
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"string": [
3+
{
4+
"name": "app_name",
5+
"value": "Servo Shell"
6+
}
7+
]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../../../resources/servo_64.png
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"app": {
3+
"products": [
4+
{
5+
"name": "default",
6+
"signingConfig": "default",
7+
"compileSdkVersion": 11,
8+
"compatibleSdkVersion": 11,
9+
"targetSdkVersion": 11,
10+
"runtimeOS": "OpenHarmony"
11+
},
12+
{
13+
"name": "harmonyos",
14+
"signingConfig": "default",
15+
"compatibleSdkVersion": "4.1.0(11)",
16+
"targetSdkVersion": "4.1.0(11)",
17+
"runtimeOS": "HarmonyOS"
18+
}
19+
],
20+
"buildModeSet": [
21+
{
22+
"name": "debug"
23+
},
24+
{
25+
"name": "release"
26+
}
27+
]
28+
},
29+
"modules": [
30+
{
31+
"name": "servoshell",
32+
"srcPath": "./entry",
33+
"targets": [
34+
{
35+
"name": "default",
36+
"applyToProducts": [
37+
"default",
38+
"harmonyos"
39+
]
40+
}
41+
]
42+
}
43+
]
44+
}

0 commit comments

Comments
 (0)