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/.
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
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."""
27 activated
= platform
.system() == "Darwin"
29 def __init__(self
, env
, mach_cmd
):
30 super(MacosDevice
, self
).__init
__(env
, mach_cmd
)
33 def _run_process(self
, args
):
36 stdout
=subprocess
.PIPE
,
37 stderr
=subprocess
.PIPE
,
38 universal_newlines
=True,
41 stdout
, stderr
= p
.communicate(timeout
=45)
43 raise subprocess
.CalledProcessError(
44 p
.returncode
, args
, output
=stdout
, stderr
=stderr
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}"
58 self
._run
_process
(cmd
.split())
59 except subprocess
.CalledProcessError
:
60 self
.error(f
"Can't mount {dmg}")
62 shutil
.rmtree(str(mount
))
65 # browse the mounted volume, to look for the app.
68 for f
in os
.listdir(str(mount
)):
69 if not f
.endswith(".app"):
72 shutil
.copytree(str(app
), str(target
))
77 self
._run
_process
(f
"hdiutil detach {str(mount)}".split())
78 except subprocess
.CalledProcessError
as e
: # noqa
79 self
.warning("Detach failed {e.stdout}")
82 shutil
.rmtree(str(mount
))
84 self
.error(f
"No app file found in {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"):
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
)
120 for dir in self
._tmp
_dirs
:
122 shutil
.rmtree(str(dir))