def _get_test_manifest(self):
        m = InstallManifest()
        m.add_symlink(self.tmppath("s_source"), "s_dest")
        m.add_copy(self.tmppath("c_source"), "c_dest")
        m.add_required_exists("e_dest")

        return m
Exemple #2
0
    def _manifest_for_project(self, srcdir, project):
        manifest = InstallManifest()

        if project.manifest:
            manifest.add_copy(mozpath.join(srcdir, project.manifest), 'AndroidManifest.xml')

        if project.res:
            manifest.add_symlink(mozpath.join(srcdir, project.res), 'res')
        else:
            # Eclipse expects a res directory no matter what, so we
            # make an empty directory if the project doesn't specify.
            res = os.path.abspath(mozpath.join(os.path.dirname(__file__),
                'templates', 'android_eclipse_empty_resource_directory'))
            manifest.add_pattern_copy(res, '.**', 'res')

        if project.assets:
            manifest.add_symlink(mozpath.join(srcdir, project.assets), 'assets')

        for cpe in project._classpathentries:
            manifest.add_symlink(mozpath.join(srcdir, cpe.srcdir), cpe.dstdir)

        # JARs and native libraries go in the same place. For now, we're adding
        # class path entries with the full path to required JAR files (which
        # makes sense for JARs in the source directory, but probably doesn't for
        # JARs in the object directory). This could be a problem because we only
        # know the contents of (a subdirectory of) libs/ after a successful
        # build and package, which is after build-backend time. At the cost of
        # some flexibility, we explicitly copy certain libraries here; if the
        # libraries aren't present -- namely, when the tree hasn't been packaged
        # -- this fails. That's by design, to avoid crashes on device caused by
        # missing native libraries.
        for src, dst in project.libs:
            manifest.add_copy(mozpath.join(srcdir, src), dst)

        return manifest
Exemple #3
0
    def test_adds(self):
        m = InstallManifest()
        m.add_symlink('s_source', 's_dest')
        m.add_copy('c_source', 'c_dest')
        m.add_required_exists('e_dest')
        m.add_optional_exists('o_dest')
        m.add_pattern_symlink('ps_base', 'ps/*', 'ps_dest')
        m.add_pattern_copy('pc_base', 'pc/**', 'pc_dest')

        self.assertEqual(len(m), 6)
        self.assertIn('s_dest', m)
        self.assertIn('c_dest', m)
        self.assertIn('e_dest', m)
        self.assertIn('o_dest', m)

        with self.assertRaises(ValueError):
            m.add_symlink('s_other', 's_dest')

        with self.assertRaises(ValueError):
            m.add_copy('c_other', 'c_dest')

        with self.assertRaises(ValueError):
            m.add_required_exists('e_dest')

        with self.assertRaises(ValueError):
            m.add_optional_exists('o_dest')

        with self.assertRaises(ValueError):
            m.add_pattern_symlink('ps_base', 'ps/*', 'ps_dest')

        with self.assertRaises(ValueError):
            m.add_pattern_copy('pc_base', 'pc/**', 'pc_dest')
    def _manifest_for_project(self, srcdir, project):
        manifest = InstallManifest()

        if project.manifest:
            manifest.add_copy(mozpath.join(srcdir, project.manifest), 'AndroidManifest.xml')

        if project.res:
            manifest.add_symlink(mozpath.join(srcdir, project.res), 'res')
        else:
            # Eclipse expects a res directory no matter what, so we
            # make an empty directory if the project doesn't specify.
            res = os.path.abspath(mozpath.join(os.path.dirname(__file__),
                'templates', 'android_eclipse_empty_resource_directory'))
            manifest.add_pattern_copy(res, '.**', 'res')

        if project.assets:
            manifest.add_symlink(mozpath.join(srcdir, project.assets), 'assets')

        for cpe in project._classpathentries:
            manifest.add_symlink(mozpath.join(srcdir, cpe.srcdir), cpe.dstdir)

        # JARs and native libraries go in the same place. For now, we're adding
        # class path entries with the full path to required JAR files (which
        # makes sense for JARs in the source directory, but probably doesn't for
        # JARs in the object directory). This could be a problem because we only
        # know the contents of (a subdirectory of) libs/ after a successful
        # build and package, which is after build-backend time. At the cost of
        # some flexibility, we explicitly copy certain libraries here; if the
        # libraries aren't present -- namely, when the tree hasn't been packaged
        # -- this fails. That's by design, to avoid crashes on device caused by
        # missing native libraries.
        for src, dst in project.libs:
            manifest.add_copy(mozpath.join(srcdir, src), dst)

        return manifest
    def _manifest_for_project(self, srcdir, project):
        manifest = InstallManifest()

        if project.manifest:
            manifest.add_copy(mozpath.join(srcdir, project.manifest), 'AndroidManifest.xml')

        if project.res:
            manifest.add_symlink(mozpath.join(srcdir, project.res), 'res')

        if project.assets:
            manifest.add_symlink(mozpath.join(srcdir, project.assets), 'assets')

        for cpe in project._classpathentries:
            manifest.add_symlink(mozpath.join(srcdir, cpe.srcdir), cpe.dstdir)

        # JARs and native libraries go in the same place. For now,
        # we're adding class path entries with the full path to
        # required JAR files (which makes sense for JARs in the source
        # directory, but probably doesn't for JARs in the object
        # directory). This could be a problem because we only know
        # the contents of (a subdirectory of) libs/ after a successful
        # build and package, which is after build-backend time. So we
        # use a pattern symlink that is resolved at manifest install
        # time.
        if project.libs:
            manifest.add_pattern_copy(mozpath.join(srcdir, project.libs), '**', 'libs')

        return manifest
