Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support to install different architectures #7214

Merged
merged 43 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8e7a9a6
feat: compute supported archs from manifest
nachoaldamav Oct 17, 2023
2feb12c
feat: support different archs when installing
nachoaldamav Oct 17, 2023
13ad5eb
feat: add changeset
nachoaldamav Oct 17, 2023
63672ff
fix: fix type errors and tests
nachoaldamav Oct 17, 2023
6bbf7e8
fix: recreate changeset
nachoaldamav Oct 17, 2023
1704b6b
fix: type errors
nachoaldamav Oct 17, 2023
fb12599
fix: type errors
nachoaldamav Oct 17, 2023
a11b928
fix: type errors
nachoaldamav Oct 18, 2023
f2435bf
fix: type errors
nachoaldamav Oct 18, 2023
b71e17f
fix: make `supportedArchitectures` optional
nachoaldamav Oct 18, 2023
ff84b6f
style: format code
nachoaldamav Oct 18, 2023
1c27f8b
refactor: rename `dedupeCurrent` function
nachoaldamav Oct 18, 2023
e9d97a5
perf: improve `dedupeCurrent` function
nachoaldamav Oct 18, 2023
bd758d0
style: format code
nachoaldamav Oct 18, 2023
745bc80
test: add tests for `checkPlatform`
nachoaldamav Oct 18, 2023
9f31bfb
test: updating optional deps
zkochan Oct 21, 2023
6003697
Merge remote-tracking branch 'origin/main' into support-different-archs
zkochan Oct 21, 2023
f1dd64c
fix: relink optional deps
zkochan Oct 21, 2023
d4a2c2d
fix: remove console log
zkochan Oct 21, 2023
ce0ae43
test: fix
zkochan Oct 21, 2023
8400ab2
test: fix
zkochan Oct 21, 2023
e93bd5d
test: fix
zkochan Oct 21, 2023
00c8c78
test: refactor
zkochan Oct 22, 2023
04de31c
refactor: less changes
zkochan Oct 22, 2023
70a5f06
refactor: less changes
zkochan Oct 22, 2023
15693fe
test: not needed optional deps are removed
zkochan Oct 22, 2023
a49f697
fix: remove unused optional deps
zkochan Oct 22, 2023
16b36f5
Revert "fix: remove unused optional deps"
zkochan Oct 23, 2023
3f28907
fix: removing not needed artifacts
zkochan Oct 23, 2023
cca836b
fix: prune hoisted node_modules
zkochan Oct 23, 2023
b5274fc
fix: prune
zkochan Oct 23, 2023
b6a4f84
Merge remote-tracking branch 'origin/main' into support-different-archs
zkochan Oct 23, 2023
93b894d
fix: read supportedArchitectures from the right place
zkochan Oct 24, 2023
25b7204
refactor: supported archs
zkochan Oct 24, 2023
4771553
test: fix
zkochan Oct 24, 2023
2490390
test: fix
zkochan Oct 24, 2023
28ec8a3
test: fix
zkochan Oct 24, 2023
24011ef
test: fix
zkochan Oct 24, 2023
2dbd1e2
Merge remote-tracking branch 'origin/main' into support-different-archs
zkochan Oct 24, 2023
d0c7623
refactor: revert refactor
zkochan Oct 24, 2023
ef49d5d
Merge remote-tracking branch 'origin/main' into support-different-archs
zkochan Oct 24, 2023
6b8dc98
refactor: rootProjectManifestDir
zkochan Oct 24, 2023
6b20099
docs: update changeset
zkochan Oct 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 55 additions & 0 deletions .changeset/angry-maps-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
"@pnpm/plugin-commands-publishing": minor
"@pnpm/plugin-commands-script-runners": minor
"@pnpm/filter-workspace-packages": minor
"@pnpm/plugin-commands-licenses": minor
"@pnpm/plugin-commands-patching": minor
"@pnpm/resolve-dependencies": minor
"@pnpm/package-is-installable": minor
"@pnpm/package-requester": minor
"@pnpm/plugin-commands-rebuild": minor
"@pnpm/store-controller-types": minor
"@pnpm/plugin-commands-store": minor
"@pnpm/license-scanner": minor
"@pnpm/filter-lockfile": minor
"@pnpm/workspace.find-packages": minor
"@pnpm/headless": minor
"@pnpm/deps.graph-builder": minor
"@pnpm/core": minor
"@pnpm/types": minor
"@pnpm/cli-utils": minor
"@pnpm/config": minor
"pnpm": minor
---

