Skip to content
Merged
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
89 changes: 82 additions & 7 deletions Herebyfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const __filename = url.fileURLToPath(new URL(import.meta.url));
const __dirname = path.dirname(__filename);

const isCI = !!process.env.CI || !!process.env.TF_BUILD;
const stableThreeComponentVersionPattern = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;

const $pipe = _$({ verbose: "short" });
const $ = _$({ verbose: "short", stdio: "inherit" });
Expand Down Expand Up @@ -68,6 +69,7 @@ const { values: rawOptions } = parseArgs({

setPrerelease: { type: "string" },
forRelease: { type: "boolean" },
respectGoEnv: { type: "boolean" },

race: { type: "boolean", default: parseEnvBoolean("RACE") },
noembed: { type: "boolean", default: parseEnvBoolean("NOEMBED") },
Expand Down Expand Up @@ -98,6 +100,12 @@ const publishAsTypescript = nativePreviewReleaseProfile === "typescript";
if (options.forRelease && !options.setPrerelease && (!nativePreviewReleaseVersion || produceAnyVsix)) {
throw new Error("forRelease requires setPrerelease unless nativePreviewReleaseVersion is hardcoded and VSIX production is disabled");
}
if (options.respectGoEnv && options.forRelease) {
throw new Error("respectGoEnv cannot be combined with forRelease");
}
if (options.respectGoEnv && options.setPrerelease) {
throw new Error("respectGoEnv requires the version declared in the source");
}
if (usePublishedPlatformPackagesForVsix && !publishAsTypescript) {
throw new Error("usePublishedPlatformPackagesForVsix requires nativePreviewReleaseProfile to be 'typescript'");
}
Expand Down Expand Up @@ -200,7 +208,7 @@ function getReleaseBuildFlags(versionOverride) {
function buildTsc(opts) {
opts ||= {};
const out = opts.out ?? path.resolve("./built/local/tsc" + (process.platform === "win32" ? ".exe" : ""));
const env = { ...goBuildEnv, ...opts.env };
const env = { ...(options.respectGoEnv ? {} : goBuildEnv), ...opts.env };
return $({ cancelSignal: opts.abortSignal, env, cwd: "./tsc" })`go build ${goBuildFlags} ${opts.extraFlags ?? []} ${goBuildTags("noembed")} -o ${out} ./cmd/tsc`;
}

Expand Down Expand Up @@ -1202,7 +1210,7 @@ function getPublishTag() {
}
const match = version.match(/-(dev|beta|rc)(?:[.-]|$)/);
if (match?.[1]) return match[1] === "dev" ? "next" : match[1];
if (version === nativePreviewReleaseVersion) return "latest";
if (version === nativePreviewReleaseVersion && stableThreeComponentVersionPattern.test(version)) return "latest";
throw new Error(`Refusing to publish 'typescript' with the latest tag from non-release version ${version}.`);
}
return "latest";
Expand All @@ -1212,6 +1220,7 @@ const extensionDir = path.resolve("./packages/vscode-typescript");
const nightlyExtensionDir = path.resolve("./packages/vscode-typescript-nightly");
const builtNpm = path.resolve("./built/npm");
const builtVsix = path.resolve("./built/vsix");
const typeScriptReleaseInfoPath = path.resolve("./built/typescript-release-info.json");
const builtPublishedPlatformPackages = path.resolve("./built/published-platform-packages");
const builtSignTmp = path.resolve("./built/sign-tmp");
const publishedTypeScriptAliasPackageName = "@typescript/bundled-typescript";
Expand Down Expand Up @@ -1772,10 +1781,22 @@ function stripConditionsFromValue(value) {

export const buildNativePreviewPackages = task({
name: "typescript:build",
hiddenFromTaskList: true,
description: "Builds TypeScript npm packages for the current platform. Pass --respectGoEnv to preserve caller-provided Go build settings.",
run: runBuildNativePreviewPackages,
});

export const writeTypeScriptReleaseInfo = task({
name: "typescript:release-info",
hiddenFromTaskList: true,
run: async () => {
await fs.promises.mkdir(path.dirname(typeScriptReleaseInfoPath), { recursive: true });
await fs.promises.writeFile(
typeScriptReleaseInfoPath,
JSON.stringify({ version: getVersion(), npmTag: getPublishTag() }, undefined, 4) + "\n",
);
},
});

async function runBuildNativePreviewPackages() {
if (usePublishedPlatformPackagesForVsix) {
checkPublishedPlatformPackagesForVsix();
Expand Down Expand Up @@ -1819,8 +1840,10 @@ async function runBuildNativePreviewPackages() {
}
stripSourceConditions(inputPackageJson);

const { stdout: gitHead } = await $pipe`git rev-parse HEAD`;
inputPackageJson.gitHead = gitHead;
if (fs.existsSync(".git")) {
const { stdout: gitHead } = await $pipe`git rev-parse HEAD`;
inputPackageJson.gitHead = gitHead;
}
inputPackageJson.publishConfig = {
access: "public",
tag: getPublishTag(),
Expand Down Expand Up @@ -1877,7 +1900,9 @@ async function runBuildNativePreviewPackages() {
throw new Error(`Found external imports in .d.ts files:\n${importErrors.map(e => " " + e).join("\n")}`);
}

const extraFlags = getReleaseBuildFlags(options.setPrerelease || nativePreviewReleaseVersion ? getVersion() : undefined);
const extraFlags = options.respectGoEnv
? []
: getReleaseBuildFlags(options.setPrerelease || nativePreviewReleaseVersion ? getVersion() : undefined);

const platformBuilders = platforms.map(({ npmDir, npmPackageName, nodeOs, nodeArch, goos, goarch }) => async () => {
const packageJson = {
Expand Down Expand Up @@ -1913,7 +1938,11 @@ async function runBuildNativePreviewPackages() {
const exeName = nativePreviewExeName(nodeOs);
await buildTsc({
out: publishAsTypescript ? path.join(out, exeName) : out,
env: { GOOS: goos, GOARCH: goarch, GOARM: "6", CGO_ENABLED: "0" },
env: {
GOOS: goos,
GOARCH: goarch,
...(options.respectGoEnv ? {} : { GOARM: "6", CGO_ENABLED: "0" }),
},
extraFlags,
});
});
Expand All @@ -1932,6 +1961,52 @@ async function runBuildNativePreviewPackages() {
}
}

/**
* @param {ReturnType<typeof getPlatforms>} platforms
*/
async function testNativePreviewPackage(platforms) {
const hostPlatform = platforms.find(({ nodeOs, nodeArch }) => nodeOs === process.platform && nodeArch === process.arch);
assert(hostPlatform, `No package was built for the host platform ${process.platform}-${process.arch}`);

const testRoot = path.resolve("built/package-test");
const nodeModules = path.join(testRoot, "node_modules");
const mainPackageDir = path.join(nodeModules, ...mainNativePreviewPackage.npmPackageName.split("/"));
const platformPackageDir = path.join(nodeModules, ...hostPlatform.npmPackageName.split("/"));
const sourceFile = path.join(testRoot, "index.ts");

await rimraf(testRoot);
try {
await cpRecursive(mainNativePreviewPackage.npmDir, mainPackageDir);
await cpRecursive(hostPlatform.npmDir, platformPackageDir);
await fs.promises.writeFile(sourceFile, 'export const value: string = "value";\n');

const binName = publishAsTypescript ? "tsc" : "tsgo";
const binPath = path.join(mainPackageDir, "bin", binName);
const { stdout: versionOutput } = await $pipe`${process.execPath} ${binPath} --version`;
assert.equal(versionOutput.trim(), `Version ${getVersion()}`);

const { stdout: listFilesOutput } = await $pipe`${process.execPath} ${binPath} --noEmit --listFiles ${sourceFile}`;
assert(!listFilesOutput.includes("bundled:///"), "Packaged compiler listed an embedded library path");

const expectedLib = path.resolve(platformPackageDir, "lib", "lib.es5.d.ts");
const listedFiles = listFilesOutput
.split(/\r?\n/)
.filter(Boolean)
.map(file => path.resolve(file));
assert(listedFiles.includes(expectedLib), `Expected packaged compiler to list ${expectedLib}`);
}
finally {
await rimraf(testRoot);
}
}

export const testNativePreviewPackageTask = task({
name: "typescript:test-package",
description: "Tests the TypeScript npm package for the current platform.",
dependencies: options.forRelease ? undefined : [buildNativePreviewPackages],
run: () => testNativePreviewPackage(getPlatforms()),
});

export const signNativePreviewPackages = task({
name: "typescript:sign",
hiddenFromTaskList: true,
Expand Down
7 changes: 1 addition & 6 deletions tools/pipelines/steps/setup-node-npm-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,10 @@ steps:

- bash: |
cat > .npmrc << 'EOF'
registry=https://pkgs.dev.azure.com/devdiv/devdiv/_packaging/devdiv_PublicPackages/npm/registry/
registry=https://packagefeedproxy.microsoft.io/npm/
EOF
displayName: 'Set up .npmrc'
- task: npmAuthenticate@0
inputs:
workingFile: .npmrc
displayName: 'Authenticate npm'

- pwsh: |
npm install -g (Get-Content package.json | ConvertFrom-Json).packageManager
npm --version
Expand Down
23 changes: 23 additions & 0 deletions tools/pipelines/steps/setup-vsce.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
parameters:
- name: condition
type: string
default: succeeded()

steps:
- task: NodeTool@0
condition: ${{ parameters.condition }}
inputs:
versionSpec: 24.x
displayName: 'Install Node'

- bash: |
cat > .npmrc << 'EOF'
registry=https://packagefeedproxy.microsoft.io/npm/
EOF
npm init -y
condition: ${{ parameters.condition }}
displayName: 'Set up npm'

- bash: npm install --no-save @vscode/vsce@3.9.2
condition: ${{ parameters.condition }}
displayName: 'Install vsce'
85 changes: 57 additions & 28 deletions tools/pipelines/typescript-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,12 @@ schedules:
include:
- main

name: $(date:yyyyMMdd)$(rev:.r)
name: $(Date:yyyyMMdd).$(runRevision)
appendCommitMessageToRunName: false

parameters:
- name: signType
displayName: Sign type
type: string
default: auto
values:
- auto
- real
- test

variables:
- name: runRevision
value: $[counter(format('{0:yyyyMMdd}', pipeline.startTime), 1)]
# For MicroBuild telemetry
- name: TeamName
value: TypeScript
Expand All @@ -46,8 +38,6 @@ extends:
fetchTags: false
retryCount: 3
sourceAnalysisPool: VSEngSS-MicroBuild2022-1ES
sourceRepositoriesToScan:
include:
featureFlags:
golang:
internalModuleProxy:
Expand Down Expand Up @@ -104,18 +94,11 @@ extends:
- task: MicroBuildSigningPlugin@4
displayName: '🔩 Install Signing Plugin'
inputs:
${{ if eq(parameters.signType, 'auto') }}:
${{ if eq(variables['Build.Reason'], 'Schedule') }}:
signType: real
${{ else }}:
signType: test
${{ else }}:
signType: ${{ parameters.signType }}
signType: real
# azureSubscription AKA ConnectedServiceName
azureSubscription: 'MicroBuild Signing Task (DevDiv)'
${{ if or(and(eq(parameters.signType, 'auto'), eq(variables['Build.Reason'], 'Schedule')), eq(parameters.signType, 'real')) }}:
# From "nonwindowspmeservicename" in https://dev.azure.com/devdiv/1ESPipelineTemplates/_git/MicroBuildTemplate?path=/azure-pipelines/Stages/Stage.yml
ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39
# From "nonwindowspmeservicename" in https://dev.azure.com/devdiv/1ESPipelineTemplates/_git/MicroBuildTemplate?path=/azure-pipelines/Stages/Stage.yml
ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39
# We do this ourselves.
zipSources: false
env:
Expand All @@ -127,24 +110,70 @@ extends:
fetchDepth: 1
fetchTags: false
- template: /tools/pipelines/steps/setup-node-npm-ci.yml@self

- bash: |
set -euo pipefail

if ! [[ "$INITIAL_BUILD_NUMBER" =~ ^[0-9]{8}\.[1-9][0-9]*$ ]]; then
echo "Unexpected initial build number: $INITIAL_BUILD_NUMBER" >&2
exit 1
fi

echo "##vso[task.setvariable variable=initialBuildNumber]$INITIAL_BUILD_NUMBER"

npx hereby typescript:release-info --forRelease --setPrerelease "dev.$INITIAL_BUILD_NUMBER"

releaseInfo="built/typescript-release-info.json"
version="$(jq -r '.version' "$releaseInfo")"
npmTag="$(jq -r '.npmTag' "$releaseInfo")"
if [ -z "$version" ] || [ "$version" = "null" ]; then
echo "Unable to determine the TypeScript package version." >&2
exit 1
fi

case "$npmTag" in
next)
if [[ "$version" != *"-dev.$INITIAL_BUILD_NUMBER" ]]; then
echo "Nightly package version $version does not contain build number $INITIAL_BUILD_NUMBER." >&2
exit 1
fi
finalBuildNumber="$version"
;;
latest | beta | rc)
finalBuildNumber="${version}_${INITIAL_BUILD_NUMBER}"
;;
*)
echo "Unexpected npm publish tag: $npmTag" >&2
exit 1
;;
esac

echo "##vso[build.updatebuildnumber]$finalBuildNumber"
displayName: 'Set versioned build name'
env:
INITIAL_BUILD_NUMBER: $(Build.BuildNumber)

- template: /tools/pipelines/steps/setup-go.yml@self

- bash: npx hereby typescript:build --forRelease
- bash: npx hereby typescript:build --forRelease --setPrerelease dev.$(initialBuildNumber)
displayName: 'Build packages'

- bash: npx hereby typescript:sign --forRelease
- bash: npx hereby typescript:test-package --forRelease --setPrerelease dev.$(initialBuildNumber)
displayName: 'Test packages'

- bash: npx hereby typescript:sign --forRelease --setPrerelease dev.$(initialBuildNumber)
displayName: 'Sign packages'
env:
# Needed for ESRP
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

- bash: npx hereby typescript:pack --forRelease
- bash: npx hereby typescript:pack --forRelease --setPrerelease dev.$(initialBuildNumber)
displayName: 'Pack packages'

- bash: npx hereby vscode-typescript:pack --forRelease
- bash: npx hereby vscode-typescript:pack --forRelease --setPrerelease dev.$(initialBuildNumber)
displayName: 'Pack extensions'

- bash: npx hereby vscode-typescript:sign --forRelease
- bash: npx hereby vscode-typescript:sign --forRelease --setPrerelease dev.$(initialBuildNumber)
displayName: 'Sign extensions'
env:
# Needed for ESRP
Expand Down
Loading