Exemple #6
0
    def _manifest_for_project(self, srcdir, project):
        manifest = InstallManifest()

        if project.manifest:
            manifest.add_copy(mozpath.join(srcdir, project.manifest), "AndroidManifest.xml")

        if project.res:
            manifest.add_symlink(mozpath.join(srcdir, project.res), "res")

        if project.assets:
            manifest.add_symlink(mozpath.join(srcdir, project.assets), "assets")

        for cpe in project._classpathentries:
            manifest.add_symlink(mozpath.join(srcdir, cpe.srcdir), cpe.dstdir)

        # JARs and native libraries go in the same place. This
        # wouldn't be a problem, except we only know the contents of
        # (a subdirectory of) libs/ after a successful build and
        # package, which is after build-backend time. So we use a
        # pattern symlink that is resolved at manifest install time.
        if project.libs:
            manifest.add_pattern_copy(mozpath.join(srcdir, project.libs), "**", "libs")

        for extra_jar in sorted(project.extra_jars):
            manifest.add_copy(mozpath.join(srcdir, extra_jar), mozpath.join("libs", os.path.basename(extra_jar)))

        return manifest
    def _manifest_for_project(self, srcdir, project):
        manifest = InstallManifest()

        if project.manifest:
            manifest.add_copy(mozpath.join(srcdir, project.manifest), 'AndroidManifest.xml')

        if project.res:
            manifest.add_symlink(mozpath.join(srcdir, project.res), 'res')
        else:
            # Eclipse expects a res directory no matter what, so we
            # make an empty directory if the project doesn't specify.
            res = os.path.abspath(mozpath.join(os.path.dirname(__file__),
                'templates', 'android_eclipse_empty_resource_directory'))
            manifest.add_pattern_copy(res, '.**', 'res')

        if project.assets:
            manifest.add_symlink(mozpath.join(srcdir, project.assets), 'assets')

        for cpe in project._classpathentries:
            manifest.add_symlink(mozpath.join(srcdir, cpe.srcdir), cpe.dstdir)

        # JARs and native libraries go in the same place. For now,
        # we're adding class path entries with the full path to
        # required JAR files (which makes sense for JARs in the source
        # directory, but probably doesn't for JARs in the object
        # directory). This could be a problem because we only know
        # the contents of (a subdirectory of) libs/ after a successful
        # build and package, which is after build-backend time. So we
        # use a pattern symlink that is resolved at manifest install
        # time.
        if project.libs:
            manifest.add_pattern_copy(mozpath.join(srcdir, project.libs), '**', 'libs')

        return manifest
Exemple #8
0
    def test_adds(self):
        m = InstallManifest()
        m.add_symlink('s_source', 's_dest')
        m.add_copy('c_source', 'c_dest')
        m.add_required_exists('e_dest')
        m.add_optional_exists('o_dest')
        m.add_pattern_symlink('ps_base', 'ps/*', 'ps_dest')
        m.add_pattern_copy('pc_base', 'pc/**', 'pc_dest')

        self.assertEqual(len(m), 6)
        self.assertIn('s_dest', m)
        self.assertIn('c_dest', m)
        self.assertIn('e_dest', m)
        self.assertIn('o_dest', m)

        with self.assertRaises(ValueError):
            m.add_symlink('s_other', 's_dest')

        with self.assertRaises(ValueError):
            m.add_copy('c_other', 'c_dest')

        with self.assertRaises(ValueError):
            m.add_required_exists('e_dest')

        with self.assertRaises(ValueError):
            m.add_optional_exists('o_dest')

        with self.assertRaises(ValueError):
            m.add_pattern_symlink('ps_base', 'ps/*', 'ps_dest')

        with self.assertRaises(ValueError):
            m.add_pattern_copy('pc_base', 'pc/**', 'pc_dest')
Exemple #9
0
    def _manifest_for_project(self, srcdir, project):
        manifest = InstallManifest()

        if project.manifest:
            manifest.add_copy(mozpath.join(srcdir, project.manifest), 'AndroidManifest.xml')

        if project.res:
            manifest.add_symlink(mozpath.join(srcdir, project.res), 'res')
        else:
            # Eclipse expects a res directory no matter what, so we
            # make an empty directory if the project doesn't specify.
            res = os.path.abspath(mozpath.join(os.path.dirname(__file__),
                'templates', 'android_eclipse_empty_resource_directory'))
            manifest.add_pattern_copy(res, '.**', 'res')

        if project.assets:
            manifest.add_symlink(mozpath.join(srcdir, project.assets), 'assets')

        for cpe in project._classpathentries:
            manifest.add_symlink(mozpath.join(srcdir, cpe.srcdir), cpe.dstdir)

        # JARs and native libraries go in the same place. For now,
        # we're adding class path entries with the full path to
        # required JAR files (which makes sense for JARs in the source
        # directory, but probably doesn't for JARs in the object
        # directory). This could be a problem because we only know
        # the contents of (a subdirectory of) libs/ after a successful
        # build and package, which is after build-backend time. So we
        # use a pattern symlink that is resolved at manifest install
        # time.
        if project.libs:
            manifest.add_pattern_copy(mozpath.join(srcdir, project.libs), '**', 'libs')

        return manifest
Exemple #10
0
    def _get_test_manifest(self):
        m = InstallManifest()
        m.add_symlink(self.tmppath('s_source'), 's_dest')
        m.add_copy(self.tmppath('c_source'), 'c_dest')
        m.add_required_exists('e_dest')
        m.add_optional_exists('o_dest')

        return m
    def _get_test_manifest(self):
        m = InstallManifest()
        m.add_symlink(self.tmppath('s_source'), 's_dest')
        m.add_copy(self.tmppath('c_source'), 'c_dest')
        m.add_preprocess(self.tmppath('p_source'), 'p_dest', self.tmppath('p_source.pp'), '#', {'FOO':'BAR', 'BAZ':'QUX'})
        m.add_required_exists('e_dest')
        m.add_optional_exists('o_dest')
        m.add_pattern_symlink('ps_base', '*', 'ps_dest')
        m.add_pattern_copy('pc_base', '**', 'pc_dest')

        return m
Exemple #12
0
    def _get_test_manifest(self):
        m = InstallManifest()
        m.add_link(self.tmppath('s_source'), 's_dest')
        m.add_copy(self.tmppath('c_source'), 'c_dest')
        m.add_preprocess(self.tmppath('p_source'), 'p_dest', self.tmppath('p_source.pp'), '#', {'FOO':'BAR', 'BAZ':'QUX'})
        m.add_required_exists('e_dest')
        m.add_optional_exists('o_dest')
        m.add_pattern_link('ps_base', '*', 'ps_dest')
        m.add_pattern_copy('pc_base', '**', 'pc_dest')
        m.add_content('the content\non\nmultiple lines', 'content')

        return m
