This instance will be upgraded to Heptapod 0.31.0 on 2022-05-19 at 11:00 UTC+2 (a few minutes of down time)

pyoxidizer.bzl 11.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
# The following variables can be passed in as parameters:
#
# VERSION
#   Version string of program being produced.
#
# MSI_NAME
#   Root name of MSI installer.
#
# EXTRA_MSI_FEATURES
#   ; delimited string of extra features to advertise in the built MSA.
11
12
13
14
15
16
17
18
19
20
21
22
23
#
# SIGNING_PFX_PATH
#   Path to code signing certificate to use.
#
# SIGNING_PFX_PASSWORD
#   Password to code signing PFX file defined by SIGNING_PFX_PATH.
#
# SIGNING_SUBJECT_NAME
#   String fragment in code signing certificate subject name used to find
#   code signing certificate in Windows certificate store.
#
# TIME_STAMP_SERVER_URL
#   URL of time-stamp token authority (RFC 3161) servers to stamp code signatures.
24

Gregory Szorc's avatar
Gregory Szorc committed
25
ROOT = CWD + "/../.."
26

27
28
29
VERSION = VARS.get("VERSION", "5.8")
MSI_NAME = VARS.get("MSI_NAME", "mercurial")
EXTRA_MSI_FEATURES = VARS.get("EXTRA_MSI_FEATURES")
30
31
32
33
SIGNING_PFX_PATH = VARS.get("SIGNING_PFX_PATH")
SIGNING_PFX_PASSWORD = VARS.get("SIGNING_PFX_PASSWORD", "")
SIGNING_SUBJECT_NAME = VARS.get("SIGNING_SUBJECT_NAME")
TIME_STAMP_SERVER_URL = VARS.get("TIME_STAMP_SERVER_URL", "http://timestamp.digicert.com")
34

35
IS_WINDOWS = "windows" in BUILD_TARGET_TRIPLE
36
IS_MACOS = "darwin" in BUILD_TARGET_TRIPLE
37

38
# Code to run in Python interpreter.
39
RUN_CODE = """
40
41
42
43
44
45
46
47
import os
import sys
extra_path = os.environ.get('PYTHONPATH')
if extra_path is not None:
    # extensions and hooks expect a working python environment
    # We do not prepend the values because the Mercurial library wants to be in
    # the front of the sys.path to avoid picking up other installations.
    sys.path.extend(extra_path.split(os.pathsep))
48
49
50
# Add user site to sys.path to load extensions without the full path
if os.name == 'nt':
    vi = sys.version_info
51
52
53
54
55
56
57
58
59
    appdata = os.environ.get('APPDATA')
    if appdata:
        sys.path.append(
            os.path.join(
                appdata,
                'Python',
                'Python%d%d' % (vi[0], vi[1]),
                'site-packages',
            )
60
        )
61
62
63
64
65
66
67
68
69
70
71
72
73
74
elif sys.platform == "darwin":
    vi = sys.version_info

    def joinuser(*args):
        return os.path.expanduser(os.path.join(*args))

    # Note: site.py uses `sys._framework` instead of hardcoding "Python" as the
    #   3rd arg, but that is set to an empty string in an oxidized binary.  It
    #   has a fallback to ~/.local when `sys._framework` isn't set, but we want
    #   to match what the system python uses, so it sees pip installed stuff.
    usersite = joinuser("~", "Library", "Python",
                        "%d.%d" % vi[:2], "lib/python/site-packages")

    sys.path.append(usersite)
75
76
77
78
79
import hgdemandimport;
hgdemandimport.enable();
from mercurial import dispatch;
dispatch.run();
"""
80
81
82
83

set_build_path(ROOT + "/build/pyoxidizer")

def make_distribution():
84
    return default_python_distribution(python_version = "3.9")
85

86
def resource_callback(policy, resource):
87
    if not (IS_WINDOWS or IS_MACOS):
88
89
        resource.add_location = "in-memory"
        return
Augie Fackler's avatar
Augie Fackler committed
90

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
    # We use a custom resource routing policy to influence where things are loaded
    # from.
    #
    # For Python modules and resources, we load from memory if they are in
    # the standard library and from the filesystem if not. This is because
    # parts of Mercurial and some 3rd party packages aren't yet compatible
    # with memory loading.
    #
    # For Python extension modules, we load from the filesystem because
    # this yields greatest compatibility.
    if type(resource) in ("PythonModuleSource", "PythonPackageResource", "PythonPackageDistributionResource"):
        if resource.is_stdlib:
            resource.add_location = "in-memory"
        else:
            resource.add_location = "filesystem-relative:lib"

    elif type(resource) == "PythonExtensionModule":
        resource.add_location = "filesystem-relative:lib"

