Skip to content

Commit 085add5

Browse files
fix: ensure copied project files are writable on read-only filesystems
When the vscode-wpilib extension is installed on a read-only filesystem (e.g. the Nix store on NixOS), ncp preserves the source's read-only file permissions when copying template files to create a new project. This causes project creation to fail because subsequent steps cannot write to the copied files (e.g. replacing placeholders in build.gradle). Add a setWritableRecursive helper that ensures all copied files have the owner-write permission bit set, and call it after each ncp copy in ncpAsync. Fixes project creation on NixOS / systems using Nix package manager.
1 parent ee754c0 commit 085add5

2 files changed

Lines changed: 40 additions & 11 deletions

File tree

vscode-wpilib/src/shared/permissions.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
'use strict';
22

33
import * as fs from 'fs';
4+
import * as path from 'path';
5+
6+
/**
7+
* Recursively ensures all files and directories under `dir` are
8+
* owner-writable. This is needed when copying from read-only
9+
* filesystems (e.g. the Nix store) where copied files preserve
10+
* the source's read-only permissions.
11+
*/
12+
export async function setWritableRecursive(dir: string): Promise<void> {
13+
if (process.platform === 'win32') {
14+
return;
15+
}
16+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
17+
for (const entry of entries) {
18+
const fullPath = path.join(dir, entry.name);
19+
if (entry.isDirectory()) {
20+
await setWritableRecursive(fullPath);
21+
}
22+
const stat = await fs.promises.stat(fullPath);
23+
await fs.promises.chmod(fullPath, stat.mode | fs.constants.S_IWUSR);
24+
}
25+
}
426

527
export function setExecutePermissions(file: string): Promise<void> {
628
if (process.platform === 'win32') {

vscode-wpilib/src/utilities.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as util from 'util';
77
import * as vscode from 'vscode';
88
import { IExecuteAPI, IPreferences } from 'vscode-wpilibapi';
99
import { localize as i18n } from './locale';
10-
import { setExecutePermissions } from './shared/permissions';
10+
import { setExecutePermissions, setWritableRecursive } from './shared/permissions';
1111

1212
// General utilites usable by multiple classes
1313

@@ -69,18 +69,25 @@ export const deleteFileAsync = util.promisify(fs.unlink);
6969

7070
export const mkdirpAsync = mkdirp;
7171

72-
export function ncpAsync(source: string, dest: string, options: ncp.Options = {}): Promise<void> {
73-
return mkdirpAsync(dest).then(() => {
74-
return new Promise<void>((resolve, reject) => {
75-
ncp.ncp(source, dest, options, (err) => {
76-
if (err) {
77-
reject(err);
78-
} else {
79-
resolve();
80-
}
81-
});
72+
export async function ncpAsync(
73+
source: string,
74+
dest: string,
75+
options: ncp.Options = {}
76+
): Promise<void> {
77+
await mkdirpAsync(dest);
78+
await new Promise<void>((resolve, reject) => {
79+
ncp.ncp(source, dest, options, (err) => {
80+
if (err) {
81+
reject(err);
82+
} else {
83+
resolve();
84+
}
8285
});
8386
});
87+
// Ensure copied files are writable. This is necessary when the source
88+
// resides on a read-only filesystem (e.g. the Nix store on NixOS),
89+
// because ncp preserves the source's file permissions.
90+
await setWritableRecursive(dest);
8491
}
8592

8693
export const readdirAsync = util.promisify(fs.readdir);

0 commit comments

Comments
 (0)