Exemple #13
0
    def test_or(self):
        m1 = self._get_test_manifest()
        m2 = InstallManifest()
        m2.add_symlink('s_source2', 's_dest2')
        m2.add_copy('c_source2', 'c_dest2')

        m1 |= m2

        self.assertEqual(len(m2), 2)
        self.assertEqual(len(m1), 6)

        self.assertIn('s_dest2', m1)
        self.assertIn('c_dest2', m1)
Exemple #14
0
    def _get_test_manifest(self):
        m = InstallManifest()
        m.add_symlink(self.tmppath("s_source"), "s_dest")
        m.add_copy(self.tmppath("c_source"), "c_dest")
        m.add_preprocess(
            self.tmppath("p_source"), "p_dest", self.tmppath("p_source.pp"), "#", {"FOO": "BAR", "BAZ": "QUX"}
        )
        m.add_required_exists("e_dest")
        m.add_optional_exists("o_dest")
        m.add_pattern_symlink("ps_base", "*", "ps_dest")
        m.add_pattern_copy("pc_base", "**", "pc_dest")

        return m
    def test_or(self):
        m1 = self._get_test_manifest()
        m2 = InstallManifest()
        m2.add_symlink("s_source2", "s_dest2")
        m2.add_copy("c_source2", "c_dest2")

        m1 |= m2

        self.assertEqual(len(m2), 2)
        self.assertEqual(len(m1), 5)

        self.assertIn("s_dest2", m1)
        self.assertIn("c_dest2", m1)
Exemple #16
0
    def test_or(self):
        m1 = self._get_test_manifest()
        orig_length = len(m1)
        m2 = InstallManifest()
        m2.add_link("s_source2", "s_dest2")
        m2.add_copy("c_source2", "c_dest2")

        m1 |= m2

        self.assertEqual(len(m2), 2)
        self.assertEqual(len(m1), orig_length + 2)

        self.assertIn("s_dest2", m1)
        self.assertIn("c_dest2", m1)
    def test_adds(self):
        m = InstallManifest()
        m.add_symlink("s_source", "s_dest")
        m.add_copy("c_source", "c_dest")
        m.add_required_exists("e_dest")

        self.assertEqual(len(m), 3)
        self.assertIn("s_dest", m)
        self.assertIn("c_dest", m)
        self.assertIn("e_dest", m)

        with self.assertRaises(ValueError):
            m.add_symlink("s_other", "s_dest")

        with self.assertRaises(ValueError):
            m.add_copy("c_other", "c_dest")

        with self.assertRaises(ValueError):
            m.add_required_exists("e_dest")
Exemple #18
0
    def test_adds(self):
        m = InstallManifest()
        m.add_link("s_source", "s_dest")
        m.add_copy("c_source", "c_dest")
        m.add_required_exists("e_dest")
        m.add_optional_exists("o_dest")
        m.add_pattern_link("ps_base", "ps/*", "ps_dest")
        m.add_pattern_copy("pc_base", "pc/**", "pc_dest")
        m.add_preprocess("p_source", "p_dest", "p_source.pp")
        m.add_content("content", "content")

        self.assertEqual(len(m), 8)
        self.assertIn("s_dest", m)
        self.assertIn("c_dest", m)
        self.assertIn("p_dest", m)
        self.assertIn("e_dest", m)
        self.assertIn("o_dest", m)
        self.assertIn("content", m)

        with self.assertRaises(ValueError):
            m.add_link("s_other", "s_dest")

        with self.assertRaises(ValueError):
            m.add_copy("c_other", "c_dest")

        with self.assertRaises(ValueError):
            m.add_preprocess("p_other", "p_dest", "p_other.pp")

        with self.assertRaises(ValueError):
            m.add_required_exists("e_dest")

        with self.assertRaises(ValueError):
            m.add_optional_exists("o_dest")

        with self.assertRaises(ValueError):
            m.add_pattern_link("ps_base", "ps/*", "ps_dest")

        with self.assertRaises(ValueError):
            m.add_pattern_copy("pc_base", "pc/**", "pc_dest")

        with self.assertRaises(ValueError):
            m.add_content("content", "content")
Exemple #19
0
    def _get_test_manifest(self):
        m = InstallManifest()
        m.add_link(self.tmppath("s_source"), "s_dest")
        m.add_copy(self.tmppath("c_source"), "c_dest")
        m.add_preprocess(
            self.tmppath("p_source"),
            "p_dest",
            self.tmppath("p_source.pp"),
            "#",
            {
                "FOO": "BAR",
                "BAZ": "QUX"
            },
        )
        m.add_required_exists("e_dest")
        m.add_optional_exists("o_dest")
        m.add_pattern_link("ps_base", "*", "ps_dest")
        m.add_pattern_copy("pc_base", "**", "pc_dest")
        m.add_content("the content\non\nmultiple lines", "content")

        return m
Exemple #20
0
    def test_adds(self):
        m = InstallManifest()
        m.add_symlink("s_source", "s_dest")
        m.add_copy("c_source", "c_dest")
        m.add_required_exists("e_dest")
        m.add_optional_exists("o_dest")
        m.add_pattern_symlink("ps_base", "ps/*", "ps_dest")
        m.add_pattern_copy("pc_base", "pc/**", "pc_dest")
        m.add_preprocess("p_source", "p_dest", "p_source.pp")

        self.assertEqual(len(m), 7)
        self.assertIn("s_dest", m)
        self.assertIn("c_dest", m)
        self.assertIn("p_dest", m)
        self.assertIn("e_dest", m)
        self.assertIn("o_dest", m)

        with self.assertRaises(ValueError):
            m.add_symlink("s_other", "s_dest")

        with self.assertRaises(ValueError):
            m.add_copy("c_other", "c_dest")

        with self.assertRaises(ValueError):
            m.add_preprocess("p_other", "p_dest", "p_other.pp")

        with self.assertRaises(ValueError):
            m.add_required_exists("e_dest")

        with self.assertRaises(ValueError):
            m.add_optional_exists("o_dest")

        with self.assertRaises(ValueError):
            m.add_pattern_symlink("ps_base", "ps/*", "ps_dest")

        with self.assertRaises(ValueError):
            m.add_pattern_copy("pc_base", "pc/**", "pc_dest")