110
def make_exe(dist):
111
    """Builds a Rust-wrapped Mercurial binary."""
112
    packaging_policy = dist.make_python_packaging_policy()
Augie Fackler's avatar
Augie Fackler committed
113

114
115
116
    # Extension may depend on any Python functionality. Include all
    # extensions.
    packaging_policy.extension_module_filter = "all"
117
    packaging_policy.resources_location = "in-memory"
118
    if IS_WINDOWS or IS_MACOS:
119
        packaging_policy.resources_location_fallback = "filesystem-relative:lib"
120
121
    packaging_policy.register_resource_callback(resource_callback)

122
    config = dist.make_python_interpreter_config()
123
    config.allocator_backend = "default"
124
    config.run_command = RUN_CODE
Augie Fackler's avatar
Augie Fackler committed
125

126
127
    # We want to let the user load extensions from the file system
    config.filesystem_importer = True
Augie Fackler's avatar
Augie Fackler committed
128

129
130
131
    # We need this to make resourceutil happy, since it looks for sys.frozen.
    config.sys_frozen = True
    config.legacy_windows_stdio = True
132
133

    exe = dist.to_python_executable(
Gregory Szorc's avatar
Gregory Szorc committed
134
        name = "hg",
135
        packaging_policy = packaging_policy,
Gregory Szorc's avatar
Gregory Szorc committed
136
        config = config,
137
138
    )

139
    # Add Mercurial to resources.
140
    exe.add_python_resources(exe.pip_install(["--verbose", ROOT]))
141
142

    # On Windows, we install extra packages for convenience.
143
    if IS_WINDOWS:
144
        exe.add_python_resources(
145
            exe.pip_install(["-r", ROOT + "/contrib/packaging/requirements-windows-py3.txt"]),
146
        )
147
148
149
150
151
152
    extra_packages = VARS.get("extra_py_packages", "")
    if extra_packages:
        for extra in extra_packages.split(","):
            extra_src, pkgs = extra.split("=")
            pkgs = pkgs.split(":")
            exe.add_python_resources(exe.read_package_root(extra_src, pkgs))
153

Gregory Szorc's avatar
Gregory Szorc committed
154
155
    return exe

156
def make_manifest(dist, exe):
Gregory Szorc's avatar
Gregory Szorc committed
157
158
    m = FileManifest()
    m.add_python_resource(".", exe)
159

160
    return m
161

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193

# This adjusts the InstallManifest produced from exe generation to provide
# additional files found in a Windows install layout.
def make_windows_install_layout(manifest):
    # Copy various files to new install locations. This can go away once
    # we're using the importlib resource reader.
    RECURSIVE_COPIES = {
        "lib/mercurial/locale/": "locale/",
        "lib/mercurial/templates/": "templates/",
    }
    for (search, replace) in RECURSIVE_COPIES.items():
        for path in manifest.paths():
            if path.startswith(search):
                new_path = path.replace(search, replace)
                print("copy %s to %s" % (path, new_path))
                file = manifest.get_file(path)
                manifest.add_file(file, path = new_path)

    # Similar to above, but with filename pattern matching.
    # lib/mercurial/helptext/**/*.txt -> helptext/
    # lib/mercurial/defaultrc/*.rc -> defaultrc/
    for path in manifest.paths():
        if path.startswith("lib/mercurial/helptext/") and path.endswith(".txt"):
            new_path = path[len("lib/mercurial/"):]
        elif path.startswith("lib/mercurial/defaultrc/") and path.endswith(".rc"):
            new_path = path[len("lib/mercurial/"):]
        else:
            continue

        print("copying %s to %s" % (path, new_path))
        manifest.add_file(manifest.get_file(path), path = new_path)

194
195
196
197
198
199
200
201
202
203
204
    extra_install_files = VARS.get("extra_install_files", "")
    if extra_install_files:
        for extra in extra_install_files.split(","):
            print("adding extra files from %s" % extra)
            # TODO: I expected a ** glob to work, but it didn't.
            #
            # TODO: I know this has forward-slash paths. As far as I can tell,
            # backslashes don't ever match glob() expansions in 
            # tugger-starlark, even on Windows.
            manifest.add_manifest(glob(include=[extra + "/*/*"], strip_prefix=extra+"/"))

