Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions profiler-cli/src/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
writeStartupError,
} from './session';
import {
describeProfileVersionError,
describeSessionDirFailure,
describeSocketListenError,
describeStaleSocketFailure,
Expand All @@ -58,12 +59,10 @@ function formatProfileLoadError(error: unknown): string {
error instanceof ProfileVersionError ||
(error instanceof Error && error.name === 'ProfileVersionError')
) {
const versionError = error as ProfileVersionError;
return (
`This profile is version ${versionError.profileVersion}, but this profiler-cli only ` +
`supports up to version ${versionError.supportedVersion} of the ${versionError.formatName} profile format.\n` +
`Update to the latest version with:\n` +
` npm install -g ${PACKAGE_NAME}@latest`
return describeProfileVersionError(
error as ProfileVersionError,
PACKAGE_NAME,
process.argv[1]
);
}
return error instanceof Error ? error.message : String(error);
Expand Down
56 changes: 56 additions & 0 deletions profiler-cli/src/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import type { ProfileVersionError } from 'firefox-profiler/profile-logic/errors';

/**
* Advice shown whenever the failure looks like a sandbox restriction on the
Expand Down Expand Up @@ -346,3 +347,58 @@ export function indentBlock(text: string, prefix: string = ' '): string {
.map((line) => `${prefix}${line}`)
.join('\n');
}

/**
* Where `./mach` keeps its downloaded tools, including the copies of samply
* and profiler-cli that Firefox's bootstrap installs.
*/
export function getMozbuildStatePath(): string {
return (
process.env.MOZBUILD_STATE_PATH || path.join(os.homedir(), '.mozbuild')
);
}

function realpathOrSelf(filePath: string): string {
try {
return fs.realpathSync(filePath);
} catch {
return path.resolve(filePath);
}
}

/**
* Whether the profiler-cli script at `scriptPath` is the copy that
* `./mach bootstrap` installed from Firefox's in-tree toolchain.
*/
export function isInstalledByMachBootstrap(scriptPath: string): boolean {
const toolDir = realpathOrSelf(
path.join(getMozbuildStatePath(), 'profiler-cli')
);
const relative = path.relative(toolDir, realpathOrSelf(scriptPath));
return (
relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative)
);
}

/**
* Explain that a profile is too new for this build and how to update it.
* The bootstrapped copy can only be updated through "./mach bootstrap".
*/
export function describeProfileVersionError(
error: ProfileVersionError,
packageName: string,
scriptPath: string
): string {
const versionLine = `This profile is version ${error.profileVersion}, but this profiler-cli only supports up to version ${error.supportedVersion} of the ${error.formatName} profile format.`;
if (isInstalledByMachBootstrap(scriptPath)) {
return [
versionLine,
'This profiler-cli was installed by "./mach bootstrap". Update your Firefox checkout to the latest main branch and run "./mach bootstrap" again.',
].join('\n');
}
return [
versionLine,
'Update to the latest version with:',
` npm install -g ${packageName}@latest`,
].join('\n');
}
12 changes: 1 addition & 11 deletions profiler-cli/src/samply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

import * as child_process from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { getMozbuildStatePath } from './diagnostics';

export type SamplyServer = {
binaryPath: string;
Expand All @@ -37,16 +37,6 @@ function isExecutableFile(filePath: string): boolean {
}
}

/**
* Where `./mach` keeps its downloaded tools. Firefox's bootstrap installs
* samply there, so it is a good fallback when samply is not on PATH.
*/
function getMozbuildStatePath(): string {
return (
process.env.MOZBUILD_STATE_PATH || path.join(os.homedir(), '.mozbuild')
);
}

/**
* Resolve the samply binary: PROFILER_CLI_SAMPLY_PATH if set, then PATH, then
* the mozbuild state directory. Returns null when none of them has it. An
Expand Down
50 changes: 50 additions & 0 deletions profiler-cli/src/test/unit/diagnostics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as os from 'os';
import * as path from 'path';
import {
assertSocketPathUsable,
describeProfileVersionError,
describeSessionDirFailure,
describeSocketConnectError,
describeSocketListenError,
Expand All @@ -20,6 +21,8 @@ import {
getErrnoCode,
indentBlock,
} from '../../diagnostics';
import { ProfileVersionError } from 'firefox-profiler/profile-logic/errors';
import { snapshotEnv } from '../integration/fake-samply';

function errnoError(code: string, message: string): NodeJS.ErrnoException {
const error: NodeJS.ErrnoException = new Error(message);
Expand Down Expand Up @@ -229,4 +232,51 @@ describe('profiler-cli diagnostics', function () {
expect(indentBlock('a\nb')).toBe(' a\n b');
});
});

describe('describeProfileVersionError', function () {
const PACKAGE = '@firefox-devtools/profiler-cli';
const error = new ProfileVersionError('processed', 78, 77);
let restoreEnv: () => void;

beforeEach(function () {
restoreEnv = snapshotEnv(['MOZBUILD_STATE_PATH']);
process.env.MOZBUILD_STATE_PATH = path.join(tmpDir, 'mozbuild');
});

afterEach(function () {
restoreEnv();
});

it('points npm installs at npm', function () {
const message = describeProfileVersionError(
error,
PACKAGE,
path.join(tmpDir, 'npm', 'profiler-cli', 'dist', 'profiler-cli.js')
);
expect(message).toContain('version 78');
expect(message).toContain('up to version 77 of the processed');
expect(message).toContain(`npm install -g ${PACKAGE}@latest`);
expect(message).not.toContain('mach bootstrap');
});

it('points bootstrapped installs at mach bootstrap', function () {
const message = describeProfileVersionError(
error,
PACKAGE,
path.join(tmpDir, 'mozbuild', 'profiler-cli', 'dist', 'profiler-cli.js')
);
expect(message).toContain('version 78');
expect(message).toContain('./mach bootstrap');
expect(message).not.toContain('npm install -g');
});

it('does not treat a sibling directory of the bootstrapped copy as bootstrapped', function () {
const message = describeProfileVersionError(
error,
PACKAGE,
path.join(tmpDir, 'mozbuild', 'profiler-cli-old', 'profiler-cli.js')
);
expect(message).not.toContain('mach bootstrap');
});
});
});
Loading