Exemple #21
0
def repackage_msix(
    dir_or_package,
    channel=None,
    branding=None,
    template=None,
    distribution_dirs=[],
    locale_allowlist=set(),
    version=None,
    vendor=None,
    displayname=None,
    app_name="firefox",
    identity=None,
    publisher=None,
    publisher_display_name="Mozilla Corporation",
    arch=None,
    output=None,
    force=False,
    log=None,
    verbose=False,
    makeappx=None,
):
    if not channel:
        raise Exception("channel is required")
    if channel not in ["official", "beta", "aurora", "nightly", "unofficial"]:
        raise Exception("channel is unrecognized: {}".format(channel))

    if not branding:
        raise Exception("branding dir is required")
    if not os.path.isdir(branding):
        raise Exception("branding dir {} does not exist".format(branding))

    # TODO: maybe we can fish this from the package directly?  Maybe from a DLL,
    # maybe from application.ini?
    if arch is None or arch not in _MSIX_ARCH.keys():
        raise Exception(
            "arch name must be provided and one of {}.".format(_MSIX_ARCH.keys())
        )

    if not os.path.exists(dir_or_package):
        raise Exception("{} does not exist".format(dir_or_package))

    if (
        os.path.isfile(dir_or_package)
        and os.path.splitext(dir_or_package)[1] == ".msix"
    ):
        # The convention is $MOZBUILD_STATE_PATH/cache/$FEATURE.
        msix_dir = mozpath.normsep(
            mozpath.join(
                get_state_dir(),
                "cache",
                "mach-msix",
                "msix-unpack",
            )
        )

        if os.path.exists(msix_dir):
            shutil.rmtree(msix_dir)
        ensureParentDir(msix_dir)

        dir_or_package = unpack_msix(dir_or_package, msix_dir, log=log, verbose=verbose)

    log(
        logging.INFO,
        "msix",
        {
            "input": dir_or_package,
        },
        "Adding files from '{input}'",
    )

    if os.path.isdir(dir_or_package):
        finder = FileFinder(dir_or_package)
    else:
        finder = JarFinder(dir_or_package, JarReader(dir_or_package))

    values = get_application_ini_values(
        finder,
        dict(section="App", value="CodeName", fallback="Name"),
        dict(section="App", value="Vendor"),
    )
    first = next(values)
    displayname = displayname or "Mozilla {}".format(first)
    second = next(values)
    vendor = vendor or second

    # For `AppConstants.jsm` and `brand.properties`, which are in the omnijar in packaged builds.
    # The nested langpack XPI files can't be read by `mozjar.py`.
    unpack_finder = UnpackFinder(finder, unpack_xpi=False)

    if not version:
        values = get_appconstants_jsm_values(
            unpack_finder, "MOZ_APP_VERSION_DISPLAY", "MOZ_BUILDID"
        )
        display_version = next(values)
        buildid = next(values)
        version = get_embedded_version(display_version, buildid)
        log(
            logging.INFO,
            "msix",
            {
                "version": version,
                "display_version": display_version,
                "buildid": buildid,
            },
            "AppConstants.jsm display version is '{display_version}' and build ID is '{buildid}':"
            + " embedded version will be '{version}'",
        )

    # TODO: Bug 1721922: localize this description via Fluent.
    lines = []
    for _, f in unpack_finder.find("**/chrome/en-US/locale/branding/brand.properties"):
        lines.extend(
            line
            for line in f.open().read().decode("utf-8").splitlines()
            if "brandFullName" in line
        )
    (brandFullName,) = lines  # We expect exactly one definition.
    _, _, brandFullName = brandFullName.partition("=")
    brandFullName = brandFullName.strip()

    # We don't have a build at repackage-time to gives us this value, and the
    # source of truth is a branding-specific `configure.sh` shell script that we
    # can't easily evaluate completely here.  Instead, we take the last value
    # from `configure.sh`.
    lines = [
        line
        for line in open(mozpath.join(branding, "configure.sh")).readlines()
        if "MOZ_IGECKOBACKCHANNEL_IID" in line
    ]
    MOZ_IGECKOBACKCHANNEL_IID = lines[-1]
    _, _, MOZ_IGECKOBACKCHANNEL_IID = MOZ_IGECKOBACKCHANNEL_IID.partition("=")
    MOZ_IGECKOBACKCHANNEL_IID = MOZ_IGECKOBACKCHANNEL_IID.strip()
    if MOZ_IGECKOBACKCHANNEL_IID.startswith(('"', "'")):
        MOZ_IGECKOBACKCHANNEL_IID = MOZ_IGECKOBACKCHANNEL_IID[1:-1]

    # The convention is $MOZBUILD_STATE_PATH/cache/$FEATURE.
    output_dir = mozpath.normsep(
        mozpath.join(
            get_state_dir(), "cache", "mach-msix", "msix-temp-{}".format(channel)
        )
    )

    if channel == "beta":
        # Release (official) and Beta share branding.  Differentiate Beta a little bit.
        displayname += " Beta"
        brandFullName += " Beta"

    # Like 'Firefox Package Root', 'Firefox Nightly Package Root', 'Firefox Beta
    # Package Root'.  This is `BrandFullName` in the installer, and we want to
    # be close but to not match.  By not matching, we hope to prevent confusion
    # and/or errors between regularly installed builds and App Package builds.
    instdir = "{} Package Root".format(displayname)

    # The standard package name is like "CompanyNoSpaces.ProductNoSpaces".
    identity = identity or "{}.{}".format(vendor, displayname).replace(" ", "")

    # We might want to include the publisher ID hash here.  I.e.,
    # "__{publisherID}".  My locally produced MSIX was named like
    # `Mozilla.MozillaFirefoxNightly_89.0.0.0_x64__4gf61r4q480j0`, suggesting also a
    # missing field, but it's not necessary, since this is just an output file name.
    package_output_name = "{identity}_{version}_{arch}".format(
        identity=identity, version=version, arch=_MSIX_ARCH[arch]
    )
    # The convention is $MOZBUILD_STATE_PATH/cache/$FEATURE.
    default_output = mozpath.normsep(
        mozpath.join(
            get_state_dir(), "cache", "mach-msix", "{}.msix".format(package_output_name)
        )
    )
    output = output or default_output
    log(logging.INFO, "msix", {"output": output}, "Repackaging to: {output}")

    m = InstallManifest()
    m.add_copy(mozpath.join(template, "Resources.pri"), "Resources.pri")

    m.add_pattern_copy(mozpath.join(branding, "msix", "Assets"), "**", "Assets")
    m.add_pattern_copy(mozpath.join(template, "VFS"), "**", "VFS")

    copier = FileCopier()

    # TODO: Bug 1710147: filter out MSVCRT files and use a dependency instead.
    for p, f in finder:
        if not os.path.isdir(dir_or_package):
            # In archived builds, `p` is like "firefox/firefox.exe"; we want just "firefox.exe".
            pp = os.path.relpath(p, "firefox")
        else:
            # In local builds and unpacked MSIX directories, `p` is like "firefox.exe" already.
            pp = p

        if pp.startswith("distribution"):
            # Treat any existing distribution as a distribution directory,
            # potentially with language packs. This makes it easy to repack
            # unpacked MSIXes.
            distribution_dir = mozpath.join(dir_or_package, "distribution")
            if distribution_dir not in distribution_dirs:
                distribution_dirs.append(distribution_dir)

            continue

        copier.add(mozpath.normsep(mozpath.join("VFS", "ProgramFiles", instdir, pp)), f)

    # Locales to declare as supported in `AppxManifest.xml`.
    locales = set(["en-US"])

    for distribution_dir in [
        mozpath.join(template, "distribution")
    ] + distribution_dirs:
        log(
            logging.INFO,
            "msix",
            {"dir": distribution_dir},
            "Adding distribution files from {dir}",
        )

        # In automation, we have no easy way to remap the names of artifacts fetched from dependent
        # tasks.  In particular, langpacks will be named like `target.langpack.xpi`.  The fetch
        # tasks do allow us to put them in a per-locale directory, so that the entire set can be
        # fetched.  Here we remap the names.
        finder = FileFinder(distribution_dir)

        for p, f in finder:
            locale = None
            if os.path.basename(p) == "target.langpack.xpi":
                # Turn "/path/to/LOCALE/target.langpack.xpi" into "LOCALE".  This is how langpacks
                # are presented in CI.
                base, locale = os.path.split(os.path.dirname(p))

                # Like "locale-LOCALE/[email protected]".  This is what AMO
                # serves and how flatpak builds name langpacks, but not how snap builds name
                # langpacks.  I can't explain the discrepancy.
                dest = mozpath.normsep(
                    mozpath.join(
                        base,
                        f"locale-{locale}",
                        f"langpack-{locale}@firefox.mozilla.org.xpi",
                    )
                )

                log(
                    logging.DEBUG,
                    "msix",
                    {"path": p, "dest": dest},
                    "Renaming langpack {path} to {dest}",
                )

            elif os.path.basename(p).startswith("langpack-"):
                # Turn "/path/to/[email protected]" into "LOCALE".  This is
                # how langpacks are presented from an unpacked MSIX.
                _, _, locale = os.path.basename(p).partition("langpack-")
                locale, _, _ = locale.partition("@")
                dest = p

            else:
                dest = p

            if locale:
                locale = locale.strip().lower()
                locales.add(locale)
                log(
                    logging.DEBUG,
                    "msix",
                    {"locale": locale, "dest": dest},
                    "Distributing locale '{locale}' from {dest}",
                )

            dest = mozpath.normsep(
                mozpath.join("VFS", "ProgramFiles", instdir, "distribution", dest)
            )
            if copier.contains(dest):
                log(
                    logging.INFO,
                    "msix",
                    {"dest": dest, "path": mozpath.join(finder.base, p)},
                    "Skipping duplicate: {dest} from {path}",
                )
                continue

            log(
                logging.DEBUG,
                "msix",
                {"dest": dest, "path": mozpath.join(finder.base, p)},
                "Adding distribution path: {dest} from {path}",
            )

            copier.add(
                dest,
                f,
            )

    locales.remove("en-US")

    # Windows MSIX packages support a finite set of locales: see
    # https://docs.microsoft.com/en-us/windows/uwp/publish/supported-languages, which is encoded in
    # https://searchfox.org/mozilla-central/source/browser/installer/windows/msix/msix-all-locales.
    # We distribute all of the langpacks supported by the release channel in our MSIX, which is
    # encoded in https://searchfox.org/mozilla-central/source/browser/locales/all-locales.  But we
    # only advertise support in the App manifest for the intersection of that set and the set of
    # supported locales.
    #
    # We distribute all langpacks to avoid the following issue.  Suppose a user manually installs a
    # langpack that is not supported by Windows, and then updates the installed MSIX package.  MSIX
    # package upgrades are essentially paveover installs, so there is no opportunity for Firefox to
    # update the langpack before the update.  But, since all langpacks are bundled with the MSIX,
    # that langpack will be up-to-date, preventing one class of YSOD.
    unadvertised = set()
    if locale_allowlist:
        unadvertised = locales - locale_allowlist
        locales = locales & locale_allowlist
    for locale in sorted(unadvertised):
        log(
            logging.INFO,
            "msix",
            {"locale": locale},
            "Not advertising distributed locale '{locale}' that is not recognized by Windows",
        )

    locales = ["en-US"] + list(sorted(locales))
    resource_language_list = "\n".join(
        f'    <Resource Language="{locale}" />' for locale in sorted(locales)
    )

    defines = {
        "APPX_ARCH": _MSIX_ARCH[arch],
        "APPX_DISPLAYNAME": brandFullName,
        "APPX_DESCRIPTION": brandFullName,
        # Like 'Mozilla.MozillaFirefox', 'Mozilla.MozillaFirefoxBeta', or
        # 'Mozilla.MozillaFirefoxNightly'.
        "APPX_IDENTITY": identity,
        # Like 'Firefox Package Root', 'Firefox Nightly Package Root', 'Firefox
        # Beta Package Root'.  See above.
        "APPX_INSTDIR": instdir,
        # Like 'Firefox%20Package%20Root'.
        "APPX_INSTDIR_QUOTED": urllib.parse.quote(instdir),
        "APPX_PUBLISHER": publisher,
        "APPX_PUBLISHER_DISPLAY_NAME": publisher_display_name,
        "APPX_RESOURCE_LANGUAGE_LIST": resource_language_list,
        "APPX_VERSION": version,
        "MOZ_APP_DISPLAYNAME": displayname,
        "MOZ_APP_NAME": app_name,
        "MOZ_IGECKOBACKCHANNEL_IID": MOZ_IGECKOBACKCHANNEL_IID,
    }

    m.add_preprocess(
        mozpath.join(template, "AppxManifest.xml.in"),
        "AppxManifest.xml",
        [],
        defines=defines,
        marker="<!-- #",  # So that we can have well-formed XML.
    )
    m.populate_registry(copier)

    output_dir = mozpath.abspath(output_dir)
    ensureParentDir(output_dir)

    start = time.time()
    result = copier.copy(
        output_dir, remove_empty_directories=True, skip_if_older=not force
    )
    if log:
        log_copy_result(log, time.time() - start, output_dir, result)

    if verbose:
        # Dump AppxManifest.xml contents for ease of debugging.
        log(logging.DEBUG, "msix", {}, "AppxManifest.xml")
        log(logging.DEBUG, "msix", {}, ">>>")
        for line in open(mozpath.join(output_dir, "AppxManifest.xml")).readlines():
            log(logging.DEBUG, "msix", {}, line[:-1])  # Drop trailing line terminator.
        log(logging.DEBUG, "msix", {}, "<<<")

    if not makeappx:
        makeappx = find_sdk_tool("makeappx.exe", log=log)
    if not makeappx:
        raise ValueError(
            "makeappx is required; " "set MAKEAPPX or WINDOWSSDKDIR or PATH"
        )

    # `makeappx.exe` supports both slash and hyphen style arguments; `makemsix`
    # supports only hyphen style.  `makeappx.exe` allows to overwrite and to
    # provide more feedback, so we prefer invoking with these flags.  This will
    # also accommodate `wine makeappx.exe`.
    stdout = subprocess.run(
        [makeappx], check=False, capture_output=True, universal_newlines=True
    ).stdout
    is_makeappx = "MakeAppx Tool" in stdout

    if is_makeappx:
        args = [makeappx, "pack", "/d", output_dir, "/p", output, "/overwrite"]
    else:
        args = [makeappx, "pack", "-d", output_dir, "-p", output]
    if verbose and is_makeappx:
        args.append("/verbose")
    joined = " ".join(shlex_quote(arg) for arg in args)
    log(logging.INFO, "msix", {"args": args, "joined": joined}, "Invoking: {joined}")

    sys.stdout.flush()  # Otherwise the subprocess output can be interleaved.
    if verbose:
        subprocess.check_call(args, universal_newlines=True)
    else:
        # Suppress output unless we fail.
        try:
            subprocess.check_output(args, universal_newlines=True)
        except subprocess.CalledProcessError as e:
            sys.stderr.write(e.output)
            raise

    return output
