import { max } from 'underscore'; import os from 'os'; const utils = require('./utils'); /* Meteor's current architecture scheme defines the following virtual * machine types, which are defined by specifying what is promised by * the host environment: * * browser.w3c * A web browser compliant with modern standards. This is * intentionally a broad definition. In the coming years, as web * standards evolve, we will likely tighten it up. * * browser.ie[678] * Old versions of Internet Explorer (not sure yet exactly which * versions to distinguish -- maybe 6 and 8?) * * os.linux.x86_64 * Linux on Intel x86 architecture. x86_64 means a system that can * run 64-bit images, furnished with 64-bit builds of shared * libraries (there is no guarantee that 32-bit builds of shared * libraries will be available). x86_32 means a system that can run * 32-bit images, furnished with 32-bit builds of shared libraries. * Additionally, if a package contains shared libraries (for use by * other packages), then if the package is built for x86_64, it * should contain a 64-bit version of the library, and likewise for * 32-bit. * * Operationally speaking, if you worked at it, under this * definition it would be possible to build a Linux system that can * run both x86_64 and x86_32 images (eg, by using a 64-bit kernel * and making sure that both versions of all relevant libraries were * installed). But we require such a host to decide whether it is * x86_64 or x86_32, and stick with it. You can't load a combination * of packages from each and expect them to work together, because * if they contain shared libraries they all need to have the same * architecture. * * Basically the punchline is: if you installed the 32-bit version * of Ubuntu, you've got a os.linux.x86_32 system and you will * use exclusively os.linux.x86_32 packages, and likewise * 64-bit. They are two parallel universes and which one you're in * is determined by which version of Red Hat or Ubuntu you * installed. * * os.osx.x86_64 * OS X (technically speaking, Darwin) on Intel x86 architecture, * with a kernel capable of loading 64-bit images, and 64-bit builds * of shared libraries available. If a os.osx.x86_64 package * contains a shared library, it is only required to provide a * 64-bit version of the library (it is not required to provide a * fat binary with both 32-bit and 64-bit builds). * * Note that in modern Darwin, both the 32 and 64 bit versions of * the kernel can load 64-bit images, and the Apple-supplied shared * libraries are fat binaries that include both 32-bit and 64-bit * builds in a single file. So it is technically fine (but * discouraged) for a os.osx.x86_64 to include a 32-bit * executable, if it only uses the system's shared libraries, but * you'll run into problems if shared libraries from other packages * are used. * * There is no os.osx.x86_32. Our experience is that such * hardware is virtually extinct. Meteor has never supported it and * nobody has asked for it. * * os.windows.x86_64 * Once, on the far side of yesterday, there was not a 64-bit * build of Meteor for Windows, due to the belief that Node didn't * take (enough?) advantage of a 64-bit platform. As time has passed, * and as V8 engine improvements have been bestowed upon it, this is * no longer as clear as it may have once been. Node.js Foundation * releases 64-bit versions themselves, likely for good reason. * Present-day operation of 64-bit binaries on 64-bit Windows * platforms show clear performance benefits over their 32-bit * siblings (e.g. 7-zip, et.al), so Meteor should also try to offer * that same benefit by building and offering a 64-bit version. * Meteor no longer supports Windows 32-bit. * * To be (more but far from completely) precise, the ABI for os.* * architectures includes a CPU type, a mode in which the code will be * run (eg, 64 bit), an executable file format (eg, ELF), a promise to * make any shared libraries available in a particular architecture, * and promise to set up the shared library search path * "appropriately". In the future it will also include some guarantees * about the directory layout in the environment, eg, location of a * directory where temporary files may be freely written. It does not * include any syscalls (beyond those used by code that customarily is * statically linked into every executable built on a platform, eg, * exit(2)). It does not guarantee the presence of any particular * shared libraries or programs (including any particular shell or * traditional tools like 'grep' or 'find'). * * To model the shared libraries that are required on a system (and * the particular versions that are required), and to model * dependencies on command-line programs like 'bash' and 'grep', the * idea is to have a package named something like 'posix-base' that * rolls up a reasonable base environment (including such modern * niceties as libopenssl) and is supplied by the container. This * allows it to be versioned, unlike architectures, which we hope to * avoid versioning. * * Q: What does "x86" mean? * A: It refers to the traditional Intel architecture, which * originally surfaced in CPUs such as the 8086 and the 80386. Those * of us who are older should remember that the last time that Intel * used this branding was the 80486, introduced in 1989, and that * today, parts that use this architecture bear names like "Core", * "Atom", and "Phenom", with no "86" it sight. We use it in the * architecture name anyway because we don't want to depart too far * from Linux's architecture names. * * Q: Why do we call it "x86_32" instead of the customary "i386" or * "i686"? * A: We wanted to have one name for 32-bit and one name for 64-bit, * rather than several names for each that are virtual synonyms for * each (eg, x86_64 vs amd64 vs ia64, i386 vs i686 vs x86). For the * moment anyway, we're willing to adopt a "one size fits all" * attitude to get there (no ability to have separate builds for 80386 * CPUs that don't support Pentium Pro extensions, for example -- * you'll have to do runtime detection if you need that). And as long * as we have to pick a name, we wanted to pick one that was super * clear (it is not obvious to many people that "i686" means "32-bit * Intel", because why should it be?) and didn't imply too close of an * equivalence to the precise meanings that other platforms may assign * to some of these strings. */ // Valid architectures that Meteor officially supports. export const VALID_ARCHITECTURES: Record = { "os.osx.x86_64": true, "os.osx.arm64": true, "os.linux.x86_64": true, "os.windows.x86_64": true, }; // Returns the fully qualified arch of this host -- something like // "os.linux.x86_32" or "os.osx.x86_64". Must be called inside // a fiber. Throws an error if it's not a supported architecture. // // If you change this, also change scripts/admin/launch-meteor let _host: string | null = null; // memoize export async function host() { if (!_host) { const run = async function (...args: Array) { const result = (await utils.execFile(args[0], args.slice(1))).stdout; if (! result) { throw new Error(`Can't get arch with ${args.join(" ")}?`); } return result.replace(/\s*$/, ''); // trailing whitespace }; const platform = os.platform(); if (platform === "darwin") { // Can't just test uname -m = x86_64, because Snow Leopard can // return other values. const arch = await run('uname', '-p'); if ((arch !== "i386" && arch !== "arm") || await run('sysctl', '-n', 'hw.cpu64bit_capable') !== "1") { throw new Error("Only 64-bit Intel and M1 processors are supported on OS X"); } if(arch === "arm"){ _host = "os.osx.arm64"; }else{ _host = "os.osx.x86_64"; } } else if (platform === "linux") { const machine = await run('uname', '-m'); if (["x86_64", "amd64", "ia64"].includes(machine)) { _host = "os.linux.x86_64"; } else { throw new Error(`Unsupported architecture: ${machine}`); } } else if (platform === "win32" && process.arch === "x64") { _host = "os.windows.x86_64"; } else { throw new Error(`Unsupported operating system: ${platform}`); } } return _host; } // In order to springboard to earlier Meteor releases that did not have // 64-bit Windows builds, Windows installations must be allowed to // download 32-bit builds of meteor-tool. export function acceptableMeteorToolArches(): string[] { if (os.platform() === "win32") { switch (utils.architecture()) { case "x86_32": return ["os.windows.x86_32"]; case "x86_64": return [ "os.windows.x86_64", "os.windows.x86_32", ]; } } return [host()]; } // 64-bit Windows machines that have been using a 32-bit version of Meteor // are eligible to switch to 64-bit beginning with Meteor 1.6, which is // the first version of Meteor that contains this code. export function canSwitchTo64Bit(): boolean { // Automatically switching from 32-bit to 64-bit Windows builds is // disabled for the time being, since downloading additional builds of // meteor-tool isn't stable enough at the moment (on Windows, at least) // to introduce in a release candidate. return false && utils.architecture() === "x86_64" && host() === "os.windows.x86_32"; } // True if `host` (an architecture name such as 'os.linux.x86_64') can run // programs of architecture `program` (which might be something like 'os', // 'os.linux', or 'os.linux.x86_64'). // // `host` and `program` are just mnemonics -- `host` does not // necessarily have to be a fully qualified architecture name. This // function just checks to see if `program` describes a set of // environments that is a (non-strict) superset of `host`. export function matches(host: string, program: string): boolean { return host.substr(0, program.length) === program && (host.length === program.length || host.substr(program.length, 1) === "."); } const legacyArches = [ "web.browser.legacy", // It's important to include web.browser.legacy resources in the Cordova // bundle, since Cordova bundles are built into the mobile application, // rather than being downloaded from a web server at runtime. This means // we can't distinguish between clients at runtime, so we have to use // code that works for all clients. "web.cordova", ]; export function isLegacyArch(arch: string): boolean { return legacyArches.some(la => matches(arch, la)); } export function mapWhereToArches(where: string) { const arches: string[] = []; // Shorthands for common arch prefixes: // "server" => os.* // "client" => web.* // "legacy" => web.browser.legacy, web.cordova if (where === "server") { arches.push("os"); } else if (where === "client") { arches.push("web"); } else if (where === "modern") { arches.push("web.browser"); } else if (where === "legacy") { arches.push(...legacyArches); } else { arches.push(where); } return arches; } // Like `supports`, but instead taken an array of possible // architectures as its second argument. Returns the most specific // match, or null if none match. Throws an error if `programs` // contains exact duplicates. export function mostSpecificMatch(host: string, programs: string[]): string | null { let best: string | null = null; const seen: Record = {}; programs.forEach((program: string) => { if (seen[program]) { throw new Error(`Duplicate architecture: ${program}`); } seen[program] = true; if (matches(host, program) && (!best || program.length > best.length)) { best = program; } }); return best; } // `programs` is a set of architectures (as an array of string, which // may contain duplicates). Determine if there exists any architecture // that is compatible with all of the architectures in the set. If so, // returns the least specific such architecture. Otherwise (the // architectures are disjoin) raise an exception. // // For example, for 'os' and 'os.osx', return 'os.osx'. For 'os' and // 'os.linux.x86_64', return 'os.linux.x86_64'. For 'os' and 'browser', throw an // exception. export function leastSpecificDescription(programs: string[]): string { if (programs.length === 0) { return ''; } // Find the longest string const longest = String(max(programs, (p: string) => p.length)); // If everything else in the list is compatible with the longest, // then it must be the most specific, and if everything is // compatible with the most specific then it must be the least // specific compatible description. programs.forEach((program: string) => { if (!matches(longest, program)) { throw new Error(`Incompatible architectures: '${program}' and '${longest}'`); } }); return longest; } export function withoutSpecificOs(arch: string): string { if (arch.substr(0, 3) === 'os.') { return 'os'; } return arch; }