205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
    # We also install a handful of additional files.
    EXTRA_CONTRIB_FILES = [
        "bash_completion",
        "hgweb.fcgi",
        "hgweb.wsgi",
        "logo-droplets.svg",
        "mercurial.el",
        "mq.el",
        "tcsh_completion",
        "tcsh_completion_build.sh",
        "xml.rnc",
        "zsh_completion",
    ]

    for f in EXTRA_CONTRIB_FILES:
        manifest.add_file(FileContent(path = ROOT + "/contrib/" + f), directory = "contrib")

    # Individual files with full source to destination path mapping.
    EXTRA_FILES = {
        "contrib/hgk": "contrib/hgk.tcl",
        "contrib/win32/postinstall.txt": "ReleaseNotes.txt",
        "contrib/win32/ReadMe.html": "ReadMe.html",
        "doc/style.css": "doc/style.css",
        "COPYING": "Copying.txt",
    }

    for source, dest in EXTRA_FILES.items():
        print("adding extra file %s" % dest)
        manifest.add_file(FileContent(path = ROOT + "/" + source), path = dest)

    # And finally some wildcard matches.
    manifest.add_manifest(glob(
        include = [ROOT + "/contrib/vim/*"],
        strip_prefix = ROOT + "/"
    ))
    manifest.add_manifest(glob(
        include = [ROOT + "/doc/*.html"],
        strip_prefix = ROOT + "/"
    ))

    # But we don't ship hg-ssh on Windows, so exclude its documentation.
    manifest.remove("doc/hg-ssh.8.html")

    return manifest


def make_msi(manifest):
    manifest = make_windows_install_layout(manifest)

    if "x86_64" in BUILD_TARGET_TRIPLE:
        platform = "x64"
    else:
        platform = "x86"

    manifest.add_file(
        FileContent(path = ROOT + "/contrib/packaging/wix/COPYING.rtf"),
        path = "COPYING.rtf",
    )
    manifest.remove("Copying.txt")
    manifest.add_file(
        FileContent(path = ROOT + "/contrib/win32/mercurial.ini"),
        path = "defaultrc/mercurial.rc",
    )
    manifest.add_file(
        FileContent(filename = "editor.rc", content = "[ui]\neditor = notepad\n"),
        path = "defaultrc/editor.rc",
    )

273
274
275
276
277
    wix = WiXInstaller(
        "hg",
        "%s-%s-%s.msi" % (MSI_NAME, VERSION, platform),
        arch = platform,
    )
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311

    # Materialize files in the manifest to the install layout.
    wix.add_install_files(manifest)

    # From mercurial.wxs.
    wix.install_files_root_directory_id = "INSTALLDIR"

    # Pull in our custom .wxs files.
    defines = {
        "PyOxidizer": "1",
        "Platform": platform,
        "Version": VERSION,
        "Comments": "Installs Mercurial version %s" % VERSION,
        "PythonVersion": "3",
        "MercurialHasLib": "1",
    }

    if EXTRA_MSI_FEATURES:
        defines["MercurialExtraFeatures"] = EXTRA_MSI_FEATURES

    wix.add_wxs_file(
        ROOT + "/contrib/packaging/wix/mercurial.wxs",
        preprocessor_parameters=defines,
    )

    # Our .wxs references to other files. Pull those into the build environment.
    for f in ("defines.wxi", "guids.wxi", "COPYING.rtf"):
        wix.add_build_file(f, ROOT + "/contrib/packaging/wix/" + f)

    wix.add_build_file("mercurial.ico", ROOT + "/contrib/win32/mercurial.ico")

    return wix


312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
def register_code_signers():
    if not IS_WINDOWS:
        return

    if SIGNING_PFX_PATH:
        signer = code_signer_from_pfx_file(SIGNING_PFX_PATH, SIGNING_PFX_PASSWORD)
    elif SIGNING_SUBJECT_NAME:
        signer = code_signer_from_windows_store_subject(SIGNING_SUBJECT_NAME)
    else:
        signer = None

    if signer:
        signer.set_time_stamp_server(TIME_STAMP_SERVER_URL)
        signer.activate()


register_code_signers()

Gregory Szorc's avatar
Gregory Szorc committed
330
331
332
register_target("distribution", make_distribution)
register_target("exe", make_exe, depends = ["distribution"])
register_target("app", make_manifest, depends = ["distribution", "exe"], default = True)
333
register_target("msi", make_msi, depends = ["app"])
334

335
resolve_targets()