class TestInstallManifest(HelperMixin, unittest.TestCase):
    def setUp(self):
        HelperMixin.setUp(self)
        self.srcdir = os.path.join(self.test_dir, 'src')
        os.mkdir(self.srcdir)
        self.objdir = os.path.join(self.test_dir, 'obj')
        os.mkdir(self.objdir)
        self.manifest = InstallManifest()
        self.canonical_mapping = {}
        for s in ['src1', 'src2']:
            srcfile = normpath(os.path.join(self.srcdir, s))
            objfile = normpath(os.path.join(self.objdir, s))
            self.canonical_mapping[objfile] = srcfile
            self.manifest.add_copy(srcfile, s)
        self.manifest_file = os.path.join(self.test_dir, 'install-manifest')
        self.manifest.write(self.manifest_file)

    def testMakeFileMapping(self):
        '''
        Test that valid arguments are validated.
        '''
        arg = '%s,%s' % (self.manifest_file, self.objdir)
        ret = symbolstore.validate_install_manifests([arg])
        self.assertEqual(len(ret), 1)
        manifest, dest = ret[0]
        self.assertTrue(isinstance(manifest, InstallManifest))
        self.assertEqual(dest, self.objdir)

        file_mapping = symbolstore.make_file_mapping(ret)
        for obj, src in self.canonical_mapping.iteritems():
            self.assertTrue(obj in file_mapping)
            self.assertEqual(file_mapping[obj], src)

    def testMissingFiles(self):
        '''
        Test that missing manifest files or install directories give errors.
        '''
        missing_manifest = os.path.join(self.test_dir, 'missing-manifest')
        arg = '%s,%s' % (missing_manifest, self.objdir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, missing_manifest)

        missing_install_dir = os.path.join(self.test_dir, 'missing-dir')
        arg = '%s,%s' % (self.manifest_file, missing_install_dir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, missing_install_dir)

    def testBadManifest(self):
        '''
        Test that a bad manifest file give errors.
        '''
        bad_manifest = os.path.join(self.test_dir, 'bad-manifest')
        with open(bad_manifest, 'wb') as f:
            f.write('junk\n')
        arg = '%s,%s' % (bad_manifest, self.objdir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, bad_manifest)

    def testBadArgument(self):
        '''
        Test that a bad manifest argument gives an error.
        '''
        with self.assertRaises(ValueError) as e:
            symbolstore.validate_install_manifests(['foo'])
