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(php): convert composer installer #2848

Merged
merged 5 commits into from
Jun 27, 2024
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
11 changes: 8 additions & 3 deletions docs/custom-registries.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,17 +424,22 @@ https://github.com/containerbase/php-prebuild/releases/8.3.2/php-8.3.2-jammy-x86

Composer releases are downloaded from:

- `https://github.com/containerbase/maven-prebuild/releases`
- `https://getcomposer.org/download`
- `https://api.github.com/repos/composer/composer/releases/latest`
- `https://getcomposer.org/versions`

The second url is only used when `latest` is passed as version.
Then we try to find the latest version from GitHub.
The first url is preferred and the second is used as fallback for older versions.
The last url is only used when `latest` or nothing is passed as version.
Then we try to find the latest version from getcomposer.org.

Samples:

```txt
https://github.com/containerbase/composer-prebuild/releases/2.7.7/composer-2.7.7.tar.xz.sha512
https://github.com/containerbase/composer-prebuild/releases/2.7.7/composer-2.7.7.tar.xz
https://getcomposer.org/download/2.6.6/composer.phar.sha256sum
https://getcomposer.org/download/2.6.6/composer.phar
https://getcomposer.org/versions
```

## `powershell`
Expand Down
8 changes: 7 additions & 1 deletion src/cli/install-tool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
YarnVersionResolver,
} from '../tools/node/resolver';
import { InstallNpmBaseService } from '../tools/node/utils';
import {
ComposerInstallService,
ComposerVersionResolver,
} from '../tools/php/composer';

Check warning on line 41 in src/cli/install-tool/index.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/install-tool/index.ts#L38-L41

Added lines #L38 - L41 were not covered by tests
import { InstallCocoapodsService } from '../tools/ruby/gem';
import { InstallRubyBaseService } from '../tools/ruby/utils';
import { logger } from '../utils';
Expand All @@ -55,15 +59,16 @@
container.bind(InstallLegacyToolService).toSelf();

// tool services
container.bind(INSTALL_TOOL_TOKEN).to(ComposerInstallService);

Check warning on line 62 in src/cli/install-tool/index.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/install-tool/index.ts#L62

Added line #L62 was not covered by tests
container.bind(INSTALL_TOOL_TOKEN).to(InstallBazeliskService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallBunService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallGleamService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallCocoapodsService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallDartService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallDockerService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallDotnetService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallFlutterService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallFluxService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallGleamService);

Check warning on line 71 in src/cli/install-tool/index.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/install-tool/index.ts#L71

Added line #L71 was not covered by tests
container.bind(INSTALL_TOOL_TOKEN).to(InstallGradleService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallJavaService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallJavaJreService);
Expand All @@ -87,6 +92,7 @@
container.bind(ToolVersionResolverService).toSelf();

// tool version resolver
container.bind(TOOL_VERSION_RESOLVER).to(ComposerVersionResolver);

Check warning on line 95 in src/cli/install-tool/index.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/install-tool/index.ts#L95

