Bug 1929979 - Support labeled_quantity metrics in FOG r=TravisLong
[gecko.git] / python / mozperftest / mozperftest / system / macos.py
blobb77ae504161627ff4300584419e6a4de98e09a22
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 import os
5 import platform
6 import shutil
7 import subprocess
8 import tempfile
9 from pathlib import Path
11 from mozperftest.layers import Layer
13 # Add here any option that might point to a DMG file we want to extract. The key
14 # is name of the option and the value, the file in the DMG we want to use for
15 # the option.
16 POTENTIAL_DMGS = {
17 "browsertime-binary": "Contents/MacOS/firefox",
18 "xpcshell-xre-path": "Contents/MacOS",
19 "mochitest-binary": "Contents/MacOS/firefox",
23 class MacosDevice(Layer):
24 """Runs on macOS to mount DMGs if we see one."""
26 name = "macos"
27 activated = platform.system() == "Darwin"
29 def __init__(self, env, mach_cmd):
30 super(MacosDevice, self).__init__(env, mach_cmd)
31 self._tmp_dirs = []
33 def _run_process(self, args):
34 p = subprocess.Popen(
35 args,
36 stdout=subprocess.PIPE,
37 stderr=subprocess.PIPE,
38 universal_newlines=True,
41 stdout, stderr = p.communicate(timeout=45)
42 if p.returncode != 0:
43 raise subprocess.CalledProcessError(
44 p.returncode, args, output=stdout, stderr=stderr
47 return stdout
49 def extract_app(self, dmg, target):
50 mount = Path(tempfile.mkdtemp())
52 if not Path(dmg).exists():
53 raise FileNotFoundError(dmg)
55 # mounting the DMG with hdiutil
56 cmd = f"hdiutil attach -nobrowse -mountpoint {str(mount)} {dmg}"
57 try:
58 self._run_process(cmd.split())
59 except subprocess.CalledProcessError:
60 self.error(f"Can't mount {dmg}")
61 if mount.exists():
62 shutil.rmtree(str(mount))
63 raise
65 # browse the mounted volume, to look for the app.
66 found = False
67 try:
68 for f in os.listdir(str(mount)):
69 if not f.endswith(".app"):
70 continue
71 app = mount / f
72 shutil.copytree(str(app), str(target))
73 found = True
74 break
75 finally:
76 try:
77 self._run_process(f"hdiutil detach {str(mount)}".split())
78 except subprocess.CalledProcessError as e: # noqa
79 self.warning("Detach failed {e.stdout}")
80 finally:
81 if mount.exists():
82 shutil.rmtree(str(mount))
83 if not found:
84 self.error(f"No app file found in {dmg}")
85 raise IOError(dmg)
87 def run(self, metadata):
88 # Each DMG is mounted, then we look for the .app
89 # directory in it, which is copied in a directory
90 # alongside the .dmg file. That directory
91 # is removed during teardown.
92 for option, path_in_dmg in POTENTIAL_DMGS.items():
93 value = self.get_arg(option)
95 if value is None or not value.endswith(".dmg"):
96 continue
98 self.info(f"Mounting {value}")
99 dmg_file = Path(value)
100 if not dmg_file.exists():
101 raise FileNotFoundError(str(dmg_file))
103 # let's unpack the DMG in place...
104 target = dmg_file.parent / dmg_file.name.split(".")[0]
105 self._tmp_dirs.append(target)
106 self.extract_app(dmg_file, target)
108 # ... find a specific file or directory if needed ...
109 path = target / path_in_dmg
110 if not path.exists():
111 raise FileNotFoundError(str(path))
113 # ... and swap the browsertime argument
114 self.info(f"Using {path} for {option}")
115 self.env.set_arg(option, str(path))
116 metadata.binary = str(path)
117 return metadata
119 def teardown(self):
120 for dir in self._tmp_dirs:
121 if dir.exists():
122 shutil.rmtree(str(dir))