Exemple #23
0
class TestInstallManifest(HelperMixin, unittest.TestCase):
    def setUp(self):
        HelperMixin.setUp(self)
        self.srcdir = os.path.join(self.test_dir, 'src')
        os.mkdir(self.srcdir)
        self.objdir = os.path.join(self.test_dir, 'obj')
        os.mkdir(self.objdir)
        self.manifest = InstallManifest()
        self.canonical_mapping = {}
        for s in ['src1', 'src2']:
            srcfile = os.path.join(self.srcdir, s)
            objfile = os.path.join(self.objdir, s)
            self.canonical_mapping[objfile] = srcfile
            self.manifest.add_copy(srcfile, s)
        self.manifest_file = os.path.join(self.test_dir, 'install-manifest')
        self.manifest.write(self.manifest_file)

    def testMakeFileMapping(self):
        '''
        Test that valid arguments are validated.
        '''
        arg = '%s,%s' % (self.manifest_file, self.objdir)
        ret = symbolstore.validate_install_manifests([arg])
        self.assertEqual(len(ret), 1)
        manifest, dest = ret[0]
        self.assertTrue(isinstance(manifest, InstallManifest))
        self.assertEqual(dest, self.objdir)

        file_mapping = symbolstore.make_file_mapping(ret)
        for obj, src in self.canonical_mapping.iteritems():
            self.assertTrue(obj in file_mapping)
            self.assertEqual(file_mapping[obj], src)

    def testMissingFiles(self):
        '''
        Test that missing manifest files or install directories give errors.
        '''
        missing_manifest = os.path.join(self.test_dir, 'missing-manifest')
        arg = '%s,%s' % (missing_manifest, self.objdir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, missing_manifest)

        missing_install_dir = os.path.join(self.test_dir, 'missing-dir')
        arg = '%s,%s' % (self.manifest_file, missing_install_dir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, missing_install_dir)

    def testBadManifest(self):
        '''
        Test that a bad manifest file give errors.
        '''
        bad_manifest = os.path.join(self.test_dir, 'bad-manifest')
        with open(bad_manifest, 'wb') as f:
            f.write('junk\n')
        arg = '%s,%s' % (bad_manifest, self.objdir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, bad_manifest)

    def testBadArgument(self):
        '''
        Test that a bad manifest argument gives an error.
        '''
        with self.assertRaises(ValueError) as e:
            symbolstore.validate_install_manifests(['foo'])