Support for multiple architectures when installing dependencies [#5965](https://github.com/pnpm/pnpm/issues/5965).

You can now specify architectures for which you'd like to install optional dependencies, even if they don't match the architecture of the system running the install. Use the `supportedArchitectures` field in `package.json` to define your preferences.

For example, the following configuration tells pnpm to install optional dependencies for Windows x64:

```json
{
"pnpm": {
"supportedArchitectures": {
"os": ["win32"],
"cpu": ["x64"]
}
}
}
```

Whereas this configuration will have pnpm install optional dependencies for Windows, macOS, and the architecture of the system currently running the install. It includes artifacts for both x64 and arm64 CPUs:

```json
{
"pnpm": {
"supportedArchitectures": {
"os": ["win32", "darwin", "current"],
"cpu": ["x64", "arm64"]
}
}
}
```

Additionally, `supportedArchitectures` also supports specifying the `libc` of the system.
5 changes: 5 additions & 0 deletions .changeset/dirty-crews-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pnpm/build-modules": patch
---

`filesIndexFile` may be undefined.
7 changes: 7 additions & 0 deletions cli/cli-utils/src/packageIsInstallable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { packageManager } from '@pnpm/cli-meta'
import { logger } from '@pnpm/logger'
import { checkPackage, UnsupportedEngineError, type WantedEngine } from '@pnpm/package-is-installable'
import { type SupportedArchitectures } from '@pnpm/types'

export function packageIsInstallable (
pkgPath: string,
Expand All @@ -13,6 +14,7 @@ export function packageIsInstallable (
opts: {
engineStrict?: boolean
nodeVersion?: string
supportedArchitectures?: SupportedArchitectures
}
) {
const pnpmVersion = packageManager.name === 'pnpm'
Expand All @@ -21,6 +23,11 @@ export function packageIsInstallable (
const err = checkPackage(pkgPath, pkg, {
nodeVersion: opts.nodeVersion,
pnpmVersion,
supportedArchitectures: opts.supportedArchitectures ?? {
os: ['current'],
cpu: ['current'],
libc: ['current'],
},
})
if (err === null) return
if (
Expand Down
48 changes: 25 additions & 23 deletions cli/cli-utils/src/readProjectManifest.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
import * as utils from '@pnpm/read-project-manifest'
import { type ProjectManifest } from '@pnpm/types'
import { type SupportedArchitectures, type ProjectManifest } from '@pnpm/types'
import { packageIsInstallable } from './packageIsInstallable'

export interface ReadProjectManifestOpts {
engineStrict?: boolean
nodeVersion?: string
supportedArchitectures?: SupportedArchitectures
}

interface BaseReadProjectManifestResult {
fileName: string
writeProjectManifest: (manifest: ProjectManifest, force?: boolean) => Promise<void>
}

export interface ReadProjectManifestResult extends BaseReadProjectManifestResult {
manifest: ProjectManifest
}

export async function readProjectManifest (
projectDir: string,
opts: {
engineStrict?: boolean
nodeVersion?: string
}
): Promise<{
fileName: string
manifest: ProjectManifest
writeProjectManifest: (manifest: ProjectManifest, force?: boolean) => Promise<void>
}> {
opts: ReadProjectManifestOpts = {}
): Promise<ReadProjectManifestResult> {
const { fileName, manifest, writeProjectManifest } = await utils.readProjectManifest(projectDir)
packageIsInstallable(projectDir, manifest as any, opts) // eslint-disable-line @typescript-eslint/no-explicit-any
return { fileName, manifest, writeProjectManifest }
}

export async function readProjectManifestOnly (
projectDir: string,
opts: {
engineStrict?: boolean
nodeVersion?: string
} = {}
opts: ReadProjectManifestOpts = {}
): Promise<ProjectManifest> {
const manifest = await utils.readProjectManifestOnly(projectDir)
packageIsInstallable(projectDir, manifest as any, opts) // eslint-disable-line @typescript-eslint/no-explicit-any
return manifest
}

export interface TryReadProjectManifestResult extends BaseReadProjectManifestResult {
manifest: ProjectManifest | null
}

export async function tryReadProjectManifest (
projectDir: string,
opts: {
engineStrict?: boolean
nodeVersion?: string
}
): Promise<{
fileName: string
manifest: ProjectManifest | null
writeProjectManifest: (manifest: ProjectManifest, force?: boolean) => Promise<void>
}> {
opts: ReadProjectManifestOpts
): Promise<TryReadProjectManifestResult> {
const { fileName, manifest, writeProjectManifest } = await utils.tryReadProjectManifest(projectDir)
if (manifest == null) return { fileName, manifest, writeProjectManifest }
packageIsInstallable(projectDir, manifest as any, opts) // eslint-disable-line @typescript-eslint/no-explicit-any
Expand Down
10 changes: 10 additions & 0 deletions config/config/src/getOptionsFromRootManifest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'path'
import { PnpmError } from '@pnpm/error'
import {
type SupportedArchitectures,
type AllowedDeprecatedVersions,
type PackageExtension,
type PeerDependencyRules,
Expand All @@ -18,6 +19,7 @@ export interface OptionsFromRootManifest {
packageExtensions?: Record<string, PackageExtension>
patchedDependencies?: Record<string, string>
peerDependencyRules?: PeerDependencyRules
supportedArchitectures?: SupportedArchitectures
}

export function getOptionsFromRootManifest (manifestDir: string, manifest: ProjectManifest): OptionsFromRootManifest {
Expand Down Expand Up @@ -46,6 +48,13 @@ export function getOptionsFromRootManifest (manifestDir: string, manifest: Proje
patchedDependencies[dep] = path.join(manifestDir, patchFile)
}
}

const supportedArchitectures = {
os: manifest.pnpm?.supportedArchitectures?.os ?? ['current'],
cpu: manifest.pnpm?.supportedArchitectures?.cpu ?? ['current'],
libc: manifest.pnpm?.supportedArchitectures?.libc ?? ['current'],
}

const settings: OptionsFromRootManifest = {
allowedDeprecatedVersions,
allowNonAppliedPatches,
Expand All @@ -54,6 +63,7 @@ export function getOptionsFromRootManifest (manifestDir: string, manifest: Proje
packageExtensions,
peerDependencyRules,
patchedDependencies,
supportedArchitectures,
}
if (onlyBuiltDependencies) {
settings.onlyBuiltDependencies = onlyBuiltDependencies
Expand Down
48 changes: 33 additions & 15 deletions config/package-is-installable/src/checkPlatform.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PnpmError } from '@pnpm/error'
import { type SupportedArchitectures } from '@pnpm/types'
import { familySync as getLibcFamilySync } from 'detect-libc'

const currentLibc = getLibcFamilySync() ?? 'unknown'
Expand All @@ -16,19 +17,26 @@ export class UnsupportedPlatformError extends PnpmError {

export function checkPlatform (
packageId: string,
wantedPlatform: WantedPlatform
wantedPlatform: WantedPlatform,
supportedArchitectures?: SupportedArchitectures
) {
const current = {
os: dedupeCurrent(process.platform, supportedArchitectures?.os ?? ['current']),
cpu: dedupeCurrent(process.arch, supportedArchitectures?.cpu ?? ['current']),
libc: dedupeCurrent(currentLibc, supportedArchitectures?.libc ?? ['current']),
}

const { platform, arch } = process
let osOk = true; let cpuOk = true; let libcOk = true

if (wantedPlatform.os) {
osOk = checkList(platform, wantedPlatform.os)
osOk = checkList(current.os, wantedPlatform.os)
}
if (wantedPlatform.cpu) {
cpuOk = checkList(arch, wantedPlatform.cpu)
cpuOk = checkList(current.cpu, wantedPlatform.cpu)
}
if (wantedPlatform.libc && currentLibc !== 'unknown') {
libcOk = checkList(currentLibc, wantedPlatform.libc)
libcOk = checkList(current.libc, wantedPlatform.libc)
}

if (!osOk || !cpuOk || !libcOk) {
Expand All @@ -45,25 +53,35 @@ export interface Platform {

export type WantedPlatform = Partial<Platform>

function checkList (value: string, list: string | string[]) {
let tmp; let match = false; let blc = 0
function checkList (value: string | string[], list: string | string[]): boolean {
let tmp
let match = false
let blc = 0

if (typeof list === 'string') {
list = [list]
}
if (list.length === 1 && list[0] === 'any') {
return true
}
for (let i = 0; i < list.length; ++i) {
tmp = list[i]
if (tmp[0] === '!') {
tmp = tmp.slice(1)
if (tmp === value) {
return false
const values = Array.isArray(value) ? value : [value]
for (const value of values) {
for (let i = 0; i < list.length; ++i) {
tmp = list[i]
if (tmp[0] === '!') {
tmp = tmp.slice(1)
if (tmp === value) {
return false
}
++blc
} else {
match = match || tmp === value
}
++blc
} else {
match = match || tmp === value
}
}
return match || blc === list.length
}

function dedupeCurrent (current: string, supported: string[]) {
return supported.map((supported) => supported === 'current' ? current : supported)
}
7 changes: 5 additions & 2 deletions config/package-is-installable/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
import { checkEngine, UnsupportedEngineError, type WantedEngine } from './checkEngine'
import { checkPlatform, UnsupportedPlatformError } from './checkPlatform'
import { getSystemNodeVersion } from './getSystemNodeVersion'
import { type SupportedArchitectures } from '@pnpm/types'

export type { Engine } from './checkEngine'
export type { Platform, WantedPlatform } from './checkPlatform'
Expand All @@ -31,6 +32,7 @@ export function packageIsInstallable (
optional: boolean
pnpmVersion?: string
lockfileDir: string
supportedArchitectures?: SupportedArchitectures
}
): boolean | null {
const warn = checkPackage(pkgId, pkg, options)
Expand Down Expand Up @@ -73,18 +75,19 @@ export function checkPackage (
options: {
nodeVersion?: string
pnpmVersion?: string
supportedArchitectures?: SupportedArchitectures
}
): null | UnsupportedEngineError | UnsupportedPlatformError {
return checkPlatform(pkgId, {
cpu: manifest.cpu ?? ['any'],
os: manifest.os ?? ['any'],
libc: manifest.libc ?? ['any'],
}) ?? (
}, options.supportedArchitectures) ?? (
(manifest.engines == null)
? null
: checkEngine(pkgId, manifest.engines, {
node: options.nodeVersion ?? getSystemNodeVersion(),
pnpm: options.pnpmVersion,
})
)
}
}
42 changes: 42 additions & 0 deletions config/package-is-installable/test/checkPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,45 @@ test('os wrong (negation)', () => {
test('nothing wrong (negation)', () => {
expect(checkPlatform(packageId, { cpu: '!enten-cpu', os: '!enten-os', libc: '!enten-libc' })).toBe(null)
})

test('override OS', () => {
expect(checkPlatform(packageId, { cpu: 'any', os: 'win32', libc: 'any' }, {
os: ['win32'],
cpu: ['current'],
libc: ['current'],
})).toBe(null)
})

test('accept another CPU', () => {
expect(checkPlatform(packageId, { cpu: 'x64', os: 'any', libc: 'any' }, {
os: ['current'],
cpu: ['current', 'x64'],
libc: ['current'],
})).toBe(null)
})

test('fail when CPU is different', () => {
const err = checkPlatform(packageId, { cpu: 'x64', os: 'any', libc: 'any' }, {
os: ['current'],
cpu: ['arm64'],
libc: ['current'],
})
expect(err).toBeTruthy()
expect(err?.code).toBe('ERR_PNPM_UNSUPPORTED_PLATFORM')
})

test('override libc', () => {
expect(checkPlatform(packageId, { cpu: 'any', os: 'any', libc: 'glibc' }, {
os: ['current'],
cpu: ['current'],
libc: ['glibc'],
})).toBe(null)
})

test('accept another libc', () => {
expect(checkPlatform(packageId, { cpu: 'any', os: 'any', libc: 'glibc' }, {
os: ['current'],
cpu: ['current'],
libc: ['current', 'glibc'],
})).toBe(null)
})