diff --git a/README.md b/README.md index ea1fbff..4d173b5 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ - Publishes updated crates in right order according to dependencies - Awaits when published crate will be available in registry before publishing crates which depends from it - Works fine in workspaces without cyclic dependencies +- Support `{ workspace = true }` syntax in the `Cargo.toml` ## Unimplemented features diff --git a/__tests__/Cargo.toml b/__tests__/Cargo.toml index b231b67..cf31354 100644 --- a/__tests__/Cargo.toml +++ b/__tests__/Cargo.toml @@ -7,13 +7,24 @@ members = [ 'pkg-dev', ] +[workspace.package] +version = "0.1.0" + [package] name = "pkg-all" version = "0.1.0" [dependencies] pkg-lib = { version = "0.1.0", path = "./pkg-lib" } +subcrate-d = { workspace = true, path = "../workspace/subcrate_d" } +subcrate-e = { workspace = true, path = "../workspace/subcrate_e" } +subcrate-f = { workspace = true, path = "../workspace/subcrate_f" } [dependencies.pkg-bin] version = "0.1.0" path = "./pkg-bin" + +[workspace.dependencies] +subcrate-d = { version = "0.1.0", path = "./workspace/subcrate_d" } +subcrate-e = { version = "0.1.0", path = "./workspace/subcrate_e" } +subcrate-f = { version = "0.1.0", path = "./workspace/subcrate_f" } diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index bbec1da..b62027d 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -3,22 +3,22 @@ import {findPackages, checkPackages, sortPackages} from '../src/package' import {githubHandle, lastCommitDate} from '../src/github' import {semver} from '../src/utils' import {join} from 'path' -import {exec} from '@actions/exec' const pkg_dir = __dirname test('find packages', async () => { const packages = await findPackages(pkg_dir) - expect(Object.keys(packages).length).toBe(6) + expect(Object.keys(packages).length).toBe(9) const pkg_all = packages['pkg-all'] const pkg_sys = packages['pkg-sys'] const pkg_lib = packages['pkg-lib'] const pkg_bin = packages['pkg-bin'] + const subcrate_e = packages['subcrate-e'] expect(pkg_all.path).toBe(pkg_dir) expect(pkg_all.version).toBe('0.1.0') - expect(Object.keys(pkg_all.dependencies).length).toBe(2) + expect(Object.keys(pkg_all.dependencies).length).toBe(5) expect(pkg_sys.path).toBe(join(pkg_dir, 'pkg-sys')) expect(pkg_sys.version).toBe('0.1.0') @@ -31,6 +31,10 @@ test('find packages', async () => { expect(pkg_bin.path).toBe(join(pkg_dir, 'pkg-bin')) expect(pkg_bin.version).toBe('0.1.0') expect(Object.keys(pkg_bin.dependencies).length).toBe(3) + + expect(subcrate_e.path).toBe(join(pkg_dir, 'workspace/subcrate_e')) + expect(subcrate_e.version).toBe('0.1.0') + expect(Object.keys(subcrate_e.dependencies).length).toBe(1) }) test('check packages', async () => { @@ -45,6 +49,9 @@ test('sort packages', async () => { expect(sorted).toEqual([ 'pkg-sys', 'pkg-lib', + 'subcrate-d', + 'subcrate-f', + 'subcrate-e', 'pkg-build', 'pkg-dev', 'pkg-bin', diff --git a/__tests__/workspace/subcrate_d/Cargo.toml b/__tests__/workspace/subcrate_d/Cargo.toml new file mode 100644 index 0000000..40c16e6 --- /dev/null +++ b/__tests__/workspace/subcrate_d/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "subcrate-d" +version = { workspace = true } + +[dev-dependencies.subcrate-f] +path = "../subcrate_f" \ No newline at end of file diff --git a/__tests__/workspace/subcrate_d/src/lib.rs b/__tests__/workspace/subcrate_d/src/lib.rs new file mode 100644 index 0000000..697efbb --- /dev/null +++ b/__tests__/workspace/subcrate_d/src/lib.rs @@ -0,0 +1,10 @@ +pub const TEST_VALUE: u8 = 5u8; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/__tests__/workspace/subcrate_e/Cargo.toml b/__tests__/workspace/subcrate_e/Cargo.toml new file mode 100644 index 0000000..140391f --- /dev/null +++ b/__tests__/workspace/subcrate_e/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "subcrate-e" +version = { workspace = true } + +[dependencies.subcrate-d] +workspace = true +path = "../subcrate_d" + +[dev-dependencies.subcrate-f] +path = "../subcrate_f" \ No newline at end of file diff --git a/__tests__/workspace/subcrate_e/src/lib.rs b/__tests__/workspace/subcrate_e/src/lib.rs new file mode 100644 index 0000000..a83ef8d --- /dev/null +++ b/__tests__/workspace/subcrate_e/src/lib.rs @@ -0,0 +1,11 @@ +pub use fuel_dummy_test_subcrate_d::TEST_VALUE as OTHER_TEST_VALUE; + +#[cfg(test)] +mod tests { + pub use fuel_dummy_test_subcrate_f::TEST_VALUE; + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/__tests__/workspace/subcrate_f/Cargo.toml b/__tests__/workspace/subcrate_f/Cargo.toml new file mode 100644 index 0000000..17f32e0 --- /dev/null +++ b/__tests__/workspace/subcrate_f/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "subcrate-f" +version = { workspace = true } + +[dependencies] \ No newline at end of file diff --git a/__tests__/workspace/subcrate_f/src/lib.rs b/__tests__/workspace/subcrate_f/src/lib.rs new file mode 100644 index 0000000..697efbb --- /dev/null +++ b/__tests__/workspace/subcrate_f/src/lib.rs @@ -0,0 +1,10 @@ +pub const TEST_VALUE: u8 = 5u8; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/package-lock.json b/package-lock.json index 227389c..201e7dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@actions/glob": "^0.3.0", "@actions/http-client": "^2.0.1", "@iarna/toml": "^2.2.5", + "child_process": "^1.0.2", "semver": "^7.3.7" }, "devDependencies": { @@ -2378,6 +2379,11 @@ "node": ">=10" } }, + "node_modules/child_process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", + "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==" + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -8376,6 +8382,11 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, + "child_process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", + "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==" + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", diff --git a/package.json b/package.json index 7d7a7e5..893fa93 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@actions/glob": "^0.3.0", "@actions/http-client": "^2.0.1", "@iarna/toml": "^2.2.5", + "child_process": "^1.0.2", "semver": "^7.3.7" }, "devDependencies": { diff --git a/src/package.ts b/src/package.ts index 0623323..aa45a5c 100644 --- a/src/package.ts +++ b/src/package.ts @@ -1,56 +1,31 @@ -import {dirname, join, normalize, relative, resolve} from 'path' -import {parse} from '@iarna/toml' -import {create as glob} from '@actions/glob' +import {dirname, join} from 'path' +import {spawnSync} from 'child_process' import {GitHubHandle, lastCommitDate} from './github' -import {readFile, semver, stat} from './utils' +import {semver} from './utils' import {getCrateVersions} from './crates' interface RawDependencies { - [name: string]: - | string - | { - package?: string - version?: string - path?: string - } + name: string + kind: string | null + req: string + path?: string +} + +interface Metadata { + packages: [RawManifest] } interface RawManifest { - workspace?: { - members?: string[] - } - package?: { - name?: string - version?: string - publish?: boolean - } - dependencies?: RawDependencies - 'dev-dependencies'?: RawDependencies - 'build-dependencies'?: RawDependencies + name?: string + manifest_path: string + version?: string + publish?: boolean + dependencies: [RawDependencies] } const manifest_filename = 'Cargo.toml' -async function readManifest(path: string): Promise { - try { - await stat(path) - } catch (error) { - throw new Error(`Manifest file '${path}' not found (${error})`) - } - let raw - try { - raw = await readFile(path, 'utf-8') - } catch (error) { - throw new Error(`Error when reading manifest file '${path}' (${error})`) - } - try { - return parse(raw) - } catch (error) { - throw new Error(`Error when parsing manifest file '${path}' (${error})`) - } -} - export interface Package { path: string version: string @@ -59,7 +34,7 @@ export interface Package { } export interface Dependency { - version: string + req: string path?: string } @@ -79,11 +54,36 @@ export async function findPackages( ? base_path : join(base_path, manifest_filename) const path = dirname(manifest_path) + const command = `cargo` + const args = [ + `metadata`, + `--no-deps`, + `--format-version`, + `1`, + `--manifest-path`, + `${manifest_path}` + ] + + const exec = spawnSync(command, args) - const manifest = await readManifest(manifest_path) + const exec_error = exec.stderr.toString(`utf8`) + + if (exec_error.length > 0) { + throw new Error( + `During "cargo metadata" execution got an error: '${exec_error}'` + ) + } + const output = exec.stdout.toString(`utf8`) + + let metadata: Metadata + + try { + metadata = JSON.parse(output) + } catch (error) { + throw new Error(`Error when parsing manifest file '${path}' (${error})`) + } - if (typeof manifest.package === 'object') { - const {package: package_info} = manifest + for (const package_info of metadata.packages) { if (typeof package_info.name !== 'string') { throw new Error(`Missing package name at '${path}'`) } @@ -93,81 +93,37 @@ export async function findPackages( if (package_info.publish !== false) { const dependencies: Dependencies = {} - for (const [dependency_type, manifest_dependencies] of [ - ['normal', manifest.dependencies], - ['dev', manifest['dev-dependencies']], - ['build', manifest['build-dependencies']] - ]) { - if (typeof manifest_dependencies === 'object') { - for (const name in manifest_dependencies) { - const dependency = manifest_dependencies[name] - if (typeof dependency === 'string') { - dependencies[name] = {version: dependency} - } else if (typeof dependency == 'object') { - if ( - !dependency.version && - // normal and build deps require a version - dependency_type !== 'dev' - ) { - throw new Error( - `Missing dependency '${name}' version field` - ) - } else if ( - // throw an error if there is no path or version on dev-dependencies - dependency_type === 'dev' && - !dependency.version && - !dependency.path - ) { - throw new Error( - `Missing dependency '${name}' version field` - ) - } else if (dependency.version) { - // only include package in dependency graph if version is specified - const package_name = - typeof dependency.package === 'string' - ? dependency.package - : name - dependencies[package_name] = { - version: dependency.version, - path: dependency.path - } - } - } - } + for (const dependency of package_info.dependencies) { + // Dependencies without a version requirement have a value of "*" + // and doesn't affect publishing, so skip them. + if (dependency.req === '*') { + continue + } + + if ( + !!dependency.kind && + dependency.kind === `dev` && + !dependency.path + ) { + throw new Error( + `Missing dependency '${dependency.name}' path field` + ) + } + + dependencies[dependency.name] = { + req: dependency.req, + path: dependency.path } } packages[package_info.name] = { - path, + path: dirname(package_info.manifest_path), version: package_info.version, dependencies } } } - if (typeof manifest.workspace == 'object') { - const tasks: Promise[] = [] - const {workspace} = manifest - if (Array.isArray(workspace.members)) { - const globber = await glob( - workspace.members - .map(member => join(path, member, manifest_filename)) - .join('\n') - ) - const members_paths = await globber.glob() - const parent_path = resolve(path) - for (const member_path of members_paths) { - tasks.push( - findPackages( - join(path, relative(parent_path, member_path)), - packages - ) - ) - } - } - await Promise.all(tasks) - } - return packages } @@ -242,19 +198,17 @@ export async function checkPackages( message: `Package '${package_name}' dependes from internal '${dependency_name}' which is not a workspace member. Listed workspace members only will be published` }) } - const dependency_path = normalize( - join(package_info.path, dependency.path) - ) + const dependency_path = dependency.path if (dependency_path !== dependency_package.path) { errors.push({ kind: 'mismatch-intern-dep-path', message: `Package '${package_name}' depends from internal '${dependency_name}' with path '${dependency_path}' but actual path is '${dependency_package.path}'` }) } - if (!semver(dependency_package.version, dependency.version)) { + if (!semver(dependency_package.version, dependency.req)) { errors.push({ kind: 'mismatch-intern-dep-version', - message: `Package '${package_name}' depends from internal '${dependency_name}' with version '${dependency.version}' but actual version is '${dependency_package.version}'` + message: `Package '${package_name}' depends from internal '${dependency_name}' with version '${dependency.req}' but actual version is '${dependency_package.version}'` }) } } else { @@ -270,7 +224,7 @@ export async function checkPackages( } else { if ( !versions.some(({version}) => - semver(version, dependency.version) + semver(version, dependency.req) ) ) { const versions_string = versions @@ -278,7 +232,7 @@ export async function checkPackages( .join(', ') errors.push({ kind: 'mismatch-extern-dep-version', - message: `Package '${package_name}' depends from external '${dependency_name}' with version '${dependency.version}' which does not satisfies any of '${versions_string}'` + message: `Package '${package_name}' depends from external '${dependency_name}' with version '${dependency.req}' which does not satisfies any of '${versions_string}'` }) } }