class TestInstallManifest(HelperMixin, unittest.TestCase):
    def setUp(self):
        HelperMixin.setUp(self)
        self.srcdir = os.path.join(self.test_dir, "src")
        os.mkdir(self.srcdir)
        self.objdir = os.path.join(self.test_dir, "obj")
        os.mkdir(self.objdir)
        self.manifest = InstallManifest()
        self.canonical_mapping = {}
        for s in ["src1", "src2"]:
            srcfile = os.path.join(self.srcdir, s)
            objfile = os.path.join(self.objdir, s)
            self.canonical_mapping[objfile] = srcfile
            self.manifest.add_copy(srcfile, s)
        self.manifest_file = os.path.join(self.test_dir, "install-manifest")
        self.manifest.write(self.manifest_file)

    def testMakeFileMapping(self):
        """
        Test that valid arguments are validated.
        """
        arg = "%s,%s" % (self.manifest_file, self.objdir)
        ret = symbolstore.validate_install_manifests([arg])
        self.assertEqual(len(ret), 1)
        manifest, dest = ret[0]
        self.assertTrue(isinstance(manifest, InstallManifest))
        self.assertEqual(dest, self.objdir)

        file_mapping = symbolstore.make_file_mapping(ret)
        for obj, src in self.canonical_mapping.iteritems():
            self.assertTrue(obj in file_mapping)
            self.assertEqual(file_mapping[obj], src)

    def testMissingFiles(self):
        """
        Test that missing manifest files or install directories give errors.
        """
        missing_manifest = os.path.join(self.test_dir, "missing-manifest")
        arg = "%s,%s" % (missing_manifest, self.objdir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, missing_manifest)

        missing_install_dir = os.path.join(self.test_dir, "missing-dir")
        arg = "%s,%s" % (self.manifest_file, missing_install_dir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, missing_install_dir)

    def testBadManifest(self):
        """
        Test that a bad manifest file give errors.
        """
        bad_manifest = os.path.join(self.test_dir, "bad-manifest")
        with open(bad_manifest, "wb") as f:
            f.write("junk\n")
        arg = "%s,%s" % (bad_manifest, self.objdir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, bad_manifest)

    def testBadArgument(self):
        """
        Test that a bad manifest argument gives an error.
        """
        with self.assertRaises(ValueError) as e:
            symbolstore.validate_install_manifests(["foo"])
class TestInstallManifest(HelperMixin, unittest.TestCase):
    def setUp(self):
        HelperMixin.setUp(self)
        self.srcdir = os.path.join(self.test_dir, "src")
        os.mkdir(self.srcdir)
        self.objdir = os.path.join(self.test_dir, "obj")
        os.mkdir(self.objdir)
        self.manifest = InstallManifest()
        self.canonical_mapping = {}
        for s in ["src1", "src2"]:
            srcfile = realpath(os.path.join(self.srcdir, s))
            objfile = realpath(os.path.join(self.objdir, s))
            self.canonical_mapping[objfile] = srcfile
            self.manifest.add_copy(srcfile, s)
        self.manifest_file = os.path.join(self.test_dir, "install-manifest")
        self.manifest.write(self.manifest_file)

    def testMakeFileMapping(self):
        """
        Test that valid arguments are validated.
        """
        arg = "%s,%s" % (self.manifest_file, self.objdir)
        ret = symbolstore.validate_install_manifests([arg])
        self.assertEqual(len(ret), 1)
        manifest, dest = ret[0]
        self.assertTrue(isinstance(manifest, InstallManifest))
        self.assertEqual(dest, self.objdir)

        file_mapping = symbolstore.make_file_mapping(ret)
        for obj, src in self.canonical_mapping.items():
            self.assertTrue(obj in file_mapping)
            self.assertEqual(file_mapping[obj], src)

    def testMissingFiles(self):
        """
        Test that missing manifest files or install directories give errors.
        """
        missing_manifest = os.path.join(self.test_dir, "missing-manifest")
        arg = "%s,%s" % (missing_manifest, self.objdir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, missing_manifest)

        missing_install_dir = os.path.join(self.test_dir, "missing-dir")
        arg = "%s,%s" % (self.manifest_file, missing_install_dir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, missing_install_dir)

    def testBadManifest(self):
        """
        Test that a bad manifest file give errors.
        """
        bad_manifest = os.path.join(self.test_dir, "bad-manifest")
        with open(bad_manifest, "w") as f:
            f.write("junk\n")
        arg = "%s,%s" % (bad_manifest, self.objdir)
        with self.assertRaises(IOError) as e:
            symbolstore.validate_install_manifests([arg])
            self.assertEqual(e.filename, bad_manifest)

    def testBadArgument(self):
        """
        Test that a bad manifest argument gives an error.
        """
        with self.assertRaises(ValueError):
            symbolstore.validate_install_manifests(["foo"])