Added line #L95 was not covered by tests
container.bind(TOOL_VERSION_RESOLVER).to(GradleVersionResolver);
container.bind(TOOL_VERSION_RESOLVER).to(JavaVersionResolver);
container.bind(TOOL_VERSION_RESOLVER).to(JavaJreVersionResolver);
Expand Down
1 change: 1 addition & 0 deletions src/cli/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const NoPrepareTools = [
'bun',
'bundler',
'cocoapods',
'composer',
'corepack',
'flux',
'gleam',
Expand Down
135 changes: 135 additions & 0 deletions src/cli/tools/php/composer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import fs from 'node:fs/promises';
import { join } from 'node:path';
import { isNonEmptyStringAndNotWhitespace } from '@sindresorhus/is';
import { execa } from 'execa';
import { inject, injectable } from 'inversify';
import { sort } from 'semver';
import { z } from 'zod';
import { InstallToolBaseService } from '../../install-tool/install-tool-base.service';
import { ToolVersionResolver } from '../../install-tool/tool-version-resolver';
import {
CompressionService,
EnvService,
HttpService,
PathService,
} from '../../services';
import type { HttpChecksumType } from '../../services/http.service';
import { logger } from '../../utils';

@injectable()
export class ComposerInstallService extends InstallToolBaseService {
readonly name = 'composer';

constructor(
@inject(EnvService) envSvc: EnvService,
@inject(PathService) pathSvc: PathService,
@inject(HttpService) private http: HttpService,
@inject(CompressionService) private compress: CompressionService,
) {
super(pathSvc, envSvc);
}

override async install(version: string): Promise<void> {
const name = this.name;
let filename = `${name}-${version}.tar.xz`;
let url = `https://github.com/containerbase/${name}-prebuild/releases/download/${version}/${filename}`;
let checksumFileUrl = `${url}.sha512`;
const isOnGithub = await this.http.exists(checksumFileUrl);
let file: string;

if (isOnGithub) {
logger.info(`using github`);
const checksumFile = await this.http.download({ url: checksumFileUrl });
const expectedChecksum = (
await fs.readFile(checksumFile, 'utf-8')
).trim();
file = await this.http.download({
url,
checksumType: 'sha512',
expectedChecksum,
});
} else {
logger.info(`using getcomposer.org`);
// fallback to getcomposer.org
filename = `composer.phar`;
url = `https://getcomposer.org/download/${version}/composer.phar`;
checksumFileUrl = `${url}.sha256sum`;
let expectedChecksum: string | undefined;
let checksumType: HttpChecksumType | undefined;
if (await this.http.exists(checksumFileUrl)) {
logger.debug(`using sha256sum checksum for ${filename}`);
expectedChecksum = await this.readChecksum(`${url}.sha256sum`);
checksumType = 'sha256';
} else {
throw new Error(`checksum file not found for ${filename}`);
}

if (!checksumType || !expectedChecksum) {
throw new Error(`checksum not found for ${filename}`);
}

file = await this.http.download({
url,
checksumType,
expectedChecksum,
});
}

let path = await this.pathSvc.findToolPath(this.name);
if (!path) {
path = await this.pathSvc.createToolPath(this.name);
}

if (isOnGithub) {
await this.compress.extract({ file, cwd: path });
} else {
// from getcomposer.org
path = await this.pathSvc.createVersionedToolPath(this.name, version);
path = join(path, 'bin');
await fs.mkdir(path);
path = join(path, filename);
await fs.cp(file, path);
await fs.chmod(path, 0o755);
}
}

override async link(version: string): Promise<void> {
const src = join(this.pathSvc.versionedToolPath(this.name, version), 'bin');
await this.shellwrapper({ srcDir: src });
}

override async test(_version: string): Promise<void> {
await execa('composer', ['--version'], {
stdio: ['inherit', 'inherit', 1],
});
}

private async readChecksum(url: string): Promise<string | undefined> {
const checksumFile = await this.http.download({ url });
return (await fs.readFile(checksumFile, 'utf-8')).split(' ')[0]?.trim();
}
}

@injectable()
export class ComposerVersionResolver extends ToolVersionResolver {
readonly tool = 'composer';

async resolve(version: string | undefined): Promise<string | undefined> {
if (!isNonEmptyStringAndNotWhitespace(version) || version === 'latest') {
const meta = ComposerVersionsSchema.parse(
await this.http.getJson('https://getcomposer.org/versions'),
);
// we know that the latest version is the first entry, so search for first lts
return meta;
}
return version;
}
}

const ComposerVersionsSchema = z
.object({
stable: z.array(z.object({ version: z.string() })),
})
.transform(({ stable }) => {
return sort(stable.map((v) => v.version)).pop();
});

Check warning on line 135 in src/cli/tools/php/composer.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/tools/php/composer.ts#L1-L135

Added lines #L1 - L135 were not covered by tests
36 changes: 0 additions & 36 deletions src/usr/local/containerbase/tools/v2/composer.sh

This file was deleted.

7 changes: 6 additions & 1 deletion test/php/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ RUN install-tool php 8.3.7
# renovate: datasource=github-releases packageName=composer/composer
RUN install-tool composer 2.7.7

# test with latest version
RUN install-tool composer latest

USER 1000
Expand Down Expand Up @@ -152,7 +153,8 @@ USER 1000
# renovate: datasource=github-releases packageName=containerbase/php-prebuild
RUN install-tool php 8.3.7

RUN install-tool composer latest
# test without version
RUN install-tool composer

# renovate: datasource=github-releases packageName=composer/composer
RUN install-tool composer 2.7.7
Expand All @@ -176,6 +178,9 @@ RUN set -ex; \
RUN php --version
RUN composer --version

# test from getcomposer.org
RUN install-tool composer 2.0.0

#--------------------------------------
# final
#--------------------------------------
Expand Down