Skip to content

put Linux build files inside a folder before zipping #870

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

Merged
merged 2 commits into from
Mar 1, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ export class ArduinoFrontendContribution
}

this.updaterService.init(
this.arduinoPreferences.get('arduino.ide.updateChannel')
this.arduinoPreferences.get('arduino.ide.updateChannel'),
this.arduinoPreferences.get('arduino.ide.updateBaseUrl')
);
this.updater.checkForUpdates(true).then(async (updateInfo) => {
if (!updateInfo) return;
Expand Down
9 changes: 9 additions & 0 deletions arduino-ide-extension/src/browser/arduino-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
"Release channel to get updated from. 'stable' is the stable release, 'nightly' is the latest development build."
),
},
'arduino.ide.updateBaseUrl': {
type: 'string',
default: 'https://downloads.arduino.cc/arduino-ide',
description: nls.localize(
'arduino/preferences/ide.updateBaseUrl',
`The base URL where to download updates from. Defaults to 'https://downloads.arduino.cc/arduino-ide'`
),
},
'arduino.board.certificates': {
type: 'string',
description: nls.localize(
Expand Down Expand Up @@ -178,6 +186,7 @@ export interface ArduinoConfiguration {
'arduino.window.autoScale': boolean;
'arduino.window.zoomLevel': number;
'arduino.ide.updateChannel': UpdateChannel;
'arduino.ide.updateBaseUrl': string;
'arduino.board.certificates': string;
'arduino.sketchbook.showAllFiles': boolean;
'arduino.cloud.enabled': boolean;
Expand Down
2 changes: 1 addition & 1 deletion arduino-ide-extension/src/common/protocol/ide-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface ProgressInfo {
export const IDEUpdaterPath = '/services/ide-updater';
export const IDEUpdater = Symbol('IDEUpdater');
export interface IDEUpdater extends JsonRpcServer<IDEUpdaterClient> {
init(channel: UpdateChannel): void;
init(channel: UpdateChannel, baseUrl: string): void;
checkForUpdates(initialCheck?: boolean): Promise<UpdateInfo | void>;
downloadUpdate(): Promise<void>;
quitAndInstall(): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
} from '../../common/protocol/ide-updater';

const CHANGELOG_BASE_URL = 'https://downloads.arduino.cc/arduino-ide/changelog';
const IDE_DOWNLOAD_BASE_URL = 'https://downloads.arduino.cc/arduino-ide';

@injectable()
export class IDEUpdaterImpl implements IDEUpdater {
Expand All @@ -18,14 +17,12 @@ export class IDEUpdaterImpl implements IDEUpdater {
protected theiaFEClient?: IDEUpdaterClient;
protected clients: Array<IDEUpdaterClient> = [];

init(channel: UpdateChannel): void {
init(channel: UpdateChannel, baseUrl: string): void {
this.updater.autoDownload = false;
this.updater.channel = channel;
this.updater.setFeedURL({
provider: 'generic',
url: `${IDE_DOWNLOAD_BASE_URL}/${
channel === UpdateChannel.Nightly ? 'nightly' : ''
}`,
url: `${baseUrl}/${channel === UpdateChannel.Nightly ? 'nightly' : ''}`,
channel,
});

Expand Down
6 changes: 5 additions & 1 deletion electron/packager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,11 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
for (const fileToCopy of filesToCopy) {
echo(`🚢 >>> Copying ${fileToCopy} to ${targetFolder}.`);
const isZip = await utils.isZip(fileToCopy);
cp('-rf', fileToCopy, targetFolder);
if (isZip && platform === 'linux') {
await utils.adjustArchiveStructure(fileToCopy, targetFolder);
} else {
cp('-rf', fileToCopy, targetFolder);
}
echo(`👌 >>> Copied ${fileToCopy} to ${targetFolder}.`);
}
}
Expand Down
2 changes: 1 addition & 1 deletion electron/packager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"prepare": "yarn test",
"package": "node index.js",
"test": "echo 'No test implemented'"
"test": "mocha \"./test/**/*.test.js\""
},
"keywords": [],
"author": "Arduino SA",
Expand Down
Binary file added electron/packager/test/resources/not-a-zip.dmg
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
119 changes: 119 additions & 0 deletions electron/packager/test/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const fs = require('fs');
const path = require('path');
const expect = require('chai').expect;
const track = require('temp').track();
const unpack = require('../utils').unpack;
const testMe = require('../utils');
const sinon = require('sinon');

describe('utils', () => {

describe('adjustArchiveStructure', () => {

let consoleStub;

beforeEach(() => {
consoleStub = sinon.stub(console, 'log').value(() => { });
});

afterEach(() => {
consoleStub.reset();
track.cleanupSync();
});

it('should reject when not a zip file', async () => {
try {
const invalid = path.join(__dirname, 'resources', 'not-a-zip.dmg');
await testMe.adjustArchiveStructure(invalid, track.mkdirSync());
throw new Error('Expected a rejection');
} catch (e) {
expect(e).to.be.an.instanceOf(Error);
expect(e.message).to.be.equal('Expected a ZIP file.');
}
});

it('should reject when target directory does not exist', async () => {
try {
const zip = path.join(__dirname, 'resources', 'zip-with-base-folder.zip');
await testMe.adjustArchiveStructure(zip, path.join(__dirname, 'some', 'missing', 'path'));
throw new Error('Expected a rejection');
} catch (e) {
expect(e).to.be.an.instanceOf(Error);
expect(e.message.endsWith('does not exist.')).to.be.true;
}
});

it('should reject when target is a file', async () => {
try {
const zip = path.join(__dirname, 'resources', 'zip-with-base-folder.zip');
await testMe.adjustArchiveStructure(zip, path.join(__filename));
throw new Error('Expected a rejection');
} catch (e) {
expect(e).to.be.an.instanceOf(Error);
expect(e.message.endsWith('is not a directory.')).to.be.true;
}
});

it('should be a NOOP when the zip already has the desired base folder', async () => {
const zip = path.join(__dirname, 'resources', 'zip-with-base-folder.zip');
const actual = await testMe.adjustArchiveStructure(zip, track.mkdirSync());
expect(actual).to.be.equal(zip);
});

it('should handle whitespace in file path gracefully', async () => {
const zip = path.join(__dirname, 'resources', 'zip with whitespace.zip');
const out = track.mkdirSync();
const actual = await testMe.adjustArchiveStructure(zip, out, true);
expect(actual).to.be.equal(path.join(out, 'zip with whitespace.zip'));
console.log(actual);
expect(fs.existsSync(actual)).to.be.true;

const verifyOut = track.mkdirSync();
await unpack(actual, verifyOut);

const root = path.join(verifyOut, 'zip with whitespace');
expect(fs.existsSync(root)).to.be.true;
expect(fs.lstatSync(root).isDirectory()).to.be.true;
const subs = fs.readdirSync(root);
expect(subs).to.have.lengthOf(3);
expect(subs.sort()).to.be.deep.equal(['a.txt', 'b.txt', 'foo']);
});

it('should keep the symlinks after ZIP adjustments', async function () {
if (process.platform === 'win32') {
this.skip();
}
const zip = path.join(__dirname, 'resources', 'zip-with-symlink.zip');
const out = track.mkdirSync();
const actual = await testMe.adjustArchiveStructure(zip, out, true);
expect(actual).to.be.equal(path.join(out, 'zip-with-symlink.zip'));
console.log(actual);
expect(fs.existsSync(actual)).to.be.true;

const verifyOut = track.mkdirSync();
await unpack(actual, verifyOut);
expect(fs.lstatSync(path.join(verifyOut, 'zip-with-symlink', 'folder', 'symlinked-sub')).isSymbolicLink()).to.be.true;
});

it('should adjust the archive structure if base folder is not present', async () => {
const zip = path.join(__dirname, 'resources', 'zip-without-symlink.zip');
const out = track.mkdirSync();
const actual = await testMe.adjustArchiveStructure(zip, out, true);
expect(actual).to.be.equal(path.join(out, 'zip-without-symlink.zip'));
console.log(actual);
expect(fs.existsSync(actual)).to.be.true;

const verifyOut = track.mkdirSync();
await unpack(actual, verifyOut);

const root = path.join(verifyOut, 'zip-without-symlink');
expect(fs.existsSync(root)).to.be.true;
expect(fs.lstatSync(root).isDirectory()).to.be.true;
const subs = fs.readdirSync(root);
expect(subs).to.have.lengthOf(3);
expect(subs.sort()).to.be.deep.equal(['a.txt', 'b.txt', 'foo']);
});

});

});
62 changes: 62 additions & 0 deletions electron/packager/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,67 @@ function collectUnusedDependencies(pathToProject = process.cwd()) {
});
}

/**
* `pathToZip` is a `path/to/your/app-name.zip`.
* If the `pathToZip` archive does not have a root directory with name `app-name`, it creates one, and move the content from the
* archive's root to the new root folder. If the archive already has the desired root folder, calling this function is a NOOP.
* If `pathToZip` is not a ZIP, rejects. `targetFolderName` is the destination folder not the new archive location.
*/
function adjustArchiveStructure(pathToZip, targetFolderName, noCleanup) {
return new Promise(async (resolve, reject) => {
if (!(await isZip(pathToZip))) {
reject(new Error(`Expected a ZIP file.`));
return;
}
if (!fs.existsSync(targetFolderName)) {
reject(new Error(`${targetFolderName} does not exist.`));
return;
}
if (!fs.lstatSync(targetFolderName).isDirectory()) {
reject(new Error(`${targetFolderName} is not a directory.`));
return;
}
console.log(`⏱️ >>> Adjusting ZIP structure ${pathToZip}...`);

const root = basename(pathToZip);
const resources = await list(pathToZip);
const hasBaseFolder = resources.find((name) => name === root);
if (hasBaseFolder) {
if (
resources.filter((name) => name.indexOf(path.sep) === -1).length > 1
) {
console.warn(
`${pathToZip} ZIP has the desired root folder ${root}, however the ZIP contains other entries too: ${JSON.stringify(
resources
)}`
);
}
console.log(`👌 <<< The ZIP already has the desired ${root} folder.`);
resolve(pathToZip);
return;
}

const track = temp.track();
try {
const unzipOut = path.join(track.mkdirSync(), root);
fs.mkdirSync(unzipOut);
await unpack(pathToZip, unzipOut);
const adjustedZip = path.join(targetFolderName, path.basename(pathToZip));
await pack(unzipOut, adjustedZip);
console.log(
`👌 <<< Adjusted the ZIP structure. Moved the modified ${basename(
pathToZip
)} to the ${targetFolderName} folder.`
);
resolve(adjustedZip);
} finally {
if (!noCleanup) {
track.cleanupSync();
}
}
});
}

/**
* Returns the `basename` of `pathToFile` without the file extension.
*/
Expand Down Expand Up @@ -152,6 +213,7 @@ function getChannelFile(platform) {

module.exports = {
collectUnusedDependencies,
adjustArchiveStructure,
isZip,
unpack,
isNightly,
Expand Down