Exemple #26
0
def main(output_dirname, verbose, *input_dirs):
    # Map directories to source paths, like
    # `{'values-large-v11': ['/path/to/values-large-v11/strings.xml',
    #                        '/path/to/values-large-v11/colors.xml', ...], ...}`.
    values = defaultdict(list)
    # Map unversioned resource names to maps from versions to source paths, like:
    # `{'drawable-large/icon.png':
    #     {None: '/path/to/drawable-large/icon.png',
    #      11: '/path/to/drawable-large-v11/icon.png', ...}, ...}`.
    resources = defaultdict(dict)

    manifest = InstallManifest()

    for p in uniqify(input_dirs):
        finder = FileFinder(p, find_executables=False)

        values_pattern = 'values*/*.xml'
        for path, _ in finder.find('*/*'):
            if path in MANIFEST_EXCLUSIONS:
                continue

            source_path = mozpath.join(finder.base, path)

            if mozpath.match(path, values_pattern):
                dir, _name = path.split('/')
                dir = with_version(dir)
                values[dir].append(source_path)
                continue

            (resource, version) = classify(path)

            # Earlier paths are taken in preference to later paths.
            # This agrees with aapt.
            if version not in resources:
                resources[resource][version] = source_path

    # Step 1: merge all XML values into one single, sorted
    # per-configuration values.xml file.  This apes what the Android
    # Gradle resource merging algorithm does.
    merged_values = defaultdict(list)

    for dir, files in values.items():
        for file in files:
            values = ET.ElementTree(file=file).getroot()
            merged_values[dir].extend(values)

        values = ET.Element('resources')
        # Sort by <type> tag, and then by name.  Note that <item
        # type="type"> is equivalent to <type>.
        key = lambda x: (resource_type.get(x.get('type', x.tag)), x.get('name')
                         )
        values[:] = sorted(merged_values[dir], key=key)

        for value in values:
            if value.get('name') == 'TextAppearance.Design.Snackbar.Message':
                if value.get('{http://schemas.android.com/tools}override',
                             False):
                    values.remove(value)
                    break

        merged_values[dir] = values

    for dir, values in merged_values.items():
        o = mozpath.join(output_dirname, dir, '{}.xml'.format(dir))
        ensureParentDir(o)
        ET.ElementTree(values).write(o)

        manifest.add_required_exists(mozpath.join(dir, '{}.xml'.format(dir)))

    # Step 2a: add version numbers for unversioned features
    # corresponding to when the feature was introduced.  Resource
    # qualifiers will never be recognized by Android versions before
    # they were introduced.  For example, density qualifiers are
    # supported only in Android v4 and above.  Therefore
    # "drawable-hdpi" is implicitly "drawable-hdpi-v4".  We version
    # such unversioned resources here.
    for (resource, versions) in resources.items():
        if None in versions:
            dir, name = resource.split('/')
            new_dir = with_version(dir)
            (new_resource,
             new_version) = classify('{}/{}'.format(new_dir, name))
            if new_resource != resource:
                raise ValueError('this is bad')

            # `new_version` might be None: for example, `dir` might be "drawable".
            source_path = versions.pop(None)
            versions[new_version] = source_path

            if verbose:
                if new_version:
                    print("Versioning unversioned resource {} as {}-v{}/{}".
                          format(source_path, dir, new_version, name))

    # TODO: make this a command line argument that takes MOZ_ANDROID_MIN_SDK_VERSION.
    min_sdk = 15
    retained = defaultdict(dict)

    # Step 2b: drop resource directories that will never be used by
    # Android on device.  This depends on the minimum supported
    # Android SDK version.  Suppose the minimum SDK is 15 and we have
    # drawable-v4/icon.png and drawable-v11/icon.png.  The v4 version
    # will never be chosen, since v15 is always greater than v11.
    for (resource, versions) in resources.items():

        def key(v):
            return 0 if v is None else v

        # Versions in descending order.
        version_list = sorted(versions.keys(), key=key, reverse=True)
        for version in version_list:
            retained[resource][version] = versions[version]
            if version is not None and version <= min_sdk:
                break

    if set(retained.keys()) != set(resources.keys()):
        raise ValueError('Something terrible has happened; retained '
                         'resource names do not match input resources '
                         'names')

    if verbose:
        for resource in resources:
            if resources[resource] != retained[resource]:
                for version in sorted(resources[resource].keys(),
                                      reverse=True):
                    if version in retained[resource]:
                        print("Keeping reachable resource {}".format(
                            resources[resource][version]))
                    else:
                        print("Dropping unreachable resource {}".format(
                            resources[resource][version]))

    # Populate manifest.
    for (resource, versions) in retained.items():
        for version in sorted(versions.keys(), reverse=True):
            path = resource
            if version:
                dir, name = resource.split('/')
                path = '{}-v{}/{}'.format(dir, version, name)
            manifest.add_copy(versions[version], path)

    copier = FileCopier()
    manifest.populate_registry(copier)
    print('mr', os.getcwd())
    result = copier.copy(output_dirname,
                         remove_unaccounted=True,
                         remove_all_directory_symlinks=False,
                         remove_empty_directories=True)

    if verbose:
        print('Updated:', result.updated_files_count)
        print('Removed:',
              result.removed_files_count + result.removed_directories_count)
        print('Existed:', result.existing_files_count)

    return 0